Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/ref/extensions/sandbox/upstash_box/sandbox.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `Sandbox`

::: agents.extensions.sandbox.upstash_box.sandbox
3 changes: 3 additions & 0 deletions docs/sandbox/clients.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ For provider-specific setup notes and links for the checked-in extension example
| `E2BSandboxClient` | `openai-agents[e2b]` | [E2B runner](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/e2b_runner.py) |
| `ModalSandboxClient` | `openai-agents[modal]` | [Modal runner](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/modal_runner.py) |
| `RunloopSandboxClient` | `openai-agents[runloop]` | [Runloop runner](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/runloop/runner.py) |
| `UpstashBoxSandboxClient` | `openai-agents[upstash-box]` | [Upstash Box runner](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/upstash_box_runner.py) |
| `VercelSandboxClient` | `openai-agents[vercel]` | [Vercel runner](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/vercel_runner.py) |

</div>
Expand All @@ -113,6 +114,7 @@ Hosted sandbox clients expose provider-specific mount strategies. Choose the bac
| `DaytonaSandboxClient` | Supports rclone-backed cloud storage mounts with `DaytonaCloudBucketMountStrategy`; use it with `S3Mount`, `GCSMount`, `R2Mount`, `AzureBlobMount`, and `BoxMount`. |
| `E2BSandboxClient` | Supports rclone-backed cloud storage mounts with `E2BCloudBucketMountStrategy`; use it with `S3Mount`, `GCSMount`, `R2Mount`, `AzureBlobMount`, and `BoxMount`. |
| `RunloopSandboxClient` | Supports rclone-backed cloud storage mounts with `RunloopCloudBucketMountStrategy`; use it with `S3Mount`, `GCSMount`, `R2Mount`, `AzureBlobMount`, and `BoxMount`. |
| `UpstashBoxSandboxClient` | No hosted-specific mount strategy is currently exposed. Use manifest files, repos, or other workspace inputs instead. |
| `VercelSandboxClient` | No hosted-specific mount strategy is currently exposed. Use manifest files, repos, or other workspace inputs instead. |

</div>
Expand All @@ -130,6 +132,7 @@ The table below summarizes which remote storage entries each backend can mount d
| `DaytonaSandboxClient` | ✓ | ✓ | ✓ | ✓ | ✓ | - |
| `E2BSandboxClient` | ✓ | ✓ | ✓ | ✓ | ✓ | - |
| `RunloopSandboxClient` | ✓ | ✓ | ✓ | ✓ | ✓ | - |
| `UpstashBoxSandboxClient` | - | - | - | - | - | - |
| `VercelSandboxClient` | - | - | - | - | - | - |

</div>
Expand Down
1 change: 1 addition & 0 deletions examples/run_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"examples/sandbox/docker/mounts/s3_mount_read_write.py",
"examples/sandbox/extensions/daytona/usaspending_text2sql/setup_db.py",
"examples/sandbox/extensions/temporal/temporal_sandbox_agent.py",
"examples/sandbox/extensions/upstash_box_runner.py",
"examples/sandbox/extensions/vercel_runner.py",
"examples/sandbox/memory_s3.py",
"examples/sandbox/sandbox_agent_with_remote_snapshot.py",
Expand Down
30 changes: 30 additions & 0 deletions examples/sandbox/extensions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,33 @@ Blaxel sandboxes support cloud bucket mounts (S3, R2, GCS) through
`BlaxelCloudBucketMountStrategy` and persistent drive mounts through
`BlaxelDriveMountStrategy`. See the
[Blaxel Drive docs](https://docs.blaxel.ai/Agent-drive/Overview) for details.

## Upstash Box

### Setup

Install the repo extra:

```bash
uv sync --extra upstash-box
```

Create an Upstash Box API key in the [Upstash Console](https://console.upstash.com) and export
the required environment variables:

```bash
export OPENAI_API_KEY=...
export UPSTASH_BOX_API_KEY=...
```

Optionally set `UPSTASH_BOX_BASE_URL` to target a non-default Box API endpoint.

### Run

```bash
uv run python examples/sandbox/extensions/upstash_box_runner.py --stream
```

Upstash Box has no Python SDK, so this client talks to the Box REST API directly over HTTP.
It supports command execution, file read/write, exposed ports, keep-alive boxes,
create-from-snapshot (via `snapshot_id`), and pause/resume lifecycle.
137 changes: 137 additions & 0 deletions examples/sandbox/extensions/upstash_box_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""
Upstash Box-backed sandbox example for manual validation.

This example mirrors the other extension runners. It supports a standard agent
run (non-streaming and streaming) against an Upstash Box sandbox.

Requires ``OPENAI_API_KEY`` and ``UPSTASH_BOX_API_KEY`` in the environment.
"""

from __future__ import annotations

import argparse
import asyncio
import os
import sys
from pathlib import Path

from openai.types.responses import ResponseTextDeltaEvent

from agents import ModelSettings, Runner, set_tracing_disabled
from agents.run import RunConfig
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
from agents.sandbox.capabilities import Shell

if __package__ is None or __package__ == "":
sys.path.insert(0, str(Path(__file__).resolve().parents[3]))

from examples.sandbox.misc.example_support import text_manifest

try:
from agents.extensions.sandbox import (
UpstashBoxSandboxClient,
UpstashBoxSandboxClientOptions,
)
except Exception as exc: # pragma: no cover - import path depends on optional extras
raise SystemExit(
"Upstash Box sandbox examples require the optional repo extra.\n"
"Install it with: uv sync --extra upstash-box"
) from exc


DEFAULT_MODEL = "gpt-5.5"
DEFAULT_QUESTION = "Summarize this cloud sandbox workspace in 2 sentences."


def _build_manifest() -> Manifest:
return text_manifest(
{
"README.md": (
"# Upstash Box Demo Workspace\n\n"
"This workspace exists to validate the Upstash Box sandbox backend manually.\n"
),
"launch.md": (
"# Launch\n\n"
"- Customer: Contoso Logistics.\n"
"- Goal: validate the remote sandbox agent path.\n"
),
"tasks.md": (
"# Tasks\n\n"
"1. Inspect the workspace files.\n"
"2. Summarize the setup in two sentences.\n"
),
}
)


def _require_env(name: str) -> str:
value = os.environ.get(name)
if value:
return value
raise SystemExit(f"{name} must be set before running this example.")


async def main(*, model: str, question: str, api_key: str | None, stream: bool) -> None:
_require_env("OPENAI_API_KEY")
_require_env("UPSTASH_BOX_API_KEY")

agent = SandboxAgent(
name="Upstash Box Sandbox Assistant",
model=model,
instructions=(
"Answer questions about the sandbox workspace. Inspect the files before answering "
"and keep the response concise. Cite the file names you inspected."
),
default_manifest=_build_manifest(),
capabilities=[Shell()],
model_settings=ModelSettings(tool_choice="required"),
)

run_config = RunConfig(
sandbox=SandboxRunConfig(
client=UpstashBoxSandboxClient(),
options=UpstashBoxSandboxClientOptions(api_key=api_key),
),
workflow_name="Upstash Box sandbox example",
)

if not stream:
result = await Runner.run(agent, question, run_config=run_config)
print(result.final_output)
return

stream_result = Runner.run_streamed(agent, question, run_config=run_config)
saw_text_delta = False
async for event in stream_result.stream_events():
if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent):
if not saw_text_delta:
print("assistant> ", end="", flush=True)
saw_text_delta = True
print(event.data.delta, end="", flush=True)

if saw_text_delta:
print()


if __name__ == "__main__":
set_tracing_disabled(True)

parser = argparse.ArgumentParser(description="Run an Upstash Box sandbox agent.")
parser.add_argument("--model", default=DEFAULT_MODEL, help="Model name to use.")
parser.add_argument("--question", default=DEFAULT_QUESTION, help="Prompt to send to the agent.")
parser.add_argument(
"--api-key",
default=os.environ.get("UPSTASH_BOX_API_KEY"),
help="Upstash Box API key. Defaults to UPSTASH_BOX_API_KEY.",
)
parser.add_argument("--stream", action="store_true", default=False, help="Stream the response.")
args = parser.parse_args()

asyncio.run(
main(
model=args.model,
question=args.question,
api_key=args.api_key,
stream=args.stream,
)
)
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ e2b = ["e2b==2.20.0", "e2b-code-interpreter==2.4.1"]
modal = ["modal==1.4.3"]
runloop = ["runloop_api_client>=1.16.0,<2.0.0"]
vercel = ["vercel>=0.5.6,<0.6"]
upstash-box = ["aiohttp>=3.12,<4"]
s3 = ["boto3>=1.34"]
temporal = [
"temporalio==1.26.0",
Expand Down
22 changes: 22 additions & 0 deletions src/agents/extensions/sandbox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@
except Exception: # pragma: no cover
_HAS_VERCEL = False

try:
from .upstash_box import (
UpstashBoxSandboxClient as UpstashBoxSandboxClient,
UpstashBoxSandboxClientOptions as UpstashBoxSandboxClientOptions,
UpstashBoxSandboxSession as UpstashBoxSandboxSession,
UpstashBoxSandboxSessionState as UpstashBoxSandboxSessionState,
)

_HAS_UPSTASH_BOX = True
except Exception: # pragma: no cover
_HAS_UPSTASH_BOX = False

__all__: list[str] = []

if _HAS_E2B:
Expand Down Expand Up @@ -187,6 +199,16 @@
]
)

if _HAS_UPSTASH_BOX:
__all__.extend(
[
"UpstashBoxSandboxClient",
"UpstashBoxSandboxClientOptions",
"UpstashBoxSandboxSession",
"UpstashBoxSandboxSessionState",
]
)

if _HAS_RUNLOOP:
__all__.extend(
[
Expand Down
15 changes: 15 additions & 0 deletions src/agents/extensions/sandbox/upstash_box/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from __future__ import annotations

from .sandbox import (
UpstashBoxSandboxClient,
UpstashBoxSandboxClientOptions,
UpstashBoxSandboxSession,
UpstashBoxSandboxSessionState,
)

__all__ = [
"UpstashBoxSandboxClient",
"UpstashBoxSandboxClientOptions",
"UpstashBoxSandboxSession",
"UpstashBoxSandboxSessionState",
]
Loading