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,262 @@
|
|
|
1
|
+
"""SQLite-based state backend for local persistence.
|
|
2
|
+
|
|
3
|
+
Provides persistent storage using SQLite database.
|
|
4
|
+
Ideal for local development and single-user applications.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import sqlite3
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SQLiteStateBackend:
|
|
15
|
+
"""SQLite-based state backend.
|
|
16
|
+
|
|
17
|
+
Stores all framework state in a single SQLite database file.
|
|
18
|
+
Supports both key-value storage and message history.
|
|
19
|
+
|
|
20
|
+
Tables:
|
|
21
|
+
- state: namespace/key/value for general storage
|
|
22
|
+
- messages: session_id/role/content for conversation history
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
backend = SQLiteStateBackend("./data/agent.db")
|
|
26
|
+
|
|
27
|
+
# Key-value storage
|
|
28
|
+
await backend.set("sessions", "sess_123", {"id": "sess_123"})
|
|
29
|
+
data = await backend.get("sessions", "sess_123")
|
|
30
|
+
|
|
31
|
+
# Message history
|
|
32
|
+
await backend.add_message("sess_123", {"role": "user", "content": "Hello"})
|
|
33
|
+
messages = await backend.get_messages("sess_123")
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, db_path: str | Path = "./data/agent.db"):
|
|
37
|
+
"""Initialize SQLite backend.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
db_path: Path to SQLite database file
|
|
41
|
+
"""
|
|
42
|
+
self.db_path = Path(db_path)
|
|
43
|
+
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
self._init_db()
|
|
45
|
+
|
|
46
|
+
def _init_db(self) -> None:
|
|
47
|
+
"""Initialize database tables."""
|
|
48
|
+
conn = sqlite3.connect(self.db_path)
|
|
49
|
+
try:
|
|
50
|
+
conn.execute("""
|
|
51
|
+
CREATE TABLE IF NOT EXISTS state (
|
|
52
|
+
namespace TEXT NOT NULL,
|
|
53
|
+
key TEXT NOT NULL,
|
|
54
|
+
value TEXT NOT NULL,
|
|
55
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
56
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
57
|
+
PRIMARY KEY (namespace, key)
|
|
58
|
+
)
|
|
59
|
+
""")
|
|
60
|
+
conn.execute("""
|
|
61
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
62
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
63
|
+
session_id TEXT NOT NULL,
|
|
64
|
+
namespace TEXT,
|
|
65
|
+
role TEXT NOT NULL,
|
|
66
|
+
content TEXT NOT NULL,
|
|
67
|
+
invocation_id TEXT,
|
|
68
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
69
|
+
)
|
|
70
|
+
""")
|
|
71
|
+
conn.execute("""
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_messages_session
|
|
73
|
+
ON messages(session_id, namespace)
|
|
74
|
+
""")
|
|
75
|
+
conn.commit()
|
|
76
|
+
finally:
|
|
77
|
+
conn.close()
|
|
78
|
+
|
|
79
|
+
def _get_conn(self) -> sqlite3.Connection:
|
|
80
|
+
"""Get database connection."""
|
|
81
|
+
conn = sqlite3.connect(self.db_path)
|
|
82
|
+
conn.row_factory = sqlite3.Row
|
|
83
|
+
return conn
|
|
84
|
+
|
|
85
|
+
# ========== Key-Value Storage ==========
|
|
86
|
+
|
|
87
|
+
async def get(self, namespace: str, key: str) -> Any | None:
|
|
88
|
+
"""Get value by key."""
|
|
89
|
+
conn = self._get_conn()
|
|
90
|
+
try:
|
|
91
|
+
cursor = conn.execute(
|
|
92
|
+
"SELECT value FROM state WHERE namespace = ? AND key = ?",
|
|
93
|
+
(namespace, key)
|
|
94
|
+
)
|
|
95
|
+
row = cursor.fetchone()
|
|
96
|
+
if row:
|
|
97
|
+
return json.loads(row["value"])
|
|
98
|
+
return None
|
|
99
|
+
finally:
|
|
100
|
+
conn.close()
|
|
101
|
+
|
|
102
|
+
async def set(self, namespace: str, key: str, value: Any) -> None:
|
|
103
|
+
"""Set value by key."""
|
|
104
|
+
conn = self._get_conn()
|
|
105
|
+
try:
|
|
106
|
+
value_json = json.dumps(value, default=str, ensure_ascii=False)
|
|
107
|
+
conn.execute("""
|
|
108
|
+
INSERT INTO state (namespace, key, value, updated_at)
|
|
109
|
+
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
|
110
|
+
ON CONFLICT(namespace, key) DO UPDATE SET
|
|
111
|
+
value = excluded.value,
|
|
112
|
+
updated_at = CURRENT_TIMESTAMP
|
|
113
|
+
""", (namespace, key, value_json))
|
|
114
|
+
conn.commit()
|
|
115
|
+
finally:
|
|
116
|
+
conn.close()
|
|
117
|
+
|
|
118
|
+
async def delete(self, namespace: str, key: str) -> bool:
|
|
119
|
+
"""Delete value by key."""
|
|
120
|
+
conn = self._get_conn()
|
|
121
|
+
try:
|
|
122
|
+
cursor = conn.execute(
|
|
123
|
+
"DELETE FROM state WHERE namespace = ? AND key = ?",
|
|
124
|
+
(namespace, key)
|
|
125
|
+
)
|
|
126
|
+
conn.commit()
|
|
127
|
+
return cursor.rowcount > 0
|
|
128
|
+
finally:
|
|
129
|
+
conn.close()
|
|
130
|
+
|
|
131
|
+
async def list(self, namespace: str, prefix: str = "") -> list[str]:
|
|
132
|
+
"""List keys with optional prefix filter."""
|
|
133
|
+
conn = self._get_conn()
|
|
134
|
+
try:
|
|
135
|
+
if prefix:
|
|
136
|
+
cursor = conn.execute(
|
|
137
|
+
"SELECT key FROM state WHERE namespace = ? AND key LIKE ?",
|
|
138
|
+
(namespace, f"{prefix}%")
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
cursor = conn.execute(
|
|
142
|
+
"SELECT key FROM state WHERE namespace = ?",
|
|
143
|
+
(namespace,)
|
|
144
|
+
)
|
|
145
|
+
return [row["key"] for row in cursor.fetchall()]
|
|
146
|
+
finally:
|
|
147
|
+
conn.close()
|
|
148
|
+
|
|
149
|
+
async def exists(self, namespace: str, key: str) -> bool:
|
|
150
|
+
"""Check if key exists."""
|
|
151
|
+
conn = self._get_conn()
|
|
152
|
+
try:
|
|
153
|
+
cursor = conn.execute(
|
|
154
|
+
"SELECT 1 FROM state WHERE namespace = ? AND key = ?",
|
|
155
|
+
(namespace, key)
|
|
156
|
+
)
|
|
157
|
+
return cursor.fetchone() is not None
|
|
158
|
+
finally:
|
|
159
|
+
conn.close()
|
|
160
|
+
|
|
161
|
+
# ========== Message Storage ==========
|
|
162
|
+
|
|
163
|
+
async def add_message(
|
|
164
|
+
self,
|
|
165
|
+
session_id: str,
|
|
166
|
+
message: dict[str, Any],
|
|
167
|
+
namespace: str | None = None,
|
|
168
|
+
) -> None:
|
|
169
|
+
"""Add a message to session history.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
session_id: Session ID
|
|
173
|
+
message: Message dict with role, content, invocation_id
|
|
174
|
+
namespace: Optional namespace for isolation
|
|
175
|
+
"""
|
|
176
|
+
conn = self._get_conn()
|
|
177
|
+
try:
|
|
178
|
+
content = message.get("content", "")
|
|
179
|
+
# Serialize list content (tool_use etc.)
|
|
180
|
+
if isinstance(content, list):
|
|
181
|
+
content = json.dumps(content, ensure_ascii=False)
|
|
182
|
+
|
|
183
|
+
conn.execute("""
|
|
184
|
+
INSERT INTO messages (session_id, namespace, role, content, invocation_id)
|
|
185
|
+
VALUES (?, ?, ?, ?, ?)
|
|
186
|
+
""", (
|
|
187
|
+
session_id,
|
|
188
|
+
namespace,
|
|
189
|
+
message.get("role", "user"),
|
|
190
|
+
content,
|
|
191
|
+
message.get("invocation_id"),
|
|
192
|
+
))
|
|
193
|
+
conn.commit()
|
|
194
|
+
finally:
|
|
195
|
+
conn.close()
|
|
196
|
+
|
|
197
|
+
async def get_messages(
|
|
198
|
+
self,
|
|
199
|
+
session_id: str,
|
|
200
|
+
namespace: str | None = None,
|
|
201
|
+
) -> list[dict[str, Any]]:
|
|
202
|
+
"""Get all messages for a session.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
session_id: Session ID
|
|
206
|
+
namespace: Optional namespace filter
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
List of message dicts in chronological order
|
|
210
|
+
"""
|
|
211
|
+
conn = self._get_conn()
|
|
212
|
+
try:
|
|
213
|
+
if namespace is None:
|
|
214
|
+
cursor = conn.execute(
|
|
215
|
+
"SELECT role, content, invocation_id FROM messages WHERE session_id = ? AND namespace IS NULL ORDER BY id",
|
|
216
|
+
(session_id,)
|
|
217
|
+
)
|
|
218
|
+
else:
|
|
219
|
+
cursor = conn.execute(
|
|
220
|
+
"SELECT role, content, invocation_id FROM messages WHERE session_id = ? AND namespace = ? ORDER BY id",
|
|
221
|
+
(session_id, namespace)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
result = []
|
|
225
|
+
for row in cursor.fetchall():
|
|
226
|
+
content = row["content"]
|
|
227
|
+
# Try to parse JSON content (may be tool_use etc.)
|
|
228
|
+
if content and content.startswith("["):
|
|
229
|
+
try:
|
|
230
|
+
content = json.loads(content)
|
|
231
|
+
except json.JSONDecodeError:
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
result.append({
|
|
235
|
+
"role": row["role"],
|
|
236
|
+
"content": content,
|
|
237
|
+
"invocation_id": row["invocation_id"],
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
return result
|
|
241
|
+
finally:
|
|
242
|
+
conn.close()
|
|
243
|
+
|
|
244
|
+
async def clear_messages(self, session_id: str) -> int:
|
|
245
|
+
"""Clear all messages for a session.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Number of messages deleted
|
|
249
|
+
"""
|
|
250
|
+
conn = self._get_conn()
|
|
251
|
+
try:
|
|
252
|
+
cursor = conn.execute(
|
|
253
|
+
"DELETE FROM messages WHERE session_id = ?",
|
|
254
|
+
(session_id,)
|
|
255
|
+
)
|
|
256
|
+
conn.commit()
|
|
257
|
+
return cursor.rowcount
|
|
258
|
+
finally:
|
|
259
|
+
conn.close()
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
__all__ = ["SQLiteStateBackend"]
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""State backend types and protocols.
|
|
2
|
+
|
|
3
|
+
StateBackend is a simplified key-value storage for generic state.
|
|
4
|
+
For specific data types, use dedicated backends:
|
|
5
|
+
- SessionBackend: Session management
|
|
6
|
+
- InvocationBackend: Invocation management
|
|
7
|
+
- MessageBackend: Message storage
|
|
8
|
+
- MemoryBackend: Long-term memory
|
|
9
|
+
- ArtifactBackend: File/artifact storage
|
|
10
|
+
|
|
11
|
+
Architecture:
|
|
12
|
+
- StateStore: Low-level storage protocol (implemented by API layer)
|
|
13
|
+
- StateBackend: High-level interface (used by framework, wraps StateStore)
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any, Protocol, runtime_checkable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@runtime_checkable
|
|
21
|
+
class StateStore(Protocol):
|
|
22
|
+
"""Low-level storage protocol for state persistence.
|
|
23
|
+
|
|
24
|
+
This is the interface that API/application layer implements.
|
|
25
|
+
Provides raw key-value operations without namespace logic.
|
|
26
|
+
|
|
27
|
+
Example implementations:
|
|
28
|
+
- SQLAlchemyStateStore: Uses State table
|
|
29
|
+
- RedisStateStore: Uses Redis
|
|
30
|
+
- MemoryStateStore: In-memory dict
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
async def get(self, key: str) -> Any | None:
|
|
34
|
+
"""Get value by key."""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
async def set(self, key: str, value: Any) -> None:
|
|
38
|
+
"""Set value by key."""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
async def delete(self, key: str) -> bool:
|
|
42
|
+
"""Delete value by key. Returns True if deleted."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
async def list(self, prefix: str = "") -> list[str]:
|
|
46
|
+
"""List keys with optional prefix filter."""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
async def exists(self, key: str) -> bool:
|
|
50
|
+
"""Check if key exists."""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@runtime_checkable
|
|
55
|
+
class StateBackend(Protocol):
|
|
56
|
+
"""Protocol for generic key-value state storage.
|
|
57
|
+
|
|
58
|
+
Provides simple key-value storage with namespace isolation.
|
|
59
|
+
Use for storing arbitrary state that doesn't fit other backends.
|
|
60
|
+
|
|
61
|
+
Example namespaces:
|
|
62
|
+
- "plan" - Plan state
|
|
63
|
+
- "config" - Configuration
|
|
64
|
+
- "cache" - Temporary cache
|
|
65
|
+
- "workflow" - Workflow state
|
|
66
|
+
|
|
67
|
+
Example usage:
|
|
68
|
+
# Store plan state
|
|
69
|
+
await backend.set("plan", "sess_123:plan", {"status": "active"})
|
|
70
|
+
|
|
71
|
+
# Get plan state
|
|
72
|
+
plan = await backend.get("plan", "sess_123:plan")
|
|
73
|
+
|
|
74
|
+
# List all plans
|
|
75
|
+
keys = await backend.list("plan")
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
async def get(self, namespace: str, key: str) -> Any | None:
|
|
79
|
+
"""Get value by key.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
namespace: Namespace for isolation
|
|
83
|
+
key: Storage key
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Stored value or None if not found
|
|
87
|
+
"""
|
|
88
|
+
...
|
|
89
|
+
|
|
90
|
+
async def set(self, namespace: str, key: str, value: Any) -> None:
|
|
91
|
+
"""Set value by key.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
namespace: Namespace for isolation
|
|
95
|
+
key: Storage key
|
|
96
|
+
value: Value to store (must be JSON-serializable)
|
|
97
|
+
"""
|
|
98
|
+
...
|
|
99
|
+
|
|
100
|
+
async def delete(self, namespace: str, key: str) -> bool:
|
|
101
|
+
"""Delete value by key.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
namespace: Namespace
|
|
105
|
+
key: Storage key
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
True if deleted, False if not found
|
|
109
|
+
"""
|
|
110
|
+
...
|
|
111
|
+
|
|
112
|
+
async def list(self, namespace: str, prefix: str = "") -> list[str]:
|
|
113
|
+
"""List keys with optional prefix filter.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
namespace: Namespace
|
|
117
|
+
prefix: Optional key prefix filter
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
List of matching keys
|
|
121
|
+
"""
|
|
122
|
+
...
|
|
123
|
+
|
|
124
|
+
async def exists(self, namespace: str, key: str) -> bool:
|
|
125
|
+
"""Check if key exists.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
namespace: Namespace
|
|
129
|
+
key: Storage key
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
True if exists
|
|
133
|
+
"""
|
|
134
|
+
...
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class StoreBasedStateBackend:
|
|
138
|
+
"""StateBackend implementation that wraps a StateStore.
|
|
139
|
+
|
|
140
|
+
This allows API layer to implement only the simple StateStore interface,
|
|
141
|
+
while the framework provides namespace handling.
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
# API layer implements StateStore
|
|
145
|
+
store = SQLAlchemyStateStore(session_factory, tenant_id)
|
|
146
|
+
|
|
147
|
+
# Framework wraps it as StateBackend
|
|
148
|
+
backend = StoreBasedStateBackend(store)
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def __init__(self, store: StateStore):
|
|
152
|
+
self._store = store
|
|
153
|
+
|
|
154
|
+
def _make_key(self, namespace: str, key: str) -> str:
|
|
155
|
+
"""Combine namespace and key."""
|
|
156
|
+
return f"{namespace}:{key}"
|
|
157
|
+
|
|
158
|
+
async def get(self, namespace: str, key: str) -> Any | None:
|
|
159
|
+
return await self._store.get(self._make_key(namespace, key))
|
|
160
|
+
|
|
161
|
+
async def set(self, namespace: str, key: str, value: Any) -> None:
|
|
162
|
+
await self._store.set(self._make_key(namespace, key), value)
|
|
163
|
+
|
|
164
|
+
async def delete(self, namespace: str, key: str) -> bool:
|
|
165
|
+
return await self._store.delete(self._make_key(namespace, key))
|
|
166
|
+
|
|
167
|
+
async def list(self, namespace: str, prefix: str = "") -> list[str]:
|
|
168
|
+
full_prefix = self._make_key(namespace, prefix)
|
|
169
|
+
keys = await self._store.list(full_prefix)
|
|
170
|
+
# Strip namespace prefix from results
|
|
171
|
+
ns_prefix = f"{namespace}:"
|
|
172
|
+
return [k[len(ns_prefix):] for k in keys if k.startswith(ns_prefix)]
|
|
173
|
+
|
|
174
|
+
async def exists(self, namespace: str, key: str) -> bool:
|
|
175
|
+
return await self._store.exists(self._make_key(namespace, key))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
__all__ = ["StateBackend", "StateStore", "StoreBasedStateBackend"]
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""SubAgent backend for sub-agent registry and retrieval.
|
|
2
|
+
|
|
3
|
+
Supports different agent retrieval strategies:
|
|
4
|
+
- ListSubAgentBackend: Static list of agents
|
|
5
|
+
- Custom backends: DB, API, dynamic discovery
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any, Literal, Protocol, TYPE_CHECKING, runtime_checkable
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from ...core.base import BaseAgent
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class AgentConfig:
|
|
18
|
+
"""Configuration for a sub-agent.
|
|
19
|
+
|
|
20
|
+
行为配置:
|
|
21
|
+
- inherit_messages: 是否继承父 agent 的消息历史
|
|
22
|
+
- return_to_parent: 完成后是否返回结果给父 agent
|
|
23
|
+
- create_invocation: 是否创建新 invocation(用户可直接交互)
|
|
24
|
+
|
|
25
|
+
状态配置:
|
|
26
|
+
- inherit_state: 继承哪些状态 ("all", "none", 或具体 key 列表)
|
|
27
|
+
- return_state_keys: 返回哪些状态给父 agent
|
|
28
|
+
- summary_mode: 返回摘要模式 ("full", "truncate", "none")
|
|
29
|
+
|
|
30
|
+
手动切换配置:
|
|
31
|
+
- switchable: 是否出现在前端可切换列表
|
|
32
|
+
- display_name: 前端显示名称
|
|
33
|
+
|
|
34
|
+
超时配置:
|
|
35
|
+
- timeout: 执行超时时间(秒),收到事件时会刷新
|
|
36
|
+
"""
|
|
37
|
+
key: str
|
|
38
|
+
agent: Any # BaseAgent instance or class
|
|
39
|
+
description: str = ""
|
|
40
|
+
|
|
41
|
+
# 行为配置(替代原 mode)
|
|
42
|
+
inherit_messages: bool = False
|
|
43
|
+
return_to_parent: bool = True
|
|
44
|
+
create_invocation: bool = False
|
|
45
|
+
|
|
46
|
+
# 状态配置
|
|
47
|
+
inherit_state: Literal["all", "none"] | list[str] = "none"
|
|
48
|
+
return_state_keys: list[str] = field(default_factory=list)
|
|
49
|
+
summary_mode: Literal["full", "truncate", "none"] = "full"
|
|
50
|
+
|
|
51
|
+
# 手动切换配置
|
|
52
|
+
switchable: bool = False
|
|
53
|
+
display_name: str = ""
|
|
54
|
+
|
|
55
|
+
# 超时配置(默认 5 分钟,收到事件时刷新)
|
|
56
|
+
timeout: float = 300.0
|
|
57
|
+
|
|
58
|
+
# 消息记录配置(可选覆盖,默认由 return_to_parent 派生)
|
|
59
|
+
# - return_to_parent=True → 不记录(消息归并到父级结果)
|
|
60
|
+
# - return_to_parent=False → 记录到独立 namespace
|
|
61
|
+
_record_messages: bool | None = None # None = 用默认派生逻辑
|
|
62
|
+
|
|
63
|
+
# Permission config (optional override)
|
|
64
|
+
permission_config: Any | None = None
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def record_messages(self) -> bool:
|
|
68
|
+
"""是否记录消息 - 默认由 return_to_parent 派生.
|
|
69
|
+
|
|
70
|
+
- return_to_parent=True → 不记录(结果归并到父级)
|
|
71
|
+
- return_to_parent=False → 记录(独立运行)
|
|
72
|
+
"""
|
|
73
|
+
if self._record_messages is not None:
|
|
74
|
+
return self._record_messages
|
|
75
|
+
return not self.return_to_parent
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def mode(self) -> Literal["embedded", "delegated"]:
|
|
79
|
+
"""兼容属性:根据 create_invocation 返回 mode."""
|
|
80
|
+
return "delegated" if self.create_invocation else "embedded"
|
|
81
|
+
|
|
82
|
+
def to_dict(self) -> dict:
|
|
83
|
+
return {
|
|
84
|
+
"key": self.key,
|
|
85
|
+
"agent_name": getattr(self.agent, "name", str(self.agent)),
|
|
86
|
+
"mode": self.mode,
|
|
87
|
+
"description": self.description or getattr(self.agent, "description", ""),
|
|
88
|
+
"inherit_messages": self.inherit_messages,
|
|
89
|
+
"return_to_parent": self.return_to_parent,
|
|
90
|
+
"create_invocation": self.create_invocation,
|
|
91
|
+
"switchable": self.switchable,
|
|
92
|
+
"display_name": self.display_name,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@runtime_checkable
|
|
97
|
+
class SubAgentBackend(Protocol):
|
|
98
|
+
"""Protocol for sub-agent retrieval."""
|
|
99
|
+
|
|
100
|
+
async def get(self, key: str) -> AgentConfig | None:
|
|
101
|
+
"""Get agent config by key."""
|
|
102
|
+
...
|
|
103
|
+
|
|
104
|
+
async def list(self) -> list[AgentConfig]:
|
|
105
|
+
"""List all available agents."""
|
|
106
|
+
...
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ListSubAgentBackend:
|
|
110
|
+
"""Default implementation: Static list of agents."""
|
|
111
|
+
|
|
112
|
+
def __init__(self, agents: list[AgentConfig] | None = None):
|
|
113
|
+
self._agents: dict[str, AgentConfig] = {}
|
|
114
|
+
if agents:
|
|
115
|
+
for config in agents:
|
|
116
|
+
self._agents[config.key] = config
|
|
117
|
+
|
|
118
|
+
def register(self, config: AgentConfig) -> None:
|
|
119
|
+
"""Register an agent config."""
|
|
120
|
+
self._agents[config.key] = config
|
|
121
|
+
|
|
122
|
+
def register_agent(
|
|
123
|
+
self,
|
|
124
|
+
key: str,
|
|
125
|
+
agent: Any,
|
|
126
|
+
description: str = "",
|
|
127
|
+
*,
|
|
128
|
+
inherit_messages: bool = False,
|
|
129
|
+
return_to_parent: bool = True,
|
|
130
|
+
create_invocation: bool = False,
|
|
131
|
+
switchable: bool = False,
|
|
132
|
+
display_name: str = "",
|
|
133
|
+
) -> None:
|
|
134
|
+
"""Convenience method to register an agent."""
|
|
135
|
+
self._agents[key] = AgentConfig(
|
|
136
|
+
key=key,
|
|
137
|
+
agent=agent,
|
|
138
|
+
description=description or getattr(agent, "description", ""),
|
|
139
|
+
inherit_messages=inherit_messages,
|
|
140
|
+
return_to_parent=return_to_parent,
|
|
141
|
+
create_invocation=create_invocation,
|
|
142
|
+
switchable=switchable,
|
|
143
|
+
display_name=display_name,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
async def get(self, key: str) -> AgentConfig | None:
|
|
147
|
+
return self._agents.get(key)
|
|
148
|
+
|
|
149
|
+
async def list(self) -> list[AgentConfig]:
|
|
150
|
+
return list(self._agents.values())
|
|
151
|
+
|
|
152
|
+
def list_sync(self) -> list[AgentConfig]:
|
|
153
|
+
"""Synchronous version of list() for use in property getters."""
|
|
154
|
+
return list(self._agents.values())
|
|
155
|
+
|
|
156
|
+
def clear(self) -> None:
|
|
157
|
+
"""Clear all agents."""
|
|
158
|
+
self._agents.clear()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
__all__ = [
|
|
162
|
+
"SubAgentBackend",
|
|
163
|
+
"AgentConfig",
|
|
164
|
+
"ListSubAgentBackend",
|
|
165
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Aury Agent CLI - 命令行界面
|
|
3
|
+
===========================
|
|
4
|
+
|
|
5
|
+
提供命令行工具用于:
|
|
6
|
+
- 交互式对话 (chat)
|
|
7
|
+
- 运行 Workflow (workflow)
|
|
8
|
+
- 管理会话 (session)
|
|
9
|
+
- 配置管理 (config)
|
|
10
|
+
|
|
11
|
+
扩展支持:
|
|
12
|
+
- 注册自定义 Agent
|
|
13
|
+
- 注册自定义 Tool
|
|
14
|
+
- 从配置文件加载扩展
|
|
15
|
+
- 从 entry points 加载扩展
|
|
16
|
+
"""
|
|
17
|
+
from aury.agents.cli.main import app, main
|
|
18
|
+
from aury.agents.cli.extensions import (
|
|
19
|
+
ExtensionRegistry,
|
|
20
|
+
ExtensionInfo,
|
|
21
|
+
registry,
|
|
22
|
+
register_agent,
|
|
23
|
+
register_tool,
|
|
24
|
+
get_agent,
|
|
25
|
+
get_tool,
|
|
26
|
+
auto_discover,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"app",
|
|
31
|
+
"main",
|
|
32
|
+
# Extensions
|
|
33
|
+
"ExtensionRegistry",
|
|
34
|
+
"ExtensionInfo",
|
|
35
|
+
"registry",
|
|
36
|
+
"register_agent",
|
|
37
|
+
"register_tool",
|
|
38
|
+
"get_agent",
|
|
39
|
+
"get_tool",
|
|
40
|
+
"auto_discover",
|
|
41
|
+
]
|