openai-agents-msb 0.1.0__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 (37) hide show
  1. openai_agents_msb-0.1.0/.gitignore +47 -0
  2. openai_agents_msb-0.1.0/LICENSE +21 -0
  3. openai_agents_msb-0.1.0/PKG-INFO +182 -0
  4. openai_agents_msb-0.1.0/README.md +157 -0
  5. openai_agents_msb-0.1.0/pyproject.toml +81 -0
  6. openai_agents_msb-0.1.0/src/openai_agents_msb/__init__.py +50 -0
  7. openai_agents_msb-0.1.0/src/openai_agents_msb/_discover.py +85 -0
  8. openai_agents_msb-0.1.0/src/openai_agents_msb/_exec.py +125 -0
  9. openai_agents_msb-0.1.0/src/openai_agents_msb/_network.py +156 -0
  10. openai_agents_msb-0.1.0/src/openai_agents_msb/_run.py +482 -0
  11. openai_agents_msb-0.1.0/src/openai_agents_msb/_session.py +126 -0
  12. openai_agents_msb-0.1.0/src/openai_agents_msb/_workspace.py +249 -0
  13. openai_agents_msb-0.1.0/src/openai_agents_msb/core/__init__.py +42 -0
  14. openai_agents_msb-0.1.0/src/openai_agents_msb/core/errors.py +25 -0
  15. openai_agents_msb-0.1.0/src/openai_agents_msb/core/sandbox_store.py +49 -0
  16. openai_agents_msb-0.1.0/src/openai_agents_msb/core/types.py +159 -0
  17. openai_agents_msb-0.1.0/src/openai_agents_msb/core/validate.py +17 -0
  18. openai_agents_msb-0.1.0/src/openai_agents_msb/core/yaml.py +22 -0
  19. openai_agents_msb-0.1.0/src/openai_agents_msb/py.typed +0 -0
  20. openai_agents_msb-0.1.0/src/openai_agents_msb/tools/__init__.py +17 -0
  21. openai_agents_msb-0.1.0/src/openai_agents_msb/tools/_base.py +50 -0
  22. openai_agents_msb-0.1.0/src/openai_agents_msb/tools/sandbox_config.py +117 -0
  23. openai_agents_msb-0.1.0/src/openai_agents_msb/tools/sandbox_exec.py +53 -0
  24. openai_agents_msb-0.1.0/src/openai_agents_msb/tools/sandbox_list_files.py +32 -0
  25. openai_agents_msb-0.1.0/src/openai_agents_msb/tools/sandbox_read_file.py +40 -0
  26. openai_agents_msb-0.1.0/src/openai_agents_msb/tools/sandbox_write_file.py +50 -0
  27. openai_agents_msb-0.1.0/tests/__init__.py +1 -0
  28. openai_agents_msb-0.1.0/tests/conftest.py +299 -0
  29. openai_agents_msb-0.1.0/tests/test_exec.py +115 -0
  30. openai_agents_msb-0.1.0/tests/test_network.py +152 -0
  31. openai_agents_msb-0.1.0/tests/test_open.py +165 -0
  32. openai_agents_msb-0.1.0/tests/test_run.py +356 -0
  33. openai_agents_msb-0.1.0/tests/test_sessions.py +293 -0
  34. openai_agents_msb-0.1.0/tests/test_stream.py +191 -0
  35. openai_agents_msb-0.1.0/tests/test_tools.py +216 -0
  36. openai_agents_msb-0.1.0/tests/test_vm.py +325 -0
  37. openai_agents_msb-0.1.0/tests/test_workspace.py +236 -0
@@ -0,0 +1,47 @@
1
+ # dependencies (bun install)
2
+ node_modules
3
+
4
+ # output
5
+ out
6
+ dist
7
+ *.tgz
8
+
9
+ # code coverage
10
+ coverage
11
+ *.lcov
12
+
13
+ # logs
14
+ logs
15
+ _.log
16
+ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
17
+
18
+ # dotenv environment variable files
19
+ .env
20
+ .env.development.local
21
+ .env.test.local
22
+ .env.production.local
23
+ .env.local
24
+
25
+ # caches
26
+ .eslintcache
27
+ .cache
28
+ *.tsbuildinfo
29
+
30
+ # IntelliJ based IDEs
31
+ .idea
32
+
33
+ # Finder (MacOS) folder config
34
+ .DS_Store
35
+ design.txt
36
+ .pnpm-store
37
+ examples/law-firm/.sandbox-session
38
+
39
+ # python
40
+ .venv
41
+ __pycache__
42
+ *.py[cod]
43
+ *.egg-info
44
+ .pytest_cache
45
+ .mypy_cache
46
+ .ruff_cache
47
+ uv.lock
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 iyifr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,182 @@
1
+ Metadata-Version: 2.5
2
+ Name: openai-agents-msb
3
+ Version: 0.1.0
4
+ Summary: Drop-in run() replacement and sandbox tools that give openai-agents agents an isolated microsandbox workspace.
5
+ Project-URL: Homepage, https://github.com/iyifr/model-sandbox-tools#readme
6
+ Project-URL: Repository, https://github.com/iyifr/model-sandbox-tools
7
+ Project-URL: Issues, https://github.com/iyifr/model-sandbox-tools/issues
8
+ Author: iyifr
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai-agents,code-interpreter,llm,microsandbox,openai-agents,sandbox,tools
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: microsandbox<0.8,>=0.7.6
23
+ Requires-Dist: openai-agents<0.24,>=0.23
24
+ Description-Content-Type: text/markdown
25
+
26
+ # openai-agents-msb
27
+
28
+ **Give [OpenAI Agents](https://github.com/openai/openai-agents-python) an isolated [microsandbox](https://github.com/superradcompany/microsandbox) VM to run code and edit files in.**
29
+
30
+ - **Drop-in `run()`**: Same arguments as `Runner.run()`. Swap the call, keep your agent.
31
+ - **Files in, files out**: Seed `/workspace`, get every new or changed file back after the run.
32
+ - **Lazy VMs**: The sandbox boots on the first tool call. Chat-only turns cost nothing.
33
+ - **Locked-down network**: No network by default; allow the public internet or specific hosts.
34
+ - **Secrets that stay out**: Code in the VM sees a placeholder, never the real key.
35
+ - **Persistent sessions**: Keep one VM per conversation across turns.
36
+
37
+ ## Getting Started
38
+
39
+ ```sh
40
+ pip install openai-agents-msb
41
+ ```
42
+
43
+ > **Requirements**: Python 3.10+ on macOS (Apple Silicon), Linux (KVM) or Windows (WHP).
44
+
45
+ ```python
46
+ import asyncio
47
+ from pathlib import Path
48
+
49
+ from agents import Agent
50
+ from openai_agents_msb import (
51
+ InputFile,
52
+ WorkspaceContext,
53
+ run,
54
+ sandbox_config,
55
+ sandbox_exec,
56
+ sandbox_read_file,
57
+ sandbox_write_file,
58
+ )
59
+
60
+ agent = Agent(
61
+ name="doc-agent",
62
+ instructions="Files are in /workspace. Use the sandbox tools.",
63
+ tools=[
64
+ sandbox_config(image="python:3.12-slim", interpreter="python3"),
65
+ sandbox_exec(),
66
+ sandbox_read_file(),
67
+ sandbox_write_file(),
68
+ ],
69
+ )
70
+
71
+
72
+ async def main() -> None:
73
+ await run(
74
+ agent,
75
+ "Summarize data.xlsx as a PDF",
76
+ WorkspaceContext(
77
+ input_files=[InputFile("data.xlsx", Path("data.xlsx").read_bytes())],
78
+ on_file_output=lambda f: Path("out", f.file_name).write_bytes(f.data or b""),
79
+ ),
80
+ )
81
+
82
+
83
+ asyncio.run(main())
84
+ ```
85
+
86
+ `run()` takes the workspace as an optional third positional argument; everything else is passed
87
+ through to `Runner.run()` unchanged. `run_sync()` mirrors `Runner.run_sync()`.
88
+
89
+ ## Tools
90
+
91
+ | Tool | Does |
92
+ |---|---|
93
+ | `sandbox_config(**options)` | Runs a script; configures the sandbox |
94
+ | `sandbox_exec(timeout_secs=600, max_output_bytes=32768)` | Runs a shell command |
95
+ | `sandbox_read_file()` | Reads a text file |
96
+ | `sandbox_write_file()` | Writes a text file |
97
+ | `sandbox_list_files()` | Lists a directory |
98
+
99
+ The model sees these as `sandbox_run`, `sandbox_exec`, `sandbox_read_file`, `sandbox_write_file`
100
+ and `sandbox_list_files`.
101
+
102
+ <details>
103
+ <summary><em>All <code>sandbox_config</code> options →</em></summary>
104
+
105
+ ```python
106
+ sandbox_config(
107
+ image="python:3.12-slim",
108
+ interpreter="python3",
109
+ network="public", # see Network
110
+ packages=["python-docx"], # pip install on start
111
+ timeout_secs=120, # per script, default 30
112
+ max_output_bytes=32768, # stdout/stderr sent to the model
113
+ memory=512, # MiB
114
+ cpus=1,
115
+ persist=True, # see Sessions
116
+ secrets=[SandboxSecret(env="API_KEY", value=key, host="api.example.com")],
117
+ env={"TZ": "UTC"},
118
+ create_kwargs={}, # merged into microsandbox Sandbox.create()
119
+ )
120
+ ```
121
+
122
+ Handoffs and `agent.as_tool()` agents share the run's sandbox. Tools with no `sandbox_config()`
123
+ anywhere use `python:3.12-slim`.
124
+
125
+ </details>
126
+
127
+ ## Files
128
+
129
+ `on_file_output` fires for each new or changed file after the run. `file_name` is relative to
130
+ `/workspace`; `path` is valid during the callback; `data` is `None` for files of 2 GiB or more.
131
+ Both callbacks may be sync or async.
132
+
133
+ ```python
134
+ WorkspaceContext(
135
+ input_files=[InputFile("brief.docx", data), Path("contract.pdf")],
136
+ on_file_output=lambda f: shutil.copy(f.path, f.file_name),
137
+ on_workspace_snapshot=lambda paths: print(paths),
138
+ )
139
+ ```
140
+
141
+ ## Streaming
142
+
143
+ ```python
144
+ result = run_streamed(agent, "Draft a brief", WorkspaceContext(on_file_output=save))
145
+ async for event in result.stream_events():
146
+ ...
147
+ # outputs delivered and the VM stopped once the stream ends
148
+ ```
149
+
150
+ Teardown runs when the stream finishes, including when you break out early or the run is
151
+ cancelled. If you don't want the events, `await result.wait_completed()` instead.
152
+
153
+ ## Network
154
+
155
+ ```python
156
+ sandbox_config(..., network="none") # default
157
+ sandbox_config(..., network="public") # internet, not LAN or cloud metadata
158
+ sandbox_config(..., network=Allow(["api.example.com", "*.github.com", "10.0.0.0/8"]))
159
+ ```
160
+
161
+ `secrets` hosts and PyPI (for `packages`) are allowed automatically. A host that echoes request
162
+ headers back can leak a secret's real value, so only scope secrets to APIs you trust. Pass a
163
+ `microsandbox.Network` to bypass the shorthand entirely.
164
+
165
+ ## Sessions
166
+
167
+ Set `persist=True` on `sandbox_config()` and pass a `sandbox_name` to keep one VM per conversation.
168
+
169
+ ```python
170
+ await run(agent, "Draft it", WorkspaceContext(sandbox_name="case-42", input_files=files))
171
+ await run(agent, "Now shorten it", WorkspaceContext(sandbox_name="case-42", skip_input_seed=True))
172
+
173
+ await end_session("case-42") # delete the VM and its files
174
+ await list_sessions() # [SessionInfo(name, workspace_dir, sandbox, last_used)]
175
+ ```
176
+
177
+ A follow-up on a deleted session raises `SessionNotFoundError`. Runs on the same session are
178
+ queued. Session workspaces live under `~/.mst/workspaces` unless you set `workspace_root`.
179
+
180
+ ## License
181
+
182
+ MIT
@@ -0,0 +1,157 @@
1
+ # openai-agents-msb
2
+
3
+ **Give [OpenAI Agents](https://github.com/openai/openai-agents-python) an isolated [microsandbox](https://github.com/superradcompany/microsandbox) VM to run code and edit files in.**
4
+
5
+ - **Drop-in `run()`**: Same arguments as `Runner.run()`. Swap the call, keep your agent.
6
+ - **Files in, files out**: Seed `/workspace`, get every new or changed file back after the run.
7
+ - **Lazy VMs**: The sandbox boots on the first tool call. Chat-only turns cost nothing.
8
+ - **Locked-down network**: No network by default; allow the public internet or specific hosts.
9
+ - **Secrets that stay out**: Code in the VM sees a placeholder, never the real key.
10
+ - **Persistent sessions**: Keep one VM per conversation across turns.
11
+
12
+ ## Getting Started
13
+
14
+ ```sh
15
+ pip install openai-agents-msb
16
+ ```
17
+
18
+ > **Requirements**: Python 3.10+ on macOS (Apple Silicon), Linux (KVM) or Windows (WHP).
19
+
20
+ ```python
21
+ import asyncio
22
+ from pathlib import Path
23
+
24
+ from agents import Agent
25
+ from openai_agents_msb import (
26
+ InputFile,
27
+ WorkspaceContext,
28
+ run,
29
+ sandbox_config,
30
+ sandbox_exec,
31
+ sandbox_read_file,
32
+ sandbox_write_file,
33
+ )
34
+
35
+ agent = Agent(
36
+ name="doc-agent",
37
+ instructions="Files are in /workspace. Use the sandbox tools.",
38
+ tools=[
39
+ sandbox_config(image="python:3.12-slim", interpreter="python3"),
40
+ sandbox_exec(),
41
+ sandbox_read_file(),
42
+ sandbox_write_file(),
43
+ ],
44
+ )
45
+
46
+
47
+ async def main() -> None:
48
+ await run(
49
+ agent,
50
+ "Summarize data.xlsx as a PDF",
51
+ WorkspaceContext(
52
+ input_files=[InputFile("data.xlsx", Path("data.xlsx").read_bytes())],
53
+ on_file_output=lambda f: Path("out", f.file_name).write_bytes(f.data or b""),
54
+ ),
55
+ )
56
+
57
+
58
+ asyncio.run(main())
59
+ ```
60
+
61
+ `run()` takes the workspace as an optional third positional argument; everything else is passed
62
+ through to `Runner.run()` unchanged. `run_sync()` mirrors `Runner.run_sync()`.
63
+
64
+ ## Tools
65
+
66
+ | Tool | Does |
67
+ |---|---|
68
+ | `sandbox_config(**options)` | Runs a script; configures the sandbox |
69
+ | `sandbox_exec(timeout_secs=600, max_output_bytes=32768)` | Runs a shell command |
70
+ | `sandbox_read_file()` | Reads a text file |
71
+ | `sandbox_write_file()` | Writes a text file |
72
+ | `sandbox_list_files()` | Lists a directory |
73
+
74
+ The model sees these as `sandbox_run`, `sandbox_exec`, `sandbox_read_file`, `sandbox_write_file`
75
+ and `sandbox_list_files`.
76
+
77
+ <details>
78
+ <summary><em>All <code>sandbox_config</code> options →</em></summary>
79
+
80
+ ```python
81
+ sandbox_config(
82
+ image="python:3.12-slim",
83
+ interpreter="python3",
84
+ network="public", # see Network
85
+ packages=["python-docx"], # pip install on start
86
+ timeout_secs=120, # per script, default 30
87
+ max_output_bytes=32768, # stdout/stderr sent to the model
88
+ memory=512, # MiB
89
+ cpus=1,
90
+ persist=True, # see Sessions
91
+ secrets=[SandboxSecret(env="API_KEY", value=key, host="api.example.com")],
92
+ env={"TZ": "UTC"},
93
+ create_kwargs={}, # merged into microsandbox Sandbox.create()
94
+ )
95
+ ```
96
+
97
+ Handoffs and `agent.as_tool()` agents share the run's sandbox. Tools with no `sandbox_config()`
98
+ anywhere use `python:3.12-slim`.
99
+
100
+ </details>
101
+
102
+ ## Files
103
+
104
+ `on_file_output` fires for each new or changed file after the run. `file_name` is relative to
105
+ `/workspace`; `path` is valid during the callback; `data` is `None` for files of 2 GiB or more.
106
+ Both callbacks may be sync or async.
107
+
108
+ ```python
109
+ WorkspaceContext(
110
+ input_files=[InputFile("brief.docx", data), Path("contract.pdf")],
111
+ on_file_output=lambda f: shutil.copy(f.path, f.file_name),
112
+ on_workspace_snapshot=lambda paths: print(paths),
113
+ )
114
+ ```
115
+
116
+ ## Streaming
117
+
118
+ ```python
119
+ result = run_streamed(agent, "Draft a brief", WorkspaceContext(on_file_output=save))
120
+ async for event in result.stream_events():
121
+ ...
122
+ # outputs delivered and the VM stopped once the stream ends
123
+ ```
124
+
125
+ Teardown runs when the stream finishes, including when you break out early or the run is
126
+ cancelled. If you don't want the events, `await result.wait_completed()` instead.
127
+
128
+ ## Network
129
+
130
+ ```python
131
+ sandbox_config(..., network="none") # default
132
+ sandbox_config(..., network="public") # internet, not LAN or cloud metadata
133
+ sandbox_config(..., network=Allow(["api.example.com", "*.github.com", "10.0.0.0/8"]))
134
+ ```
135
+
136
+ `secrets` hosts and PyPI (for `packages`) are allowed automatically. A host that echoes request
137
+ headers back can leak a secret's real value, so only scope secrets to APIs you trust. Pass a
138
+ `microsandbox.Network` to bypass the shorthand entirely.
139
+
140
+ ## Sessions
141
+
142
+ Set `persist=True` on `sandbox_config()` and pass a `sandbox_name` to keep one VM per conversation.
143
+
144
+ ```python
145
+ await run(agent, "Draft it", WorkspaceContext(sandbox_name="case-42", input_files=files))
146
+ await run(agent, "Now shorten it", WorkspaceContext(sandbox_name="case-42", skip_input_seed=True))
147
+
148
+ await end_session("case-42") # delete the VM and its files
149
+ await list_sessions() # [SessionInfo(name, workspace_dir, sandbox, last_used)]
150
+ ```
151
+
152
+ A follow-up on a deleted session raises `SessionNotFoundError`. Runs on the same session are
153
+ queued. Session workspaces live under `~/.mst/workspaces` unless you set `workspace_root`.
154
+
155
+ ## License
156
+
157
+ MIT
@@ -0,0 +1,81 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "openai-agents-msb"
7
+ version = "0.1.0"
8
+ description = "Drop-in run() replacement and sandbox tools that give openai-agents agents an isolated microsandbox workspace."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "iyifr" }]
13
+ keywords = [
14
+ "openai-agents",
15
+ "microsandbox",
16
+ "sandbox",
17
+ "ai-agents",
18
+ "llm",
19
+ "tools",
20
+ "code-interpreter",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 3 - Alpha",
24
+ "Intended Audience :: Developers",
25
+ "License :: OSI Approved :: MIT License",
26
+ "Programming Language :: Python :: 3",
27
+ "Programming Language :: Python :: 3.10",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Programming Language :: Python :: 3.13",
31
+ "Typing :: Typed",
32
+ ]
33
+ dependencies = [
34
+ "microsandbox>=0.7.6,<0.8",
35
+ "openai-agents>=0.23,<0.24",
36
+ ]
37
+
38
+ [project.urls]
39
+ Homepage = "https://github.com/iyifr/model-sandbox-tools#readme"
40
+ Repository = "https://github.com/iyifr/model-sandbox-tools"
41
+ Issues = "https://github.com/iyifr/model-sandbox-tools/issues"
42
+
43
+ [dependency-groups]
44
+ dev = [
45
+ "pytest>=8.3",
46
+ "pytest-asyncio>=0.24",
47
+ "pytest-timeout>=2.3",
48
+ "mypy>=1.13",
49
+ ]
50
+
51
+ [tool.hatch.build.targets.wheel]
52
+ packages = ["src/openai_agents_msb"]
53
+
54
+ [tool.hatch.build.targets.sdist]
55
+ include = ["src", "tests", "README.md", "LICENSE"]
56
+
57
+ [tool.pytest.ini_options]
58
+ testpaths = ["tests"]
59
+ asyncio_mode = "auto"
60
+ asyncio_default_fixture_loop_scope = "function"
61
+ # A wedged sandbox should fail the suite, not hang it. VM tests override this.
62
+ timeout = 60
63
+ markers = [
64
+ "vm: needs a real microsandbox VM on this host",
65
+ "network: needs outbound network access from the VM",
66
+ "llm: needs a real model; set MST_LLM_BASE_URL to enable",
67
+ ]
68
+
69
+ [tool.mypy]
70
+ python_version = "3.10"
71
+ strict = true
72
+ files = ["src", "tests"]
73
+ # microsandbox ships py.typed but its native stubs are partial in places.
74
+ warn_unused_ignores = false
75
+
76
+ [tool.ruff]
77
+ line-length = 100
78
+ target-version = "py310"
79
+
80
+ [tool.ruff.lint]
81
+ select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
@@ -0,0 +1,50 @@
1
+ """Give openai-agents agents an isolated microsandbox VM to run code and edit files in."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ._run import MstRunResultStreaming, run, run_streamed, run_sync
6
+ from ._session import SessionInfo, end_session, list_sessions
7
+ from .core import (
8
+ Allow,
9
+ FileOutput,
10
+ InputFile,
11
+ SandboxConfig,
12
+ SandboxSecret,
13
+ SandboxSetupError,
14
+ SandboxVolumeMount,
15
+ SessionNotFoundError,
16
+ WorkspaceContext,
17
+ )
18
+ from .tools import (
19
+ sandbox_config,
20
+ sandbox_exec,
21
+ sandbox_list_files,
22
+ sandbox_read_file,
23
+ sandbox_write_file,
24
+ )
25
+
26
+ __version__ = "0.1.0"
27
+
28
+ __all__ = [
29
+ "Allow",
30
+ "FileOutput",
31
+ "InputFile",
32
+ "MstRunResultStreaming",
33
+ "SandboxConfig",
34
+ "SandboxSecret",
35
+ "SandboxSetupError",
36
+ "SandboxVolumeMount",
37
+ "SessionInfo",
38
+ "SessionNotFoundError",
39
+ "WorkspaceContext",
40
+ "end_session",
41
+ "list_sessions",
42
+ "run",
43
+ "run_streamed",
44
+ "run_sync",
45
+ "sandbox_config",
46
+ "sandbox_exec",
47
+ "sandbox_list_files",
48
+ "sandbox_read_file",
49
+ "sandbox_write_file",
50
+ ]
@@ -0,0 +1,85 @@
1
+ """Finding the sandbox config an agent graph asks for."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+ from agents import Agent
9
+
10
+ from .core import SandboxConfig
11
+
12
+ __all__ = [
13
+ "DEFAULT_SANDBOX_CONFIG",
14
+ "MST_SANDBOX_CONFIG_ATTR",
15
+ "MST_TOOL_ATTR",
16
+ "DiscoveredSandbox",
17
+ "config_key",
18
+ "discover_sandbox_config",
19
+ ]
20
+
21
+ # Markers that `sandbox_config()` and the other tools set on their FunctionTool,
22
+ # so run() can recognise MST tools on an arbitrary agent.
23
+ MST_TOOL_ATTR = "__mst_tool__"
24
+ MST_SANDBOX_CONFIG_ATTR = "__mst_sandbox_config__"
25
+
26
+ DEFAULT_SANDBOX_CONFIG = SandboxConfig(image="python:3.12-slim", interpreter="python3")
27
+ """Used when an agent has sandbox tools but no `sandbox_config()` to configure them."""
28
+
29
+
30
+ def config_key(c: SandboxConfig) -> tuple[Any, ...]:
31
+ """The parts of a config that force a separate VM."""
32
+ return (
33
+ c.image,
34
+ c.interpreter,
35
+ c.cpus or 1,
36
+ c.memory or 256,
37
+ repr(c.network),
38
+ tuple(sorted(c.packages)),
39
+ )
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class DiscoveredSandbox:
44
+ config: SandboxConfig | None
45
+ """Config from `sandbox_config()` tools; None if none were found."""
46
+ has_tools: bool
47
+ """Whether any MST tool is reachable."""
48
+
49
+
50
+ def discover_sandbox_config(agent: Agent[Any]) -> DiscoveredSandbox:
51
+ """Collects sandbox tools from the agent and every agent reachable through handoffs.
52
+
53
+ Agents wrapped with `as_tool()` can't be inspected; their tools get the
54
+ sandbox lazily, from whichever config the run settles on.
55
+ """
56
+ configs: list[SandboxConfig] = []
57
+ has_tools = False
58
+ seen: set[int] = set()
59
+
60
+ def visit(a: Agent[Any]) -> None:
61
+ nonlocal has_tools
62
+ if id(a) in seen:
63
+ return
64
+ seen.add(id(a))
65
+ for tool in a.tools:
66
+ if getattr(tool, MST_TOOL_ATTR, False):
67
+ has_tools = True
68
+ config = getattr(tool, MST_SANDBOX_CONFIG_ATTR, None)
69
+ if config is not None:
70
+ configs.append(config)
71
+ for handoff in a.handoffs or []:
72
+ target = getattr(handoff, "agent", handoff)
73
+ if isinstance(target, Agent):
74
+ visit(target)
75
+
76
+ visit(agent)
77
+
78
+ if len(configs) > 1:
79
+ first = config_key(configs[0])
80
+ if any(config_key(c) != first for c in configs):
81
+ raise ValueError(
82
+ "[mst] All sandbox_config() tools reachable from an agent (including "
83
+ "handoffs) must use an identical SandboxConfig"
84
+ )
85
+ return DiscoveredSandbox(config=configs[0] if configs else None, has_tools=has_tools)