aury-agent 0.0.4__py3-none-any.whl

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 (149) hide show
  1. aury/__init__.py +2 -0
  2. aury/agents/__init__.py +55 -0
  3. aury/agents/a2a/__init__.py +168 -0
  4. aury/agents/backends/__init__.py +196 -0
  5. aury/agents/backends/artifact/__init__.py +9 -0
  6. aury/agents/backends/artifact/memory.py +130 -0
  7. aury/agents/backends/artifact/types.py +133 -0
  8. aury/agents/backends/code/__init__.py +65 -0
  9. aury/agents/backends/file/__init__.py +11 -0
  10. aury/agents/backends/file/local.py +66 -0
  11. aury/agents/backends/file/types.py +40 -0
  12. aury/agents/backends/invocation/__init__.py +8 -0
  13. aury/agents/backends/invocation/memory.py +81 -0
  14. aury/agents/backends/invocation/types.py +110 -0
  15. aury/agents/backends/memory/__init__.py +8 -0
  16. aury/agents/backends/memory/memory.py +179 -0
  17. aury/agents/backends/memory/types.py +136 -0
  18. aury/agents/backends/message/__init__.py +9 -0
  19. aury/agents/backends/message/memory.py +122 -0
  20. aury/agents/backends/message/types.py +124 -0
  21. aury/agents/backends/sandbox.py +275 -0
  22. aury/agents/backends/session/__init__.py +8 -0
  23. aury/agents/backends/session/memory.py +93 -0
  24. aury/agents/backends/session/types.py +124 -0
  25. aury/agents/backends/shell/__init__.py +11 -0
  26. aury/agents/backends/shell/local.py +110 -0
  27. aury/agents/backends/shell/types.py +55 -0
  28. aury/agents/backends/shell.py +209 -0
  29. aury/agents/backends/snapshot/__init__.py +19 -0
  30. aury/agents/backends/snapshot/git.py +95 -0
  31. aury/agents/backends/snapshot/hybrid.py +125 -0
  32. aury/agents/backends/snapshot/memory.py +86 -0
  33. aury/agents/backends/snapshot/types.py +59 -0
  34. aury/agents/backends/state/__init__.py +29 -0
  35. aury/agents/backends/state/composite.py +49 -0
  36. aury/agents/backends/state/file.py +57 -0
  37. aury/agents/backends/state/memory.py +52 -0
  38. aury/agents/backends/state/sqlite.py +262 -0
  39. aury/agents/backends/state/types.py +178 -0
  40. aury/agents/backends/subagent/__init__.py +165 -0
  41. aury/agents/cli/__init__.py +41 -0
  42. aury/agents/cli/chat.py +239 -0
  43. aury/agents/cli/config.py +236 -0
  44. aury/agents/cli/extensions.py +460 -0
  45. aury/agents/cli/main.py +189 -0
  46. aury/agents/cli/session.py +337 -0
  47. aury/agents/cli/workflow.py +276 -0
  48. aury/agents/context_providers/__init__.py +66 -0
  49. aury/agents/context_providers/artifact.py +299 -0
  50. aury/agents/context_providers/base.py +177 -0
  51. aury/agents/context_providers/memory.py +70 -0
  52. aury/agents/context_providers/message.py +130 -0
  53. aury/agents/context_providers/skill.py +50 -0
  54. aury/agents/context_providers/subagent.py +46 -0
  55. aury/agents/context_providers/tool.py +68 -0
  56. aury/agents/core/__init__.py +83 -0
  57. aury/agents/core/base.py +573 -0
  58. aury/agents/core/context.py +797 -0
  59. aury/agents/core/context_builder.py +303 -0
  60. aury/agents/core/event_bus/__init__.py +15 -0
  61. aury/agents/core/event_bus/bus.py +203 -0
  62. aury/agents/core/factory.py +169 -0
  63. aury/agents/core/isolator.py +97 -0
  64. aury/agents/core/logging.py +95 -0
  65. aury/agents/core/parallel.py +194 -0
  66. aury/agents/core/runner.py +139 -0
  67. aury/agents/core/services/__init__.py +5 -0
  68. aury/agents/core/services/file_session.py +144 -0
  69. aury/agents/core/services/message.py +53 -0
  70. aury/agents/core/services/session.py +53 -0
  71. aury/agents/core/signals.py +109 -0
  72. aury/agents/core/state.py +363 -0
  73. aury/agents/core/types/__init__.py +107 -0
  74. aury/agents/core/types/action.py +176 -0
  75. aury/agents/core/types/artifact.py +135 -0
  76. aury/agents/core/types/block.py +736 -0
  77. aury/agents/core/types/message.py +350 -0
  78. aury/agents/core/types/recall.py +144 -0
  79. aury/agents/core/types/session.py +257 -0
  80. aury/agents/core/types/subagent.py +154 -0
  81. aury/agents/core/types/tool.py +205 -0
  82. aury/agents/eval/__init__.py +331 -0
  83. aury/agents/hitl/__init__.py +57 -0
  84. aury/agents/hitl/ask_user.py +242 -0
  85. aury/agents/hitl/compaction.py +230 -0
  86. aury/agents/hitl/exceptions.py +87 -0
  87. aury/agents/hitl/permission.py +617 -0
  88. aury/agents/hitl/revert.py +216 -0
  89. aury/agents/llm/__init__.py +31 -0
  90. aury/agents/llm/adapter.py +367 -0
  91. aury/agents/llm/openai.py +294 -0
  92. aury/agents/llm/provider.py +476 -0
  93. aury/agents/mcp/__init__.py +153 -0
  94. aury/agents/memory/__init__.py +46 -0
  95. aury/agents/memory/compaction.py +394 -0
  96. aury/agents/memory/manager.py +465 -0
  97. aury/agents/memory/processor.py +177 -0
  98. aury/agents/memory/store.py +187 -0
  99. aury/agents/memory/types.py +137 -0
  100. aury/agents/messages/__init__.py +40 -0
  101. aury/agents/messages/config.py +47 -0
  102. aury/agents/messages/raw_store.py +224 -0
  103. aury/agents/messages/store.py +118 -0
  104. aury/agents/messages/types.py +88 -0
  105. aury/agents/middleware/__init__.py +31 -0
  106. aury/agents/middleware/base.py +341 -0
  107. aury/agents/middleware/chain.py +342 -0
  108. aury/agents/middleware/message.py +129 -0
  109. aury/agents/middleware/message_container.py +126 -0
  110. aury/agents/middleware/raw_message.py +153 -0
  111. aury/agents/middleware/truncation.py +139 -0
  112. aury/agents/middleware/types.py +81 -0
  113. aury/agents/plugin.py +162 -0
  114. aury/agents/react/__init__.py +4 -0
  115. aury/agents/react/agent.py +1923 -0
  116. aury/agents/sandbox/__init__.py +23 -0
  117. aury/agents/sandbox/local.py +239 -0
  118. aury/agents/sandbox/remote.py +200 -0
  119. aury/agents/sandbox/types.py +115 -0
  120. aury/agents/skill/__init__.py +16 -0
  121. aury/agents/skill/loader.py +180 -0
  122. aury/agents/skill/types.py +83 -0
  123. aury/agents/tool/__init__.py +39 -0
  124. aury/agents/tool/builtin/__init__.py +23 -0
  125. aury/agents/tool/builtin/ask_user.py +155 -0
  126. aury/agents/tool/builtin/bash.py +107 -0
  127. aury/agents/tool/builtin/delegate.py +726 -0
  128. aury/agents/tool/builtin/edit.py +121 -0
  129. aury/agents/tool/builtin/plan.py +277 -0
  130. aury/agents/tool/builtin/read.py +91 -0
  131. aury/agents/tool/builtin/thinking.py +111 -0
  132. aury/agents/tool/builtin/yield_result.py +130 -0
  133. aury/agents/tool/decorator.py +252 -0
  134. aury/agents/tool/set.py +204 -0
  135. aury/agents/usage/__init__.py +12 -0
  136. aury/agents/usage/tracker.py +236 -0
  137. aury/agents/workflow/__init__.py +85 -0
  138. aury/agents/workflow/adapter.py +268 -0
  139. aury/agents/workflow/dag.py +116 -0
  140. aury/agents/workflow/dsl.py +575 -0
  141. aury/agents/workflow/executor.py +659 -0
  142. aury/agents/workflow/expression.py +136 -0
  143. aury/agents/workflow/parser.py +182 -0
  144. aury/agents/workflow/state.py +145 -0
  145. aury/agents/workflow/types.py +86 -0
  146. aury_agent-0.0.4.dist-info/METADATA +90 -0
  147. aury_agent-0.0.4.dist-info/RECORD +149 -0
  148. aury_agent-0.0.4.dist-info/WHEEL +4 -0
  149. aury_agent-0.0.4.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,23 @@
1
+ """Sandbox module for isolated code execution environments.
2
+
3
+ Provides abstracted sandbox interfaces supporting:
4
+ - Local Docker-based sandboxes for CLI/development
5
+ - Remote API-based sandboxes for SaaS deployment
6
+ """
7
+ from .types import ExecutionResult, SandboxConfig, Sandbox, SandboxProvider
8
+ from .local import LocalSandbox, LocalSandboxProvider
9
+ from .remote import RemoteSandbox, RemoteSandboxProvider
10
+
11
+ __all__ = [
12
+ # Types
13
+ "ExecutionResult",
14
+ "SandboxConfig",
15
+ "Sandbox",
16
+ "SandboxProvider",
17
+ # Local
18
+ "LocalSandbox",
19
+ "LocalSandboxProvider",
20
+ # Remote
21
+ "RemoteSandbox",
22
+ "RemoteSandboxProvider",
23
+ ]
@@ -0,0 +1,239 @@
1
+ """Local Docker-based sandbox implementation."""
2
+ from __future__ import annotations
3
+
4
+ import asyncio
5
+ import os
6
+ import tarfile
7
+ import time
8
+ from io import BytesIO
9
+ from pathlib import Path
10
+ from typing import Any, Literal
11
+
12
+ from .types import ExecutionResult, SandboxConfig
13
+
14
+
15
+ class LocalSandbox:
16
+ """Local Docker-based sandbox implementation.
17
+
18
+ Uses Docker containers for isolation. Requires Docker to be installed
19
+ and accessible via the Docker socket.
20
+ """
21
+
22
+ def __init__(self, container: Any, config: SandboxConfig) -> None:
23
+ self._container = container
24
+ self._config = config
25
+ self._status: Literal["creating", "running", "stopped", "failed"] = "running"
26
+
27
+ @property
28
+ def id(self) -> str:
29
+ return self._container.id
30
+
31
+ @property
32
+ def status(self) -> Literal["creating", "running", "stopped", "failed"]:
33
+ return self._status
34
+
35
+ async def execute(
36
+ self,
37
+ command: str | list[str],
38
+ *,
39
+ timeout: int | None = None,
40
+ stdin: str | None = None,
41
+ env: dict[str, str] | None = None,
42
+ workdir: str | None = None,
43
+ ) -> ExecutionResult:
44
+ """Execute command in Docker container."""
45
+ if isinstance(command, list):
46
+ cmd = command
47
+ else:
48
+ cmd = ["/bin/sh", "-c", command]
49
+
50
+ work_dir = workdir or self._config.workdir
51
+ effective_timeout = timeout or self._config.timeout
52
+ start_time = time.time()
53
+
54
+ def _exec() -> tuple[int, bytes]:
55
+ exec_result = self._container.exec_run(
56
+ cmd=cmd,
57
+ environment=env,
58
+ workdir=work_dir,
59
+ stdin=stdin is not None,
60
+ demux=True,
61
+ )
62
+ return exec_result.exit_code, exec_result.output
63
+
64
+ try:
65
+ loop = asyncio.get_event_loop()
66
+ exit_code, output = await asyncio.wait_for(
67
+ loop.run_in_executor(None, _exec),
68
+ timeout=effective_timeout,
69
+ )
70
+
71
+ stdout = ""
72
+ stderr = ""
73
+ if output:
74
+ if isinstance(output, tuple):
75
+ stdout = (output[0] or b"").decode("utf-8", errors="replace")
76
+ stderr = (output[1] or b"").decode("utf-8", errors="replace")
77
+ else:
78
+ stdout = output.decode("utf-8", errors="replace")
79
+
80
+ return ExecutionResult(
81
+ exit_code=exit_code or 0,
82
+ stdout=stdout,
83
+ stderr=stderr,
84
+ duration_ms=int((time.time() - start_time) * 1000),
85
+ )
86
+
87
+ except asyncio.TimeoutError:
88
+ return ExecutionResult(
89
+ exit_code=-1,
90
+ stdout="",
91
+ stderr=f"Command timed out after {effective_timeout}s",
92
+ duration_ms=effective_timeout * 1000,
93
+ timed_out=True,
94
+ )
95
+
96
+ async def write_file(self, path: str, content: str | bytes) -> None:
97
+ """Write file to container using tar archive."""
98
+ if isinstance(content, str):
99
+ content = content.encode("utf-8")
100
+
101
+ def _write() -> None:
102
+ tar_stream = BytesIO()
103
+ with tarfile.open(fileobj=tar_stream, mode="w") as tar:
104
+ file_info = tarfile.TarInfo(name=os.path.basename(path))
105
+ file_info.size = len(content)
106
+ tar.addfile(file_info, BytesIO(content))
107
+ tar_stream.seek(0)
108
+ self._container.put_archive(os.path.dirname(path) or "/", tar_stream)
109
+
110
+ loop = asyncio.get_event_loop()
111
+ await loop.run_in_executor(None, _write)
112
+
113
+ async def read_file(self, path: str) -> bytes:
114
+ """Read file from container using tar archive."""
115
+ def _read() -> bytes:
116
+ bits, _ = self._container.get_archive(path)
117
+ tar_stream = BytesIO()
118
+ for chunk in bits:
119
+ tar_stream.write(chunk)
120
+ tar_stream.seek(0)
121
+
122
+ with tarfile.open(fileobj=tar_stream, mode="r") as tar:
123
+ member = tar.getmembers()[0]
124
+ f = tar.extractfile(member)
125
+ if f:
126
+ return f.read()
127
+ return b""
128
+
129
+ loop = asyncio.get_event_loop()
130
+ return await loop.run_in_executor(None, _read)
131
+
132
+ async def upload(self, local_path: Path, remote_path: str) -> None:
133
+ """Upload file/directory to container."""
134
+ def _upload() -> None:
135
+ tar_stream = BytesIO()
136
+ with tarfile.open(fileobj=tar_stream, mode="w") as tar:
137
+ tar.add(str(local_path), arcname=os.path.basename(remote_path))
138
+ tar_stream.seek(0)
139
+ dest_dir = os.path.dirname(remote_path) or "/"
140
+ self._container.put_archive(dest_dir, tar_stream)
141
+
142
+ loop = asyncio.get_event_loop()
143
+ await loop.run_in_executor(None, _upload)
144
+
145
+ async def download(self, remote_path: str, local_path: Path) -> None:
146
+ """Download file/directory from container."""
147
+ def _download() -> None:
148
+ bits, _ = self._container.get_archive(remote_path)
149
+ tar_stream = BytesIO()
150
+ for chunk in bits:
151
+ tar_stream.write(chunk)
152
+ tar_stream.seek(0)
153
+ with tarfile.open(fileobj=tar_stream, mode="r") as tar:
154
+ tar.extractall(str(local_path.parent))
155
+
156
+ loop = asyncio.get_event_loop()
157
+ await loop.run_in_executor(None, _download)
158
+
159
+ async def stop(self) -> None:
160
+ """Stop container."""
161
+ def _stop() -> None:
162
+ self._container.stop()
163
+
164
+ loop = asyncio.get_event_loop()
165
+ await loop.run_in_executor(None, _stop)
166
+ self._status = "stopped"
167
+
168
+ async def destroy(self) -> None:
169
+ """Remove container."""
170
+ def _remove() -> None:
171
+ self._container.remove(force=True)
172
+
173
+ loop = asyncio.get_event_loop()
174
+ await loop.run_in_executor(None, _remove)
175
+ self._status = "stopped"
176
+
177
+
178
+ class LocalSandboxProvider:
179
+ """Local Docker-based sandbox provider.
180
+
181
+ Creates sandboxes using local Docker installation.
182
+ Suitable for CLI usage and development.
183
+
184
+ Requires: docker package (`pip install docker`)
185
+ """
186
+
187
+ def __init__(
188
+ self,
189
+ docker_socket: str = "unix:///var/run/docker.sock",
190
+ default_config: SandboxConfig | None = None,
191
+ ) -> None:
192
+ self.docker_socket = docker_socket
193
+ self.default_config = default_config or SandboxConfig()
194
+ self._client: Any = None
195
+
196
+ def _get_client(self) -> Any:
197
+ """Lazy-load Docker client."""
198
+ if self._client is None:
199
+ try:
200
+ import docker
201
+ self._client = docker.DockerClient(base_url=self.docker_socket)
202
+ except ImportError:
203
+ raise ImportError(
204
+ "docker package required for LocalSandboxProvider. "
205
+ "Install with: pip install docker"
206
+ )
207
+ return self._client
208
+
209
+ async def create(self, config: SandboxConfig | None = None) -> LocalSandbox:
210
+ """Create a new Docker-based sandbox."""
211
+ cfg = config or self.default_config
212
+ client = self._get_client()
213
+
214
+ def _create() -> Any:
215
+ volumes = {}
216
+ for host, container in cfg.volumes.items():
217
+ volumes[host] = {"bind": container, "mode": "rw"}
218
+
219
+ container = client.containers.run(
220
+ image=cfg.image,
221
+ detach=True,
222
+ mem_limit=cfg.memory_limit,
223
+ cpu_period=100000,
224
+ cpu_quota=int(cfg.cpu_limit * 100000),
225
+ network_disabled=not cfg.network,
226
+ volumes=volumes or None,
227
+ environment=cfg.env or None,
228
+ working_dir=cfg.workdir,
229
+ command="sleep infinity",
230
+ )
231
+ return container
232
+
233
+ loop = asyncio.get_event_loop()
234
+ container = await loop.run_in_executor(None, _create)
235
+
236
+ return LocalSandbox(container, cfg)
237
+
238
+
239
+ __all__ = ["LocalSandbox", "LocalSandboxProvider"]
@@ -0,0 +1,200 @@
1
+ """Remote API-based sandbox implementation."""
2
+ from __future__ import annotations
3
+
4
+ import base64
5
+ from pathlib import Path
6
+ from typing import Any, Literal
7
+
8
+ from .types import ExecutionResult, SandboxConfig
9
+
10
+
11
+ class RemoteSandbox:
12
+ """Remote sandbox accessed via HTTP API.
13
+
14
+ Connects to a sandbox service for SaaS deployments.
15
+ """
16
+
17
+ def __init__(
18
+ self,
19
+ client: Any, # httpx.AsyncClient
20
+ api_url: str,
21
+ sandbox_id: str,
22
+ api_key: str,
23
+ config: SandboxConfig,
24
+ ) -> None:
25
+ self._client = client
26
+ self._api_url = api_url
27
+ self._sandbox_id = sandbox_id
28
+ self._api_key = api_key
29
+ self._config = config
30
+ self._status: Literal["creating", "running", "stopped", "failed"] = "running"
31
+
32
+ @property
33
+ def id(self) -> str:
34
+ return self._sandbox_id
35
+
36
+ @property
37
+ def status(self) -> Literal["creating", "running", "stopped", "failed"]:
38
+ return self._status
39
+
40
+ def _headers(self) -> dict[str, str]:
41
+ return {"Authorization": f"Bearer {self._api_key}"}
42
+
43
+ async def execute(
44
+ self,
45
+ command: str | list[str],
46
+ *,
47
+ timeout: int | None = None,
48
+ stdin: str | None = None,
49
+ env: dict[str, str] | None = None,
50
+ workdir: str | None = None,
51
+ ) -> ExecutionResult:
52
+ """Execute command via API."""
53
+ response = await self._client.post(
54
+ f"{self._api_url}/sandboxes/{self._sandbox_id}/exec",
55
+ headers=self._headers(),
56
+ json={
57
+ "command": command if isinstance(command, str) else " ".join(command),
58
+ "timeout": timeout or self._config.timeout,
59
+ "stdin": stdin,
60
+ "env": env,
61
+ "workdir": workdir or self._config.workdir,
62
+ },
63
+ timeout=timeout or self._config.timeout + 10,
64
+ )
65
+ response.raise_for_status()
66
+ data = response.json()
67
+
68
+ return ExecutionResult(
69
+ exit_code=data.get("exit_code", 0),
70
+ stdout=data.get("stdout", ""),
71
+ stderr=data.get("stderr", ""),
72
+ duration_ms=data.get("duration_ms", 0),
73
+ timed_out=data.get("timed_out", False),
74
+ )
75
+
76
+ async def write_file(self, path: str, content: str | bytes) -> None:
77
+ """Write file via API."""
78
+ if isinstance(content, str):
79
+ content = content.encode("utf-8")
80
+
81
+ response = await self._client.post(
82
+ f"{self._api_url}/sandboxes/{self._sandbox_id}/files",
83
+ headers=self._headers(),
84
+ json={
85
+ "path": path,
86
+ "content": base64.b64encode(content).decode("ascii"),
87
+ },
88
+ )
89
+ response.raise_for_status()
90
+
91
+ async def read_file(self, path: str) -> bytes:
92
+ """Read file via API."""
93
+ response = await self._client.get(
94
+ f"{self._api_url}/sandboxes/{self._sandbox_id}/files",
95
+ headers=self._headers(),
96
+ params={"path": path},
97
+ )
98
+ response.raise_for_status()
99
+ data = response.json()
100
+ return base64.b64decode(data.get("content", ""))
101
+
102
+ async def upload(self, local_path: Path, remote_path: str) -> None:
103
+ """Upload file via API."""
104
+ content = local_path.read_bytes()
105
+ await self.write_file(remote_path, content)
106
+
107
+ async def download(self, remote_path: str, local_path: Path) -> None:
108
+ """Download file via API."""
109
+ content = await self.read_file(remote_path)
110
+ local_path.write_bytes(content)
111
+
112
+ async def stop(self) -> None:
113
+ """Stop sandbox via API."""
114
+ response = await self._client.post(
115
+ f"{self._api_url}/sandboxes/{self._sandbox_id}/stop",
116
+ headers=self._headers(),
117
+ )
118
+ response.raise_for_status()
119
+ self._status = "stopped"
120
+
121
+ async def destroy(self) -> None:
122
+ """Destroy sandbox via API."""
123
+ response = await self._client.delete(
124
+ f"{self._api_url}/sandboxes/{self._sandbox_id}",
125
+ headers=self._headers(),
126
+ )
127
+ response.raise_for_status()
128
+ self._status = "stopped"
129
+
130
+
131
+ class RemoteSandboxProvider:
132
+ """Remote sandbox provider via HTTP API.
133
+
134
+ Creates sandboxes by calling a remote sandbox service.
135
+ Suitable for SaaS deployments with multi-tenancy.
136
+
137
+ Requires: httpx package (`pip install httpx`)
138
+ """
139
+
140
+ def __init__(
141
+ self,
142
+ api_url: str,
143
+ api_key: str,
144
+ default_config: SandboxConfig | None = None,
145
+ ) -> None:
146
+ self.api_url = api_url.rstrip("/")
147
+ self.api_key = api_key
148
+ self.default_config = default_config or SandboxConfig()
149
+ self._client: Any = None
150
+
151
+ async def _get_client(self) -> Any:
152
+ """Lazy-load httpx client."""
153
+ if self._client is None:
154
+ try:
155
+ import httpx
156
+ self._client = httpx.AsyncClient()
157
+ except ImportError:
158
+ raise ImportError(
159
+ "httpx package required for RemoteSandboxProvider. "
160
+ "Install with: pip install httpx"
161
+ )
162
+ return self._client
163
+
164
+ async def create(self, config: SandboxConfig | None = None) -> RemoteSandbox:
165
+ """Create a new remote sandbox."""
166
+ cfg = config or self.default_config
167
+ client = await self._get_client()
168
+
169
+ response = await client.post(
170
+ f"{self.api_url}/sandboxes",
171
+ headers={"Authorization": f"Bearer {self.api_key}"},
172
+ json={
173
+ "image": cfg.image,
174
+ "timeout": cfg.timeout,
175
+ "memory_limit": cfg.memory_limit,
176
+ "cpu_limit": cfg.cpu_limit,
177
+ "network": cfg.network,
178
+ "env": cfg.env,
179
+ "workdir": cfg.workdir,
180
+ },
181
+ )
182
+ response.raise_for_status()
183
+ data = response.json()
184
+
185
+ return RemoteSandbox(
186
+ client=client,
187
+ api_url=self.api_url,
188
+ sandbox_id=data["id"],
189
+ api_key=self.api_key,
190
+ config=cfg,
191
+ )
192
+
193
+ async def close(self) -> None:
194
+ """Close the HTTP client."""
195
+ if self._client:
196
+ await self._client.aclose()
197
+ self._client = None
198
+
199
+
200
+ __all__ = ["RemoteSandbox", "RemoteSandboxProvider"]
@@ -0,0 +1,115 @@
1
+ """Sandbox types and protocols."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass, field
5
+ from pathlib import Path
6
+ from typing import Literal, Protocol, runtime_checkable
7
+
8
+
9
+ @dataclass
10
+ class ExecutionResult:
11
+ """Result of command execution in sandbox."""
12
+ exit_code: int
13
+ stdout: str
14
+ stderr: str
15
+ duration_ms: int
16
+ timed_out: bool = False
17
+
18
+ @property
19
+ def success(self) -> bool:
20
+ return self.exit_code == 0 and not self.timed_out
21
+
22
+ @property
23
+ def output(self) -> str:
24
+ """Combined output for display."""
25
+ if self.success:
26
+ return self.stdout
27
+ return f"{self.stdout}\n{self.stderr}".strip()
28
+
29
+
30
+ @dataclass
31
+ class SandboxConfig:
32
+ """Configuration for sandbox creation."""
33
+ image: str = "python:3.11-slim"
34
+ timeout: int = 300 # seconds
35
+ memory_limit: str = "512m"
36
+ cpu_limit: float = 1.0
37
+ network: bool = False
38
+ volumes: dict[str, str] = field(default_factory=dict)
39
+ env: dict[str, str] = field(default_factory=dict)
40
+ workdir: str = "/workspace"
41
+
42
+
43
+ @runtime_checkable
44
+ class Sandbox(Protocol):
45
+ """Protocol for sandbox instances.
46
+
47
+ A sandbox provides an isolated environment for executing
48
+ commands and managing files safely.
49
+ """
50
+
51
+ @property
52
+ def id(self) -> str:
53
+ """Unique sandbox identifier."""
54
+ ...
55
+
56
+ @property
57
+ def status(self) -> Literal["creating", "running", "stopped", "failed"]:
58
+ """Current sandbox status."""
59
+ ...
60
+
61
+ async def execute(
62
+ self,
63
+ command: str | list[str],
64
+ *,
65
+ timeout: int | None = None,
66
+ stdin: str | None = None,
67
+ env: dict[str, str] | None = None,
68
+ workdir: str | None = None,
69
+ ) -> ExecutionResult:
70
+ """Execute a command in the sandbox."""
71
+ ...
72
+
73
+ async def write_file(self, path: str, content: str | bytes) -> None:
74
+ """Write a file to the sandbox."""
75
+ ...
76
+
77
+ async def read_file(self, path: str) -> bytes:
78
+ """Read a file from the sandbox."""
79
+ ...
80
+
81
+ async def upload(self, local_path: Path, remote_path: str) -> None:
82
+ """Upload a file or directory to the sandbox."""
83
+ ...
84
+
85
+ async def download(self, remote_path: str, local_path: Path) -> None:
86
+ """Download a file or directory from the sandbox."""
87
+ ...
88
+
89
+ async def stop(self) -> None:
90
+ """Stop the sandbox (can be restarted)."""
91
+ ...
92
+
93
+ async def destroy(self) -> None:
94
+ """Destroy the sandbox permanently."""
95
+ ...
96
+
97
+
98
+ @runtime_checkable
99
+ class SandboxProvider(Protocol):
100
+ """Protocol for sandbox providers.
101
+
102
+ Creates and manages sandbox instances.
103
+ """
104
+
105
+ async def create(self, config: SandboxConfig | None = None) -> Sandbox:
106
+ """Create a new sandbox instance."""
107
+ ...
108
+
109
+
110
+ __all__ = [
111
+ "ExecutionResult",
112
+ "SandboxConfig",
113
+ "Sandbox",
114
+ "SandboxProvider",
115
+ ]
@@ -0,0 +1,16 @@
1
+ """Skill module for agent capabilities.
2
+
3
+ Skills are reusable capability bundles that can be loaded and injected
4
+ into agent context. Each skill contains instructions, scripts, and references.
5
+
6
+ Usage:
7
+ Use SkillProvider (from providers module) to inject skills into agent context.
8
+ """
9
+ from .types import Skill
10
+ from .loader import SkillLoader
11
+
12
+
13
+ __all__ = [
14
+ "Skill",
15
+ "SkillLoader",
16
+ ]