Metadata-Version: 2.2
Name: gl-sandbox-binary
Version: 0.0.1b1
Summary: Provider-agnostic sandbox SDK — code interpreter and template building across E2B, OpenSandbox and Bedrock AgentCore.
Author-email: Christopher Julius Limantoro <christopher.j.limantoro@gdplabs.id>
Requires-Python: <3.14,>=3.11
Description-Content-Type: text/markdown
Requires-Dist: pydantic<3.0.0,>=2.9.1
Requires-Dist: gllm-core-binary<0.5.0,>=0.3.0
Provides-Extra: e2b
Requires-Dist: e2b-code-interpreter<3.0.0,>=2.2.0; extra == "e2b"
Requires-Dist: httpx<1.0.0,>=0.27.0; extra == "e2b"
Provides-Extra: opensandbox
Requires-Dist: opensandbox<0.2.0,>=0.1.9; extra == "opensandbox"
Requires-Dist: opensandbox-code-interpreter<0.2.0,>=0.1.2; extra == "opensandbox"
Requires-Dist: boto3<2.0.0,>=1.34.0; extra == "opensandbox"
Provides-Extra: bedrock
Requires-Dist: aioboto3<16.0.0,>=15.0.0; extra == "bedrock"
Provides-Extra: all
Requires-Dist: gl-sandbox-binary[bedrock,e2b,opensandbox]; extra == "all"

# gl-sandbox

Provider-agnostic sandbox SDK: run untrusted code, ship files in and out, and build reusable
sandbox images — across **E2B**, **OpenSandbox** and **AWS Bedrock AgentCore** behind one
interface.

## Installation

The core install is provider-free — it pulls in no backend SDK. Pick the backends you need:

```bash
pip install gl-sandbox                    # ABCs, models, errors, tracing — no backend
pip install 'gl-sandbox[e2b]'             # + E2B
pip install 'gl-sandbox[opensandbox]'     # + OpenSandbox
pip install 'gl-sandbox[bedrock]'         # + AWS Bedrock AgentCore
pip install 'gl-sandbox[all]'             # + everything
```

Importing a backend module without its extra raises an `ImportError` naming the extra to install.

## Layout

The package is organized **capability-first**: a provider-agnostic root, then one package per
capability, then one module per provider inside it.

```
gl_sandbox/
├── base.py              BaseSandbox — provider- and capability-agnostic lifecycle
├── models.py            ExecutionResult, ExecutionStatus, SandboxFile
├── errors.py            Stage-attributed error taxonomy
├── observability.py     Optional OpenTelemetry spans (no-op when OTel is absent)
├── constants.py         Shared defaults
├── utils.py             Install-code generation, timeout resolution, retry wrappers
├── providers/           Provider-scoped helpers shared across capabilities
│   └── opensandbox/
│       └── snapshot.py    Snapshot name → id resolution      [opensandbox]
├── code_interpreter/    Capability: run code
│   ├── base.py            CodeInterpreterSandbox
│   ├── e2b.py             E2BSandbox                  [e2b]
│   ├── opensandbox.py     OpenSandbox                 [opensandbox]
│   └── bedrock.py         BedrockAgentCoreSandbox     [bedrock]
├── computer_use/        Capability: reserved for the desktop-automation axis
└── template/            Build sandbox images / templates / snapshots
    ├── base.py            BaseTemplateBuilder
    ├── e2b.py             E2BTemplateBuilder          [e2b]
    ├── opensandbox.py     OpenSandboxTemplateBuilder  [opensandbox]
    └── bedrock.py         BedrockTemplateBuilder      (no extra needed — no build step)
```

**Backends live in their own modules, by design.** `gl_sandbox`, `gl_sandbox.code_interpreter`
and `gl_sandbox.template` export only ABCs, models and errors, so importing them pulls in no
provider SDK. Import a concrete backend from its own module:

```python
from gl_sandbox.code_interpreter.e2b import E2BSandbox  # not from gl_sandbox
```

## Usage

### Run code in a sandbox

```python
import asyncio

from gl_sandbox import ExecutionStatus
from gl_sandbox.code_interpreter.e2b import E2BSandbox


async def main() -> None:
    sandbox = await E2BSandbox.create(api_key="e2b_...", additional_packages=["numpy"])
    try:
        result = await sandbox.execute_code("import numpy; print(numpy.__version__)")
        if result.status is ExecutionStatus.SUCCESS:
            print(result.stdout)
        else:
            print(result.error)
    finally:
        await sandbox.terminate()


asyncio.run(main())
```

### Upload files, then run against them

`SandboxFile` is gl-sandbox's own two-field DTO, so nothing here depends on an inference library.
Any structurally compatible object (`.filename` + `.data`) is accepted, including
`gllm_inference.schema.Attachment`.

```python
from gl_sandbox import SandboxFile

files = [SandboxFile.from_bytes(b"a,b\n1,2\n", "data.csv")]
result = await sandbox.execute_code(
    "import pandas as pd; print(pd.read_csv('/files/data.csv'))",
    files=files,
)
```

### Build a template once, create sandboxes from it

```python
from gl_sandbox.template import TemplateSpec
from gl_sandbox.template.e2b import E2BTemplateBuilder

builder = E2BTemplateBuilder(api_key="e2b_...")
result = await builder.ensure(TemplateSpec(name="my-base", packages=["pandas", "numpy"]))
sandbox = await E2BSandbox.create(api_key="e2b_...", template=result.ref.template_id)
```

### Errors carry the stage that failed

Every exception records the `Stage` at its raise site and exposes `transient`, so retry logic and
user-facing messages never drift apart.

```python
from gl_sandbox import SandboxStartError, Stage

try:
    sandbox = await E2BSandbox.create(api_key="e2b_...")
except SandboxStartError as exc:
    if exc.stage is Stage.CREATE and exc.transient:
        ...  # worth retrying
```

### Tracing

`observability.py` emits OpenTelemetry spans under the `gl_sandbox.*` namespace against whatever
tracer provider the host application configured. It is fully optional: with OpenTelemetry absent,
every helper degrades to a no-op, and gl-sandbox takes no dependency on it.

## Development

```bash
make setup             # uv + pre-commit + dependencies
make test              # unit tests with coverage (needs --all-extras; see below)
make test-core         # import smoke test: core install must work with no extras
make test-integration  # live, credential-gated tests that provision real sandboxes
make ruff              # lint + format check
```

Unit tests require `--all-extras`: only the OpenSandbox SDK is stubbed in `conftest.py`, while
the E2B and Bedrock tests import their SDKs for real. Integration tests are excluded by default
via `addopts = "-m 'not integration'"` and each self-skips without its credentials.

## Relationship to gllm-tools

This library was extracted from `gllm-tools` (`gllm_tools/code_interpreter/`) as a 1:1 copy.
`gllm-tools` retains its own copy and is unaffected; the two have **not** yet been deduplicated.

Deliberate differences from the origin:

| | |
|---|---|
| `gllm-inference` dependency | **removed** — `Attachment` replaced by `SandboxFile` |
| Provider SDKs | **extras-only** — core install pulls in no backend |
| `PydanticExecutorSandbox` | **dropped** (was deprecated) |
| E2B / Bedrock imports | **guarded**, so a missing extra names itself |
| Layout | capability-first, so `computer_use/` can land beside `code_interpreter/` |

Everything the port deliberately did **not** clean up is catalogued in
[docs/TECHNICAL-DEBT.md](docs/TECHNICAL-DEBT.md) — 29 open items ranked by severity, plus the
invariants that look like bugs and must be preserved.
