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,136 @@
1
+ """Memory backend types and protocols."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, Protocol, runtime_checkable
5
+
6
+
7
+ @runtime_checkable
8
+ class MemoryBackend(Protocol):
9
+ """Protocol for long-term memory storage.
10
+
11
+ Handles persistent memory entries with semantic search capability.
12
+ Used for RAG, knowledge recall, and conversation summaries.
13
+
14
+ Example usage:
15
+ # Add memory
16
+ memory_id = await backend.add(
17
+ session_id="sess_123",
18
+ content="User prefers dark mode and Python",
19
+ namespace="preferences",
20
+ metadata={"importance": 0.8},
21
+ )
22
+
23
+ # Search memories
24
+ results = await backend.search(
25
+ session_id="sess_123",
26
+ query="what are user preferences",
27
+ limit=5,
28
+ )
29
+
30
+ # Delete memories from invocation (for revert)
31
+ await backend.delete_by_invocation("sess_123", "inv_456")
32
+ """
33
+
34
+ async def add(
35
+ self,
36
+ session_id: str,
37
+ content: str,
38
+ invocation_id: str | None = None,
39
+ namespace: str | None = None,
40
+ metadata: dict[str, Any] | None = None,
41
+ ) -> str:
42
+ """Add a memory entry.
43
+
44
+ Args:
45
+ session_id: Session ID
46
+ content: Memory content text
47
+ invocation_id: Optional invocation ID for grouping/revert
48
+ namespace: Optional namespace for categorization
49
+ metadata: Optional metadata (importance, tags, etc.)
50
+
51
+ Returns:
52
+ Generated memory entry ID
53
+ """
54
+ ...
55
+
56
+ async def search(
57
+ self,
58
+ session_id: str,
59
+ query: str,
60
+ namespace: str | None = None,
61
+ limit: int = 10,
62
+ ) -> list[dict[str, Any]]:
63
+ """Search memories by semantic relevance.
64
+
65
+ Args:
66
+ session_id: Session ID
67
+ query: Search query
68
+ namespace: Optional namespace filter
69
+ limit: Max results to return
70
+
71
+ Returns:
72
+ List of memory dicts with scores, ordered by relevance:
73
+ [{"id": str, "content": str, "score": float, "metadata": dict}, ...]
74
+ """
75
+ ...
76
+
77
+ async def get(self, id: str) -> dict[str, Any] | None:
78
+ """Get memory by ID.
79
+
80
+ Args:
81
+ id: Memory entry ID
82
+
83
+ Returns:
84
+ Memory dict or None if not found
85
+ """
86
+ ...
87
+
88
+ async def delete(self, id: str) -> bool:
89
+ """Delete a memory entry.
90
+
91
+ Args:
92
+ id: Memory entry ID
93
+
94
+ Returns:
95
+ True if deleted, False if not found
96
+ """
97
+ ...
98
+
99
+ async def delete_by_invocation(
100
+ self,
101
+ session_id: str,
102
+ invocation_id: str,
103
+ namespace: str | None = None,
104
+ ) -> int:
105
+ """Delete memories by invocation (for revert).
106
+
107
+ Args:
108
+ session_id: Session ID
109
+ invocation_id: Invocation ID to delete
110
+ namespace: Optional namespace filter
111
+
112
+ Returns:
113
+ Number of memories deleted
114
+ """
115
+ ...
116
+
117
+ async def list(
118
+ self,
119
+ session_id: str,
120
+ namespace: str | None = None,
121
+ limit: int = 100,
122
+ ) -> list[dict[str, Any]]:
123
+ """List memories for a session (without search).
124
+
125
+ Args:
126
+ session_id: Session ID
127
+ namespace: Optional namespace filter
128
+ limit: Max results to return
129
+
130
+ Returns:
131
+ List of memory dicts, ordered by created_at desc
132
+ """
133
+ ...
134
+
135
+
136
+ __all__ = ["MemoryBackend"]
@@ -0,0 +1,9 @@
1
+ """Message backend."""
2
+ from .types import MessageBackend, MessageType
3
+ from .memory import InMemoryMessageBackend
4
+
5
+ __all__ = [
6
+ "MessageBackend",
7
+ "MessageType",
8
+ "InMemoryMessageBackend",
9
+ ]
@@ -0,0 +1,122 @@
1
+ """In-memory message backend implementation."""
2
+ from __future__ import annotations
3
+
4
+ from datetime import datetime
5
+ from typing import Any
6
+
7
+ from .types import MessageType
8
+
9
+
10
+ class InMemoryMessageBackend:
11
+ """In-memory implementation of MessageBackend.
12
+
13
+ Stores both truncated and raw messages in separate dicts.
14
+ Suitable for testing and simple single-process use cases.
15
+ """
16
+
17
+ def __init__(self) -> None:
18
+ # Key format: "{session_id}" or "{session_id}:{namespace}"
19
+ # Value: list of message dicts
20
+ self._truncated: dict[str, list[dict[str, Any]]] = {}
21
+ self._raw: dict[str, list[dict[str, Any]]] = {}
22
+
23
+ def _make_key(self, session_id: str, namespace: str | None) -> str:
24
+ if namespace:
25
+ return f"{session_id}:{namespace}"
26
+ return session_id
27
+
28
+ def _get_store(self, type: MessageType) -> dict[str, list[dict[str, Any]]]:
29
+ return self._truncated if type == "truncated" else self._raw
30
+
31
+ async def add(
32
+ self,
33
+ session_id: str,
34
+ message: dict[str, Any],
35
+ type: MessageType = "truncated",
36
+ agent_id: str | None = None,
37
+ namespace: str | None = None,
38
+ invocation_id: str | None = None,
39
+ ) -> None:
40
+ """Add a message."""
41
+ key = self._make_key(session_id, namespace)
42
+ store = self._get_store(type)
43
+
44
+ if key not in store:
45
+ store[key] = []
46
+
47
+ # Add metadata
48
+ msg = {
49
+ **message,
50
+ "agent_id": agent_id,
51
+ "invocation_id": invocation_id,
52
+ "created_at": datetime.now().isoformat(),
53
+ }
54
+ store[key].append(msg)
55
+
56
+ async def get(
57
+ self,
58
+ session_id: str,
59
+ type: MessageType = "truncated",
60
+ agent_id: str | None = None,
61
+ namespace: str | None = None,
62
+ limit: int | None = None,
63
+ ) -> list[dict[str, Any]]:
64
+ """Get messages."""
65
+ key = self._make_key(session_id, namespace)
66
+ store = self._get_store(type)
67
+ messages = store.get(key, [])
68
+
69
+ # Filter by agent_id if specified
70
+ if agent_id:
71
+ messages = [m for m in messages if m.get("agent_id") == agent_id]
72
+
73
+ # Apply limit (return last N messages)
74
+ if limit:
75
+ messages = messages[-limit:]
76
+
77
+ return messages.copy()
78
+
79
+ async def delete_by_invocation(
80
+ self,
81
+ session_id: str,
82
+ invocation_id: str,
83
+ type: MessageType | None = None,
84
+ namespace: str | None = None,
85
+ ) -> int:
86
+ """Delete messages by invocation."""
87
+ key = self._make_key(session_id, namespace)
88
+ deleted = 0
89
+
90
+ types_to_delete = [type] if type else ["truncated", "raw"]
91
+
92
+ for t in types_to_delete:
93
+ store = self._get_store(t)
94
+ if key in store:
95
+ original = store[key]
96
+ store[key] = [m for m in original if m.get("invocation_id") != invocation_id]
97
+ deleted += len(original) - len(store[key])
98
+
99
+ return deleted
100
+
101
+ async def clear(
102
+ self,
103
+ session_id: str,
104
+ type: MessageType | None = None,
105
+ namespace: str | None = None,
106
+ ) -> int:
107
+ """Clear all messages for a session."""
108
+ key = self._make_key(session_id, namespace)
109
+ deleted = 0
110
+
111
+ types_to_clear = [type] if type else ["truncated", "raw"]
112
+
113
+ for t in types_to_clear:
114
+ store = self._get_store(t)
115
+ if key in store:
116
+ deleted += len(store[key])
117
+ del store[key]
118
+
119
+ return deleted
120
+
121
+
122
+ __all__ = ["InMemoryMessageBackend"]
@@ -0,0 +1,124 @@
1
+ """Message backend types and protocols."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, Literal, Protocol, runtime_checkable
5
+
6
+
7
+ MessageType = Literal["truncated", "raw"]
8
+
9
+
10
+ @runtime_checkable
11
+ class MessageBackend(Protocol):
12
+ """Protocol for message storage.
13
+
14
+ Handles both truncated (context window) and raw (full history) messages
15
+ through a unified interface with type parameter.
16
+
17
+ - truncated: Messages kept in context window, may be summarized/trimmed
18
+ - raw: Full original messages for audit/replay
19
+
20
+ Example usage:
21
+ # Add truncated message (for LLM context)
22
+ await backend.add(
23
+ session_id="sess_123",
24
+ message={"role": "user", "content": "Hello"},
25
+ type="truncated",
26
+ )
27
+
28
+ # Add raw message (for audit)
29
+ await backend.add(
30
+ session_id="sess_123",
31
+ message={"role": "user", "content": "Hello", "attachments": [...]},
32
+ type="raw",
33
+ )
34
+
35
+ # Get messages for LLM
36
+ messages = await backend.get("sess_123", type="truncated", limit=50)
37
+
38
+ # Get raw history
39
+ raw_messages = await backend.get("sess_123", type="raw")
40
+ """
41
+
42
+ async def add(
43
+ self,
44
+ session_id: str,
45
+ message: dict[str, Any],
46
+ type: MessageType = "truncated",
47
+ agent_id: str | None = None,
48
+ namespace: str | None = None,
49
+ invocation_id: str | None = None,
50
+ ) -> None:
51
+ """Add a message.
52
+
53
+ Args:
54
+ session_id: Session ID
55
+ message: Message dict (role, content, tool_call_id, etc.)
56
+ type: Message type - "truncated" or "raw"
57
+ agent_id: Optional agent ID
58
+ namespace: Optional namespace for sub-agent isolation
59
+ invocation_id: Optional invocation ID for grouping
60
+ """
61
+ ...
62
+
63
+ async def get(
64
+ self,
65
+ session_id: str,
66
+ type: MessageType = "truncated",
67
+ agent_id: str | None = None,
68
+ namespace: str | None = None,
69
+ limit: int | None = None,
70
+ ) -> list[dict[str, Any]]:
71
+ """Get messages.
72
+
73
+ Args:
74
+ session_id: Session ID
75
+ type: Message type - "truncated" or "raw"
76
+ agent_id: Optional filter by agent
77
+ namespace: Optional namespace filter
78
+ limit: Max messages to return (None = all)
79
+
80
+ Returns:
81
+ List of message dicts in chronological order
82
+ """
83
+ ...
84
+
85
+ async def delete_by_invocation(
86
+ self,
87
+ session_id: str,
88
+ invocation_id: str,
89
+ type: MessageType | None = None,
90
+ namespace: str | None = None,
91
+ ) -> int:
92
+ """Delete messages by invocation (for revert).
93
+
94
+ Args:
95
+ session_id: Session ID
96
+ invocation_id: Invocation ID to delete
97
+ type: Message type to delete, None = both types
98
+ namespace: Optional namespace filter
99
+
100
+ Returns:
101
+ Number of messages deleted
102
+ """
103
+ ...
104
+
105
+ async def clear(
106
+ self,
107
+ session_id: str,
108
+ type: MessageType | None = None,
109
+ namespace: str | None = None,
110
+ ) -> int:
111
+ """Clear all messages for a session.
112
+
113
+ Args:
114
+ session_id: Session ID
115
+ type: Message type to clear, None = both types
116
+ namespace: Optional namespace filter
117
+
118
+ Returns:
119
+ Number of messages deleted
120
+ """
121
+ ...
122
+
123
+
124
+ __all__ = ["MessageBackend", "MessageType"]
@@ -0,0 +1,275 @@
1
+ """Sandbox-based backend implementations.
2
+
3
+ Provides ShellBackend and CodeBackend implementations that execute
4
+ commands and code in isolated sandbox environments.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import TYPE_CHECKING, AsyncIterator
9
+
10
+ from .shell import ShellBackend, ShellResult
11
+ from .code import CodeBackend, CodeResult
12
+
13
+ if TYPE_CHECKING:
14
+ from ..sandbox import Sandbox, SandboxProvider, SandboxConfig
15
+
16
+
17
+ class SandboxShellBackend:
18
+ """Shell backend that executes commands in a sandbox.
19
+
20
+ Commands are executed in an isolated container environment,
21
+ providing security isolation from the host system.
22
+
23
+ Usage:
24
+ provider = LocalSandboxProvider()
25
+ backend = SandboxShellBackend(provider)
26
+
27
+ result = await backend.execute("ls -la")
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ provider: "SandboxProvider",
33
+ config: "SandboxConfig | None" = None,
34
+ reuse_sandbox: bool = True,
35
+ ) -> None:
36
+ """Initialize sandbox shell backend.
37
+
38
+ Args:
39
+ provider: Sandbox provider for creating sandboxes
40
+ config: Optional sandbox configuration
41
+ reuse_sandbox: If True, reuse sandbox across commands (default)
42
+ """
43
+ self.provider = provider
44
+ self.config = config
45
+ self.reuse_sandbox = reuse_sandbox
46
+ self._sandbox: "Sandbox | None" = None
47
+
48
+ async def _get_sandbox(self) -> "Sandbox":
49
+ """Get or create sandbox instance."""
50
+ if self._sandbox is None or not self.reuse_sandbox:
51
+ self._sandbox = await self.provider.create(self.config)
52
+ return self._sandbox
53
+
54
+ async def execute(
55
+ self,
56
+ command: str,
57
+ cwd: str | None = None,
58
+ env: dict[str, str] | None = None,
59
+ timeout: int = 120,
60
+ ) -> ShellResult:
61
+ """Execute a shell command in the sandbox.
62
+
63
+ Args:
64
+ command: Shell command to execute
65
+ cwd: Working directory (relative to sandbox workdir)
66
+ env: Environment variables
67
+ timeout: Timeout in seconds
68
+
69
+ Returns:
70
+ ShellResult with stdout, stderr, exit_code
71
+ """
72
+ sandbox = await self._get_sandbox()
73
+
74
+ result = await sandbox.execute(
75
+ command,
76
+ workdir=cwd,
77
+ env=env,
78
+ timeout=timeout,
79
+ )
80
+
81
+ return ShellResult(
82
+ stdout=result.stdout,
83
+ stderr=result.stderr,
84
+ exit_code=result.exit_code,
85
+ command=command,
86
+ cwd=cwd or "",
87
+ duration_ms=result.duration_ms,
88
+ )
89
+
90
+ async def execute_stream(
91
+ self,
92
+ command: str,
93
+ cwd: str | None = None,
94
+ env: dict[str, str] | None = None,
95
+ timeout: int = 120,
96
+ ) -> AsyncIterator[str]:
97
+ """Execute command with streaming output.
98
+
99
+ Note: Basic implementation - executes and yields result.
100
+ Full streaming requires sandbox API support.
101
+ """
102
+ result = await self.execute(command, cwd, env, timeout)
103
+ if result.stdout:
104
+ yield result.stdout
105
+ if result.stderr:
106
+ yield result.stderr
107
+
108
+ async def cleanup(self) -> None:
109
+ """Cleanup sandbox resources."""
110
+ if self._sandbox:
111
+ await self._sandbox.destroy()
112
+ self._sandbox = None
113
+
114
+
115
+ class SandboxCodeBackend:
116
+ """Code backend that executes code in a sandbox.
117
+
118
+ Supports multiple programming languages by writing code
119
+ to files and executing with appropriate interpreters.
120
+
121
+ Usage:
122
+ provider = LocalSandboxProvider()
123
+ backend = SandboxCodeBackend(provider)
124
+
125
+ result = await backend.execute("print('hello')", language="python")
126
+ """
127
+
128
+ # Language configurations
129
+ LANGUAGE_CONFIG = {
130
+ "python": {
131
+ "extension": ".py",
132
+ "command": "python3",
133
+ },
134
+ "python3": {
135
+ "extension": ".py",
136
+ "command": "python3",
137
+ },
138
+ "javascript": {
139
+ "extension": ".js",
140
+ "command": "node",
141
+ },
142
+ "js": {
143
+ "extension": ".js",
144
+ "command": "node",
145
+ },
146
+ "typescript": {
147
+ "extension": ".ts",
148
+ "command": "npx ts-node",
149
+ },
150
+ "ts": {
151
+ "extension": ".ts",
152
+ "command": "npx ts-node",
153
+ },
154
+ "bash": {
155
+ "extension": ".sh",
156
+ "command": "bash",
157
+ },
158
+ "sh": {
159
+ "extension": ".sh",
160
+ "command": "sh",
161
+ },
162
+ "ruby": {
163
+ "extension": ".rb",
164
+ "command": "ruby",
165
+ },
166
+ "php": {
167
+ "extension": ".php",
168
+ "command": "php",
169
+ },
170
+ "go": {
171
+ "extension": ".go",
172
+ "command": "go run",
173
+ },
174
+ "rust": {
175
+ "extension": ".rs",
176
+ "command": "rustc -o /tmp/out && /tmp/out", # Compile and run
177
+ },
178
+ }
179
+
180
+ def __init__(
181
+ self,
182
+ provider: "SandboxProvider",
183
+ config: "SandboxConfig | None" = None,
184
+ reuse_sandbox: bool = True,
185
+ ) -> None:
186
+ """Initialize sandbox code backend.
187
+
188
+ Args:
189
+ provider: Sandbox provider for creating sandboxes
190
+ config: Optional sandbox configuration
191
+ reuse_sandbox: If True, reuse sandbox across executions (default)
192
+ """
193
+ self.provider = provider
194
+ self.config = config
195
+ self.reuse_sandbox = reuse_sandbox
196
+ self._sandbox: "Sandbox | None" = None
197
+ self._exec_counter = 0
198
+
199
+ @property
200
+ def supported_languages(self) -> list[str]:
201
+ """List of supported programming languages."""
202
+ return list(self.LANGUAGE_CONFIG.keys())
203
+
204
+ async def _get_sandbox(self) -> "Sandbox":
205
+ """Get or create sandbox instance."""
206
+ if self._sandbox is None or not self.reuse_sandbox:
207
+ self._sandbox = await self.provider.create(self.config)
208
+ return self._sandbox
209
+
210
+ async def execute(
211
+ self,
212
+ code: str,
213
+ language: str = "python",
214
+ timeout: int = 120,
215
+ ) -> CodeResult:
216
+ """Execute code in the sandbox.
217
+
218
+ Args:
219
+ code: Source code to execute
220
+ language: Programming language
221
+ timeout: Timeout in seconds
222
+
223
+ Returns:
224
+ CodeResult with output, error, exit_code
225
+ """
226
+ # Normalize language name
227
+ lang_lower = language.lower()
228
+ if lang_lower not in self.LANGUAGE_CONFIG:
229
+ return CodeResult(
230
+ output="",
231
+ error=f"Unsupported language: {language}. "
232
+ f"Supported: {', '.join(self.supported_languages)}",
233
+ exit_code=1,
234
+ language=language,
235
+ )
236
+
237
+ lang_config = self.LANGUAGE_CONFIG[lang_lower]
238
+ sandbox = await self._get_sandbox()
239
+
240
+ # Generate unique filename
241
+ self._exec_counter += 1
242
+ filename = f"code_{self._exec_counter}{lang_config['extension']}"
243
+ filepath = f"/workspace/{filename}"
244
+
245
+ # Write code to sandbox
246
+ await sandbox.write_file(filepath, code)
247
+
248
+ # Build execution command
249
+ command = f"{lang_config['command']} {filepath}"
250
+
251
+ # Execute
252
+ result = await sandbox.execute(
253
+ command,
254
+ timeout=timeout,
255
+ )
256
+
257
+ return CodeResult(
258
+ output=result.stdout,
259
+ error=result.stderr,
260
+ exit_code=result.exit_code,
261
+ language=language,
262
+ duration_ms=result.duration_ms,
263
+ )
264
+
265
+ async def cleanup(self) -> None:
266
+ """Cleanup sandbox resources."""
267
+ if self._sandbox:
268
+ await self._sandbox.destroy()
269
+ self._sandbox = None
270
+
271
+
272
+ __all__ = [
273
+ "SandboxShellBackend",
274
+ "SandboxCodeBackend",
275
+ ]
@@ -0,0 +1,8 @@
1
+ """Session backend."""
2
+ from .types import SessionBackend
3
+ from .memory import InMemorySessionBackend
4
+
5
+ __all__ = [
6
+ "SessionBackend",
7
+ "InMemorySessionBackend",
8
+ ]