chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
Minimal Runloop-backed sandbox example for manual validation.
|
||||
|
||||
This mirrors the other cloud extension examples: it creates a tiny workspace, asks a sandboxed
|
||||
agent to inspect it through one shell tool, and prints a short answer.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from openai.types.responses import ResponseTextDeltaEvent
|
||||
|
||||
from agents import ModelSettings, Runner
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
|
||||
|
||||
if __package__ is None or __package__ == "":
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[4]))
|
||||
|
||||
from examples.sandbox.misc.example_support import text_manifest
|
||||
from examples.sandbox.misc.workspace_shell import WorkspaceShellCapability
|
||||
|
||||
try:
|
||||
from agents.extensions.sandbox import (
|
||||
DEFAULT_RUNLOOP_ROOT_WORKSPACE_ROOT,
|
||||
DEFAULT_RUNLOOP_WORKSPACE_ROOT,
|
||||
RunloopSandboxClient,
|
||||
RunloopSandboxClientOptions,
|
||||
RunloopUserParameters,
|
||||
)
|
||||
except Exception as exc: # pragma: no cover - import path depends on optional extras
|
||||
raise SystemExit(
|
||||
"Runloop sandbox examples require the optional repo extra.\n"
|
||||
"Install it with: uv sync --extra runloop"
|
||||
) from exc
|
||||
|
||||
|
||||
DEFAULT_QUESTION = "Summarize this cloud sandbox workspace in 2 sentences."
|
||||
|
||||
|
||||
def _build_manifest(*, workspace_root: str) -> Manifest:
|
||||
manifest = text_manifest(
|
||||
{
|
||||
"README.md": (
|
||||
"# Runloop Demo Workspace\n\n"
|
||||
"This workspace exists to validate the Runloop sandbox backend manually.\n"
|
||||
),
|
||||
"launch.md": (
|
||||
"# Launch\n\n"
|
||||
"- Customer: Contoso Logistics.\n"
|
||||
"- Goal: validate the remote sandbox agent path.\n"
|
||||
"- Current status: Runloop backend smoke and app-server connectivity are passing.\n"
|
||||
),
|
||||
"tasks.md": (
|
||||
"# Tasks\n\n"
|
||||
"1. Inspect the workspace files.\n"
|
||||
"2. Summarize the setup and any notable status in two sentences.\n"
|
||||
),
|
||||
}
|
||||
)
|
||||
return Manifest(root=workspace_root, entries=manifest.entries)
|
||||
|
||||
|
||||
def _require_env(name: str) -> None:
|
||||
if os.environ.get(name):
|
||||
return
|
||||
raise SystemExit(f"{name} must be set before running this example.")
|
||||
|
||||
|
||||
async def main(
|
||||
*,
|
||||
model: str,
|
||||
question: str,
|
||||
pause_on_exit: bool,
|
||||
blueprint_name: str | None,
|
||||
root: bool,
|
||||
stream: bool,
|
||||
) -> None:
|
||||
_require_env("OPENAI_API_KEY")
|
||||
_require_env("RUNLOOP_API_KEY")
|
||||
|
||||
workspace_root = DEFAULT_RUNLOOP_ROOT_WORKSPACE_ROOT if root else DEFAULT_RUNLOOP_WORKSPACE_ROOT
|
||||
manifest = _build_manifest(workspace_root=workspace_root)
|
||||
agent = SandboxAgent(
|
||||
name="Runloop Sandbox Assistant",
|
||||
model=model,
|
||||
instructions=(
|
||||
"Answer questions about the sandbox workspace. Inspect the files before answering "
|
||||
"and keep the response concise. "
|
||||
"Do not invent files or statuses that are not present in the workspace. Cite the "
|
||||
"file names you inspected."
|
||||
),
|
||||
default_manifest=manifest,
|
||||
capabilities=[WorkspaceShellCapability()],
|
||||
model_settings=ModelSettings(tool_choice="required"),
|
||||
)
|
||||
|
||||
client = RunloopSandboxClient()
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=client,
|
||||
options=RunloopSandboxClientOptions(
|
||||
blueprint_name=blueprint_name,
|
||||
pause_on_exit=pause_on_exit,
|
||||
user_parameters=(RunloopUserParameters(username="root", uid=0) if root else None),
|
||||
),
|
||||
),
|
||||
workflow_name="Runloop sandbox example",
|
||||
)
|
||||
|
||||
try:
|
||||
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()
|
||||
finally:
|
||||
await client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--model", default="gpt-5.6-sol", help="Model name to use.")
|
||||
parser.add_argument("--question", default=DEFAULT_QUESTION, help="Prompt to send to the agent.")
|
||||
parser.add_argument(
|
||||
"--pause-on-exit",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Suspend the Runloop devbox on shutdown instead of deleting it.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--blueprint-name",
|
||||
default=None,
|
||||
help="Optional Runloop blueprint name to use when creating the devbox.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--root",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Launch the Runloop devbox as root. The default home/workspace root becomes /root.",
|
||||
)
|
||||
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,
|
||||
pause_on_exit=args.pause_on_exit,
|
||||
blueprint_name=args.blueprint_name,
|
||||
root=args.root,
|
||||
stream=args.stream,
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user