agentfense 0.2.1__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.
- agentfense-0.2.1/PKG-INFO +378 -0
- agentfense-0.2.1/README.md +344 -0
- agentfense-0.2.1/agentfense/__init__.py +191 -0
- agentfense-0.2.1/agentfense/_async/__init__.py +21 -0
- agentfense-0.2.1/agentfense/_async/client.py +679 -0
- agentfense-0.2.1/agentfense/_async/sandbox.py +667 -0
- agentfense-0.2.1/agentfense/_gen/__init__.py +0 -0
- agentfense-0.2.1/agentfense/_gen/codebase_pb2.py +78 -0
- agentfense-0.2.1/agentfense/_gen/codebase_pb2.pyi +141 -0
- agentfense-0.2.1/agentfense/_gen/codebase_pb2_grpc.py +366 -0
- agentfense-0.2.1/agentfense/_gen/common_pb2.py +47 -0
- agentfense-0.2.1/agentfense/_gen/common_pb2.pyi +68 -0
- agentfense-0.2.1/agentfense/_gen/common_pb2_grpc.py +24 -0
- agentfense-0.2.1/agentfense/_gen/sandbox_pb2.py +123 -0
- agentfense-0.2.1/agentfense/_gen/sandbox_pb2.pyi +255 -0
- agentfense-0.2.1/agentfense/_gen/sandbox_pb2_grpc.py +678 -0
- agentfense-0.2.1/agentfense/_shared.py +238 -0
- agentfense-0.2.1/agentfense/client.py +751 -0
- agentfense-0.2.1/agentfense/exceptions.py +333 -0
- agentfense-0.2.1/agentfense/presets.py +192 -0
- agentfense-0.2.1/agentfense/sandbox.py +672 -0
- agentfense-0.2.1/agentfense/types.py +256 -0
- agentfense-0.2.1/agentfense/utils.py +286 -0
- agentfense-0.2.1/agentfense.egg-info/PKG-INFO +378 -0
- agentfense-0.2.1/agentfense.egg-info/SOURCES.txt +33 -0
- agentfense-0.2.1/agentfense.egg-info/dependency_links.txt +1 -0
- agentfense-0.2.1/agentfense.egg-info/requires.txt +11 -0
- agentfense-0.2.1/agentfense.egg-info/top_level.txt +1 -0
- agentfense-0.2.1/pyproject.toml +75 -0
- agentfense-0.2.1/setup.cfg +4 -0
- agentfense-0.2.1/tests/test_async.py +283 -0
- agentfense-0.2.1/tests/test_presets.py +197 -0
- agentfense-0.2.1/tests/test_sandbox.py +531 -0
- agentfense-0.2.1/tests/test_session.py +158 -0
- agentfense-0.2.1/tests/test_types.py +202 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentfense
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Python SDK for AgentFense - A sandbox infrastructure service for AI Agents with fine-grained file permission control
|
|
5
|
+
Author-email: Ajax Zhan <ajaxzhan@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/AjaxZhan/AgentFense
|
|
8
|
+
Project-URL: Documentation, https://github.com/AjaxZhan/AgentFense#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/AjaxZhan/AgentFense
|
|
10
|
+
Project-URL: Issues, https://github.com/AjaxZhan/AgentFense/issues
|
|
11
|
+
Keywords: agentfense,sandbox,ai,agent,grpc,isolation
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: grpcio>=1.50.0
|
|
25
|
+
Requires-Dist: protobuf>=4.21.0
|
|
26
|
+
Requires-Dist: googleapis-common-protos>=1.60.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.20.0; extra == "dev"
|
|
30
|
+
Requires-Dist: grpcio-tools>=1.50.0; extra == "dev"
|
|
31
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: ruff>=0.0.260; extra == "dev"
|
|
34
|
+
|
|
35
|
+
# AgentFense SDK for Python
|
|
36
|
+
|
|
37
|
+
Python client library for the AgentFense service - a sandbox infrastructure for AI Agents with fine-grained file permission control.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install agentfense
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or install from source:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
cd sdk/python
|
|
49
|
+
pip install -e .
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick Start (High-Level API)
|
|
53
|
+
|
|
54
|
+
The easiest way to use the SDK is with the high-level `Sandbox` class:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from agentfense import Sandbox
|
|
58
|
+
|
|
59
|
+
# One-liner to create a sandbox from a local directory
|
|
60
|
+
with Sandbox.from_local("./my-project") as sandbox:
|
|
61
|
+
result = sandbox.run("python main.py")
|
|
62
|
+
print(result.stdout)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This automatically:
|
|
66
|
+
1. Creates a codebase
|
|
67
|
+
2. Uploads all files from the directory
|
|
68
|
+
3. Creates a sandbox with the "agent-safe" preset
|
|
69
|
+
4. Starts the sandbox
|
|
70
|
+
5. Cleans up everything when done
|
|
71
|
+
|
|
72
|
+
### Using Docker Runtime with Resource Limits
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from agentfense import Sandbox, RuntimeType, ResourceLimits
|
|
76
|
+
|
|
77
|
+
with Sandbox.from_local(
|
|
78
|
+
"./my-project",
|
|
79
|
+
preset="agent-safe",
|
|
80
|
+
runtime=RuntimeType.DOCKER,
|
|
81
|
+
image="python:3.11-slim",
|
|
82
|
+
resources=ResourceLimits(
|
|
83
|
+
memory_bytes=512 * 1024 * 1024, # 512 MB
|
|
84
|
+
pids_limit=100,
|
|
85
|
+
),
|
|
86
|
+
) as sandbox:
|
|
87
|
+
# Use a session for stateful commands
|
|
88
|
+
with sandbox.session() as session:
|
|
89
|
+
session.exec("cd /workspace")
|
|
90
|
+
session.exec("pip install -r requirements.txt")
|
|
91
|
+
result = session.exec("pytest")
|
|
92
|
+
print(result.stdout)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Permission Presets
|
|
96
|
+
|
|
97
|
+
The SDK includes several built-in permission presets:
|
|
98
|
+
|
|
99
|
+
| Preset | Description |
|
|
100
|
+
|--------|-------------|
|
|
101
|
+
| `agent-safe` | Read all files, write to /output and /tmp, hide secrets (.env, *.key, etc.) |
|
|
102
|
+
| `read-only` | Read all files, no write access |
|
|
103
|
+
| `full-access` | Full read/write access to all files |
|
|
104
|
+
| `development` | Full access except secrets |
|
|
105
|
+
| `view-only` | Can see file names but not read content |
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from agentfense import list_presets, get_preset, extend_preset
|
|
109
|
+
|
|
110
|
+
# List all available presets
|
|
111
|
+
print(list_presets()) # ['agent-safe', 'development', 'full-access', 'read-only', 'view-only']
|
|
112
|
+
|
|
113
|
+
# Extend a preset with custom rules
|
|
114
|
+
rules = extend_preset(
|
|
115
|
+
"agent-safe",
|
|
116
|
+
additions=[{"pattern": "/custom/**", "permission": "write"}],
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Error Handling
|
|
121
|
+
|
|
122
|
+
The SDK provides semantic exception classes:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from agentfense import Sandbox, SandboxError, CommandTimeoutError, CommandExecutionError
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
with Sandbox.from_local("./my-project") as sandbox:
|
|
129
|
+
result = sandbox.run("python main.py", timeout=30, raise_on_error=True)
|
|
130
|
+
except CommandTimeoutError as e:
|
|
131
|
+
print(f"Command timed out: {e}")
|
|
132
|
+
except CommandExecutionError as e:
|
|
133
|
+
print(f"Command failed with exit code {e.exit_code}: {e.stderr}")
|
|
134
|
+
except SandboxError as e:
|
|
135
|
+
print(f"Sandbox error: {e}")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Low-Level API
|
|
139
|
+
|
|
140
|
+
For fine-grained control, use the `SandboxClient` directly:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from agentfense import SandboxClient, RuntimeType, ResourceLimits
|
|
144
|
+
|
|
145
|
+
# Connect to the sandbox server
|
|
146
|
+
client = SandboxClient(endpoint="localhost:9000")
|
|
147
|
+
|
|
148
|
+
# Create a codebase (file storage)
|
|
149
|
+
codebase = client.create_codebase(name="my-project", owner_id="user_123")
|
|
150
|
+
|
|
151
|
+
# Upload a file
|
|
152
|
+
client.upload_file(codebase.id, "hello.py", b'print("Hello, World!")')
|
|
153
|
+
|
|
154
|
+
# Create a sandbox with permissions and Docker runtime
|
|
155
|
+
sandbox = client.create_sandbox(
|
|
156
|
+
codebase_id=codebase.id,
|
|
157
|
+
permissions=[
|
|
158
|
+
{"pattern": "**/*.py", "permission": "read"},
|
|
159
|
+
{"pattern": "/docs/**", "permission": "write"},
|
|
160
|
+
],
|
|
161
|
+
runtime=RuntimeType.DOCKER,
|
|
162
|
+
image="python:3.11-slim",
|
|
163
|
+
resources=ResourceLimits(memory_bytes=256 * 1024 * 1024),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Start the sandbox
|
|
167
|
+
client.start_sandbox(sandbox.id)
|
|
168
|
+
|
|
169
|
+
# Execute a command
|
|
170
|
+
result = client.exec(sandbox.id, command="python hello.py")
|
|
171
|
+
print(result.stdout) # Output: Hello, World!
|
|
172
|
+
|
|
173
|
+
# Clean up
|
|
174
|
+
client.destroy_sandbox(sandbox.id)
|
|
175
|
+
client.delete_codebase(codebase.id)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Features
|
|
179
|
+
|
|
180
|
+
### Permission Levels
|
|
181
|
+
|
|
182
|
+
The SDK supports four permission levels:
|
|
183
|
+
|
|
184
|
+
| Level | Description |
|
|
185
|
+
|-------|-------------|
|
|
186
|
+
| `none` | Completely invisible, not shown in `ls` |
|
|
187
|
+
| `view` | Visible in `ls`, but cannot read content |
|
|
188
|
+
| `read` | Can read file content |
|
|
189
|
+
| `write` | Can modify file |
|
|
190
|
+
|
|
191
|
+
### Permission Patterns
|
|
192
|
+
|
|
193
|
+
You can use glob patterns, directory paths, or file paths:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from agentfense import PermissionRule, Permission, PatternType
|
|
197
|
+
|
|
198
|
+
permissions = [
|
|
199
|
+
# Glob pattern - matches all .py files
|
|
200
|
+
PermissionRule(pattern="**/*.py", permission=Permission.READ),
|
|
201
|
+
|
|
202
|
+
# Directory - applies to entire directory
|
|
203
|
+
PermissionRule(pattern="/docs/", permission=Permission.WRITE, type=PatternType.DIRECTORY),
|
|
204
|
+
|
|
205
|
+
# Specific file
|
|
206
|
+
PermissionRule(pattern="/config.yaml", permission=Permission.VIEW, type=PatternType.FILE),
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
sandbox = client.create_sandbox(codebase_id=codebase.id, permissions=permissions)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Stateful Sessions
|
|
213
|
+
|
|
214
|
+
Sessions maintain a persistent shell process that preserves working directory and environment variables:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
with client.session(sandbox.id) as session:
|
|
218
|
+
session.exec("cd /workspace/src") # Change directory
|
|
219
|
+
session.exec("export DEBUG=1") # Set environment variable
|
|
220
|
+
session.exec("source venv/bin/activate") # Activate virtualenv
|
|
221
|
+
result = session.exec("python main.py") # All above state is preserved
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Command Execution
|
|
225
|
+
|
|
226
|
+
Execute commands with optional stdin, environment variables, and timeout:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
from datetime import timedelta
|
|
230
|
+
|
|
231
|
+
result = client.exec(
|
|
232
|
+
sandbox_id=sandbox.id,
|
|
233
|
+
command="python script.py",
|
|
234
|
+
stdin="input data",
|
|
235
|
+
env={"DEBUG": "1"},
|
|
236
|
+
workdir="/workspace/src",
|
|
237
|
+
timeout=timedelta(seconds=30),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
print(f"Exit code: {result.exit_code}")
|
|
241
|
+
print(f"stdout: {result.stdout}")
|
|
242
|
+
print(f"stderr: {result.stderr}")
|
|
243
|
+
print(f"Duration: {result.duration}")
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Streaming Output
|
|
247
|
+
|
|
248
|
+
For long-running commands, use streaming:
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
for chunk in client.exec_stream(sandbox.id, command="long-running-task"):
|
|
252
|
+
print(chunk.decode(), end="")
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### File Operations
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
# List files
|
|
259
|
+
files = client.list_files(codebase.id, path="/src", recursive=True)
|
|
260
|
+
for f in files:
|
|
261
|
+
print(f"{f.path} - {f.size} bytes")
|
|
262
|
+
|
|
263
|
+
# Download a file
|
|
264
|
+
content = client.download_file(codebase.id, "config.yaml")
|
|
265
|
+
print(content.decode())
|
|
266
|
+
|
|
267
|
+
# Upload a file
|
|
268
|
+
result = client.upload_file(codebase.id, "new_file.txt", b"file content")
|
|
269
|
+
print(f"Uploaded {result.size} bytes, checksum: {result.checksum}")
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Context Manager
|
|
273
|
+
|
|
274
|
+
Both the high-level `Sandbox` and low-level `SandboxClient` support context managers:
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
# High-level (recommended)
|
|
278
|
+
with Sandbox.from_local("./project") as sandbox:
|
|
279
|
+
sandbox.run("make test")
|
|
280
|
+
# Automatically cleans up sandbox and codebase
|
|
281
|
+
|
|
282
|
+
# Low-level
|
|
283
|
+
with SandboxClient(endpoint="localhost:9000") as client:
|
|
284
|
+
sandbox = client.create_sandbox(codebase_id="cb_123")
|
|
285
|
+
# ... use the sandbox ...
|
|
286
|
+
# Connection is automatically closed
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## API Reference
|
|
290
|
+
|
|
291
|
+
### High-Level API
|
|
292
|
+
|
|
293
|
+
#### Sandbox Class
|
|
294
|
+
|
|
295
|
+
- `Sandbox.from_local(path, preset, permissions, runtime, image, resources, ...)` - Create sandbox from local directory
|
|
296
|
+
- `Sandbox.from_codebase(codebase_id, preset, ...)` - Create sandbox from existing codebase
|
|
297
|
+
- `Sandbox.connect(sandbox_id)` - Connect to existing sandbox
|
|
298
|
+
- `sandbox.run(command, timeout, env, workdir, raise_on_error)` - Execute command (simplified)
|
|
299
|
+
- `sandbox.exec(command, stdin, env, workdir, timeout)` - Execute command (full API)
|
|
300
|
+
- `sandbox.session(shell, env)` - Create stateful session
|
|
301
|
+
- `sandbox.read_file(path)` - Read file as string
|
|
302
|
+
- `sandbox.write_file(path, content)` - Write file
|
|
303
|
+
- `sandbox.list_files(path, recursive)` - List files
|
|
304
|
+
- `sandbox.start()` / `sandbox.stop()` - Lifecycle control
|
|
305
|
+
- `sandbox.destroy(delete_codebase)` - Clean up
|
|
306
|
+
|
|
307
|
+
#### Presets
|
|
308
|
+
|
|
309
|
+
- `get_preset(name)` - Get preset as PermissionRule list
|
|
310
|
+
- `get_preset_dicts(name)` - Get preset as dict list
|
|
311
|
+
- `extend_preset(base, additions, overrides)` - Extend preset
|
|
312
|
+
- `list_presets()` - List available presets
|
|
313
|
+
- `register_preset(name, rules)` - Register custom preset
|
|
314
|
+
|
|
315
|
+
### Low-Level API
|
|
316
|
+
|
|
317
|
+
#### SandboxClient
|
|
318
|
+
|
|
319
|
+
- `create_sandbox(codebase_id, permissions, runtime, image, resources, labels, expires_in)` - Create sandbox
|
|
320
|
+
- `get_sandbox(sandbox_id)` - Get sandbox info
|
|
321
|
+
- `list_sandboxes(codebase_id)` - List sandboxes
|
|
322
|
+
- `start_sandbox(sandbox_id)` - Start sandbox
|
|
323
|
+
- `stop_sandbox(sandbox_id)` - Stop sandbox
|
|
324
|
+
- `destroy_sandbox(sandbox_id)` - Destroy sandbox
|
|
325
|
+
- `exec(sandbox_id, command, stdin, env, workdir, timeout)` - Execute command
|
|
326
|
+
- `exec_stream(sandbox_id, command, ...)` - Execute with streaming
|
|
327
|
+
- `create_session(sandbox_id, shell, env)` - Create session
|
|
328
|
+
- `session_exec(session_id, command, timeout)` - Execute in session
|
|
329
|
+
- `create_codebase(name, owner_id)` - Create codebase
|
|
330
|
+
- `get_codebase(codebase_id)` - Get codebase info
|
|
331
|
+
- `list_codebases(owner_id)` - List codebases
|
|
332
|
+
- `delete_codebase(codebase_id)` - Delete codebase
|
|
333
|
+
- `upload_file(codebase_id, file_path, content)` - Upload file
|
|
334
|
+
- `download_file(codebase_id, file_path)` - Download file
|
|
335
|
+
- `list_files(codebase_id, path, recursive)` - List files
|
|
336
|
+
|
|
337
|
+
### Types
|
|
338
|
+
|
|
339
|
+
- `RuntimeType` - BWRAP, DOCKER
|
|
340
|
+
- `ResourceLimits` - memory_bytes, cpu_quota, cpu_shares, pids_limit
|
|
341
|
+
- `Permission` - NONE, VIEW, READ, WRITE
|
|
342
|
+
- `PatternType` - GLOB, DIRECTORY, FILE
|
|
343
|
+
- `SandboxStatus` - PENDING, RUNNING, STOPPED, ERROR
|
|
344
|
+
- `SessionStatus` - UNKNOWN, ACTIVE, CLOSED
|
|
345
|
+
|
|
346
|
+
### Exceptions
|
|
347
|
+
|
|
348
|
+
- `SandboxError` - Base exception
|
|
349
|
+
- `SandboxNotFoundError` - Sandbox not found
|
|
350
|
+
- `SandboxNotRunningError` - Sandbox not running
|
|
351
|
+
- `CodebaseError` - Codebase operation error
|
|
352
|
+
- `CodebaseNotFoundError` - Codebase not found
|
|
353
|
+
- `CommandTimeoutError` - Command timed out
|
|
354
|
+
- `CommandExecutionError` - Command failed (non-zero exit)
|
|
355
|
+
- `PermissionDeniedError` - Permission denied
|
|
356
|
+
- `SessionError` - Session operation error
|
|
357
|
+
- `ConnectionError` - Connection to service failed
|
|
358
|
+
|
|
359
|
+
## Development
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
# Install dev dependencies
|
|
363
|
+
pip install -e ".[dev]"
|
|
364
|
+
|
|
365
|
+
# Run tests
|
|
366
|
+
pytest
|
|
367
|
+
|
|
368
|
+
# Format code
|
|
369
|
+
black agentfense tests
|
|
370
|
+
ruff check --fix agentfense tests
|
|
371
|
+
|
|
372
|
+
# Type check
|
|
373
|
+
mypy agentfense
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## License
|
|
377
|
+
|
|
378
|
+
MIT
|