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.
- aury/__init__.py +2 -0
- aury/agents/__init__.py +55 -0
- aury/agents/a2a/__init__.py +168 -0
- aury/agents/backends/__init__.py +196 -0
- aury/agents/backends/artifact/__init__.py +9 -0
- aury/agents/backends/artifact/memory.py +130 -0
- aury/agents/backends/artifact/types.py +133 -0
- aury/agents/backends/code/__init__.py +65 -0
- aury/agents/backends/file/__init__.py +11 -0
- aury/agents/backends/file/local.py +66 -0
- aury/agents/backends/file/types.py +40 -0
- aury/agents/backends/invocation/__init__.py +8 -0
- aury/agents/backends/invocation/memory.py +81 -0
- aury/agents/backends/invocation/types.py +110 -0
- aury/agents/backends/memory/__init__.py +8 -0
- aury/agents/backends/memory/memory.py +179 -0
- aury/agents/backends/memory/types.py +136 -0
- aury/agents/backends/message/__init__.py +9 -0
- aury/agents/backends/message/memory.py +122 -0
- aury/agents/backends/message/types.py +124 -0
- aury/agents/backends/sandbox.py +275 -0
- aury/agents/backends/session/__init__.py +8 -0
- aury/agents/backends/session/memory.py +93 -0
- aury/agents/backends/session/types.py +124 -0
- aury/agents/backends/shell/__init__.py +11 -0
- aury/agents/backends/shell/local.py +110 -0
- aury/agents/backends/shell/types.py +55 -0
- aury/agents/backends/shell.py +209 -0
- aury/agents/backends/snapshot/__init__.py +19 -0
- aury/agents/backends/snapshot/git.py +95 -0
- aury/agents/backends/snapshot/hybrid.py +125 -0
- aury/agents/backends/snapshot/memory.py +86 -0
- aury/agents/backends/snapshot/types.py +59 -0
- aury/agents/backends/state/__init__.py +29 -0
- aury/agents/backends/state/composite.py +49 -0
- aury/agents/backends/state/file.py +57 -0
- aury/agents/backends/state/memory.py +52 -0
- aury/agents/backends/state/sqlite.py +262 -0
- aury/agents/backends/state/types.py +178 -0
- aury/agents/backends/subagent/__init__.py +165 -0
- aury/agents/cli/__init__.py +41 -0
- aury/agents/cli/chat.py +239 -0
- aury/agents/cli/config.py +236 -0
- aury/agents/cli/extensions.py +460 -0
- aury/agents/cli/main.py +189 -0
- aury/agents/cli/session.py +337 -0
- aury/agents/cli/workflow.py +276 -0
- aury/agents/context_providers/__init__.py +66 -0
- aury/agents/context_providers/artifact.py +299 -0
- aury/agents/context_providers/base.py +177 -0
- aury/agents/context_providers/memory.py +70 -0
- aury/agents/context_providers/message.py +130 -0
- aury/agents/context_providers/skill.py +50 -0
- aury/agents/context_providers/subagent.py +46 -0
- aury/agents/context_providers/tool.py +68 -0
- aury/agents/core/__init__.py +83 -0
- aury/agents/core/base.py +573 -0
- aury/agents/core/context.py +797 -0
- aury/agents/core/context_builder.py +303 -0
- aury/agents/core/event_bus/__init__.py +15 -0
- aury/agents/core/event_bus/bus.py +203 -0
- aury/agents/core/factory.py +169 -0
- aury/agents/core/isolator.py +97 -0
- aury/agents/core/logging.py +95 -0
- aury/agents/core/parallel.py +194 -0
- aury/agents/core/runner.py +139 -0
- aury/agents/core/services/__init__.py +5 -0
- aury/agents/core/services/file_session.py +144 -0
- aury/agents/core/services/message.py +53 -0
- aury/agents/core/services/session.py +53 -0
- aury/agents/core/signals.py +109 -0
- aury/agents/core/state.py +363 -0
- aury/agents/core/types/__init__.py +107 -0
- aury/agents/core/types/action.py +176 -0
- aury/agents/core/types/artifact.py +135 -0
- aury/agents/core/types/block.py +736 -0
- aury/agents/core/types/message.py +350 -0
- aury/agents/core/types/recall.py +144 -0
- aury/agents/core/types/session.py +257 -0
- aury/agents/core/types/subagent.py +154 -0
- aury/agents/core/types/tool.py +205 -0
- aury/agents/eval/__init__.py +331 -0
- aury/agents/hitl/__init__.py +57 -0
- aury/agents/hitl/ask_user.py +242 -0
- aury/agents/hitl/compaction.py +230 -0
- aury/agents/hitl/exceptions.py +87 -0
- aury/agents/hitl/permission.py +617 -0
- aury/agents/hitl/revert.py +216 -0
- aury/agents/llm/__init__.py +31 -0
- aury/agents/llm/adapter.py +367 -0
- aury/agents/llm/openai.py +294 -0
- aury/agents/llm/provider.py +476 -0
- aury/agents/mcp/__init__.py +153 -0
- aury/agents/memory/__init__.py +46 -0
- aury/agents/memory/compaction.py +394 -0
- aury/agents/memory/manager.py +465 -0
- aury/agents/memory/processor.py +177 -0
- aury/agents/memory/store.py +187 -0
- aury/agents/memory/types.py +137 -0
- aury/agents/messages/__init__.py +40 -0
- aury/agents/messages/config.py +47 -0
- aury/agents/messages/raw_store.py +224 -0
- aury/agents/messages/store.py +118 -0
- aury/agents/messages/types.py +88 -0
- aury/agents/middleware/__init__.py +31 -0
- aury/agents/middleware/base.py +341 -0
- aury/agents/middleware/chain.py +342 -0
- aury/agents/middleware/message.py +129 -0
- aury/agents/middleware/message_container.py +126 -0
- aury/agents/middleware/raw_message.py +153 -0
- aury/agents/middleware/truncation.py +139 -0
- aury/agents/middleware/types.py +81 -0
- aury/agents/plugin.py +162 -0
- aury/agents/react/__init__.py +4 -0
- aury/agents/react/agent.py +1923 -0
- aury/agents/sandbox/__init__.py +23 -0
- aury/agents/sandbox/local.py +239 -0
- aury/agents/sandbox/remote.py +200 -0
- aury/agents/sandbox/types.py +115 -0
- aury/agents/skill/__init__.py +16 -0
- aury/agents/skill/loader.py +180 -0
- aury/agents/skill/types.py +83 -0
- aury/agents/tool/__init__.py +39 -0
- aury/agents/tool/builtin/__init__.py +23 -0
- aury/agents/tool/builtin/ask_user.py +155 -0
- aury/agents/tool/builtin/bash.py +107 -0
- aury/agents/tool/builtin/delegate.py +726 -0
- aury/agents/tool/builtin/edit.py +121 -0
- aury/agents/tool/builtin/plan.py +277 -0
- aury/agents/tool/builtin/read.py +91 -0
- aury/agents/tool/builtin/thinking.py +111 -0
- aury/agents/tool/builtin/yield_result.py +130 -0
- aury/agents/tool/decorator.py +252 -0
- aury/agents/tool/set.py +204 -0
- aury/agents/usage/__init__.py +12 -0
- aury/agents/usage/tracker.py +236 -0
- aury/agents/workflow/__init__.py +85 -0
- aury/agents/workflow/adapter.py +268 -0
- aury/agents/workflow/dag.py +116 -0
- aury/agents/workflow/dsl.py +575 -0
- aury/agents/workflow/executor.py +659 -0
- aury/agents/workflow/expression.py +136 -0
- aury/agents/workflow/parser.py +182 -0
- aury/agents/workflow/state.py +145 -0
- aury/agents/workflow/types.py +86 -0
- aury_agent-0.0.4.dist-info/METADATA +90 -0
- aury_agent-0.0.4.dist-info/RECORD +149 -0
- aury_agent-0.0.4.dist-info/WHEEL +4 -0
- 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,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
|
+
]
|