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,153 @@
|
|
|
1
|
+
"""RawMessageMiddleware - stores complete messages for HITL recovery.
|
|
2
|
+
|
|
3
|
+
This middleware stores complete, untruncated messages to RawMessageStore.
|
|
4
|
+
Works alongside MessageBackendMiddleware which stores truncated messages.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
raw_store = InMemoryRawMessageStore()
|
|
8
|
+
raw_middleware = RawMessageMiddleware(raw_store, persist_raw=False)
|
|
9
|
+
|
|
10
|
+
agent = ReactAgent.create(
|
|
11
|
+
llm=llm,
|
|
12
|
+
middlewares=[raw_middleware, MessageBackendMiddleware()],
|
|
13
|
+
)
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
|
+
|
|
19
|
+
from .base import BaseMiddleware
|
|
20
|
+
from .types import HookResult
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from ..messages.raw_store import RawMessageStore
|
|
24
|
+
from ..core.state import State
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RawMessageMiddleware(BaseMiddleware):
|
|
28
|
+
"""Middleware that stores complete messages to RawMessageStore.
|
|
29
|
+
|
|
30
|
+
Stores untruncated messages for:
|
|
31
|
+
- HITL recovery (restore exact conversation state)
|
|
32
|
+
- Full-context recall (when truncated history is insufficient)
|
|
33
|
+
|
|
34
|
+
Messages are stored per invocation and can be cleaned up when
|
|
35
|
+
the invocation completes (controlled by persist_raw).
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
raw_store: "RawMessageStore",
|
|
41
|
+
persist_raw: bool = False,
|
|
42
|
+
state: "State | None" = None,
|
|
43
|
+
):
|
|
44
|
+
"""Initialize with RawMessageStore.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
raw_store: RawMessageStore for storing complete messages
|
|
48
|
+
persist_raw: Whether to keep messages after invocation completes.
|
|
49
|
+
False = clean up after invocation (default)
|
|
50
|
+
True = keep forever (for audit/recall)
|
|
51
|
+
state: State instance for storing message_ids in execution namespace.
|
|
52
|
+
If provided, message IDs are automatically added to
|
|
53
|
+
state.execution["message_ids"].
|
|
54
|
+
"""
|
|
55
|
+
self.raw_store = raw_store
|
|
56
|
+
self.persist_raw = persist_raw
|
|
57
|
+
self.state = state
|
|
58
|
+
|
|
59
|
+
# Track message IDs per invocation (for cleanup)
|
|
60
|
+
self._invocation_msg_ids: dict[str, list[str]] = {}
|
|
61
|
+
|
|
62
|
+
def set_state(self, state: "State") -> None:
|
|
63
|
+
"""Set state instance (can be set after construction).
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
state: State instance for storing message_ids
|
|
67
|
+
"""
|
|
68
|
+
self.state = state
|
|
69
|
+
|
|
70
|
+
async def on_message_save(
|
|
71
|
+
self,
|
|
72
|
+
message: dict[str, Any],
|
|
73
|
+
context: dict[str, Any],
|
|
74
|
+
) -> dict[str, Any] | None:
|
|
75
|
+
"""Store complete message to RawMessageStore.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
message: Complete message dict with 'role', 'content', etc.
|
|
79
|
+
context: Execution context with 'invocation_id', etc.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The message with added 'raw_msg_id' field
|
|
83
|
+
"""
|
|
84
|
+
invocation_id = context.get("invocation_id", "")
|
|
85
|
+
if not invocation_id:
|
|
86
|
+
return message
|
|
87
|
+
|
|
88
|
+
# Store to raw store
|
|
89
|
+
msg_id = await self.raw_store.add(invocation_id, message)
|
|
90
|
+
|
|
91
|
+
# Track for cleanup
|
|
92
|
+
if invocation_id not in self._invocation_msg_ids:
|
|
93
|
+
self._invocation_msg_ids[invocation_id] = []
|
|
94
|
+
self._invocation_msg_ids[invocation_id].append(msg_id)
|
|
95
|
+
|
|
96
|
+
# Add to state.execution["message_ids"] if state is available
|
|
97
|
+
if self.state:
|
|
98
|
+
message_ids = self.state.execution.get("message_ids", [])
|
|
99
|
+
message_ids.append(msg_id)
|
|
100
|
+
self.state.execution["message_ids"] = message_ids
|
|
101
|
+
|
|
102
|
+
# Add msg_id to message for downstream middlewares
|
|
103
|
+
message["raw_msg_id"] = msg_id
|
|
104
|
+
|
|
105
|
+
return message
|
|
106
|
+
|
|
107
|
+
async def on_agent_end(
|
|
108
|
+
self,
|
|
109
|
+
agent_id: str,
|
|
110
|
+
result: Any,
|
|
111
|
+
context: dict[str, Any],
|
|
112
|
+
) -> HookResult:
|
|
113
|
+
"""Clean up raw messages when invocation completes.
|
|
114
|
+
|
|
115
|
+
Only cleans up if persist_raw is False.
|
|
116
|
+
"""
|
|
117
|
+
if self.persist_raw:
|
|
118
|
+
return HookResult.proceed()
|
|
119
|
+
|
|
120
|
+
invocation_id = context.get("invocation_id", "")
|
|
121
|
+
if invocation_id:
|
|
122
|
+
await self._cleanup_invocation(invocation_id)
|
|
123
|
+
|
|
124
|
+
return HookResult.proceed()
|
|
125
|
+
|
|
126
|
+
async def _cleanup_invocation(self, invocation_id: str) -> int:
|
|
127
|
+
"""Clean up raw messages for an invocation.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
invocation_id: Invocation ID to clean up
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Number of messages deleted
|
|
134
|
+
"""
|
|
135
|
+
# Remove from tracking
|
|
136
|
+
self._invocation_msg_ids.pop(invocation_id, None)
|
|
137
|
+
|
|
138
|
+
# Delete from store
|
|
139
|
+
return await self.raw_store.delete_by_invocation(invocation_id)
|
|
140
|
+
|
|
141
|
+
def get_message_ids(self, invocation_id: str) -> list[str]:
|
|
142
|
+
"""Get tracked message IDs for an invocation.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
invocation_id: Invocation ID
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
List of message IDs
|
|
149
|
+
"""
|
|
150
|
+
return self._invocation_msg_ids.get(invocation_id, []).copy()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
__all__ = ["RawMessageMiddleware"]
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""MessageTruncationMiddleware - truncates large message content before persistence.
|
|
2
|
+
|
|
3
|
+
This middleware intercepts on_message_save and truncates content
|
|
4
|
+
that exceeds configured limits, ensuring historical messages don't
|
|
5
|
+
consume excessive storage.
|
|
6
|
+
|
|
7
|
+
Current invocation messages remain complete in State (for recovery),
|
|
8
|
+
only persisted messages get truncated.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from .base import BaseMiddleware
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MessageTruncationMiddleware(BaseMiddleware):
|
|
18
|
+
"""Truncates large message content before persistence.
|
|
19
|
+
|
|
20
|
+
Use this middleware BEFORE MessageBackendMiddleware in the chain:
|
|
21
|
+
|
|
22
|
+
middleware = MiddlewareChain([
|
|
23
|
+
MessageTruncationMiddleware(max_content_length=2000),
|
|
24
|
+
MessageBackendMiddleware(),
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
Features:
|
|
28
|
+
- Truncates string content exceeding max_content_length
|
|
29
|
+
- Truncates individual items in list content (tool_use, tool_result)
|
|
30
|
+
- Adds "[truncated]" marker to truncated content
|
|
31
|
+
- Configurable truncation threshold
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
max_content_length: int = 2000,
|
|
37
|
+
truncate_marker: str = "... [truncated]",
|
|
38
|
+
):
|
|
39
|
+
"""Initialize MessageTruncationMiddleware.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
max_content_length: Maximum content length before truncation
|
|
43
|
+
truncate_marker: Marker appended to truncated content
|
|
44
|
+
"""
|
|
45
|
+
self.max_content_length = max_content_length
|
|
46
|
+
self.truncate_marker = truncate_marker
|
|
47
|
+
|
|
48
|
+
async def on_message_save(
|
|
49
|
+
self,
|
|
50
|
+
message: dict[str, Any],
|
|
51
|
+
context: dict[str, Any],
|
|
52
|
+
) -> dict[str, Any] | None:
|
|
53
|
+
"""Truncate message content before saving.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
message: Message dict with 'role', 'content', etc.
|
|
57
|
+
context: Execution context
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Modified message with truncated content
|
|
61
|
+
"""
|
|
62
|
+
content = message.get("content")
|
|
63
|
+
if content is None:
|
|
64
|
+
return message
|
|
65
|
+
|
|
66
|
+
# Create a copy to avoid mutating original
|
|
67
|
+
truncated_message = message.copy()
|
|
68
|
+
truncated_message["content"] = self._truncate_content(content)
|
|
69
|
+
|
|
70
|
+
return truncated_message
|
|
71
|
+
|
|
72
|
+
def _truncate_content(self, content: Any) -> Any:
|
|
73
|
+
"""Truncate content based on type.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
content: String or list content
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Truncated content
|
|
80
|
+
"""
|
|
81
|
+
if isinstance(content, str):
|
|
82
|
+
return self._truncate_string(content)
|
|
83
|
+
|
|
84
|
+
if isinstance(content, list):
|
|
85
|
+
return self._truncate_list(content)
|
|
86
|
+
|
|
87
|
+
# Unknown type, return as-is
|
|
88
|
+
return content
|
|
89
|
+
|
|
90
|
+
def _truncate_string(self, text: str) -> str:
|
|
91
|
+
"""Truncate string content."""
|
|
92
|
+
if len(text) <= self.max_content_length:
|
|
93
|
+
return text
|
|
94
|
+
|
|
95
|
+
# Keep first part, add marker
|
|
96
|
+
return text[:self.max_content_length] + self.truncate_marker
|
|
97
|
+
|
|
98
|
+
def _truncate_list(self, items: list[Any]) -> list[Any]:
|
|
99
|
+
"""Truncate list content (tool_use, tool_result, etc.)."""
|
|
100
|
+
truncated_items = []
|
|
101
|
+
|
|
102
|
+
for item in items:
|
|
103
|
+
if isinstance(item, dict):
|
|
104
|
+
truncated_item = item.copy()
|
|
105
|
+
|
|
106
|
+
# Truncate text content
|
|
107
|
+
if "text" in truncated_item and isinstance(truncated_item["text"], str):
|
|
108
|
+
truncated_item["text"] = self._truncate_string(truncated_item["text"])
|
|
109
|
+
|
|
110
|
+
# Truncate tool_result content
|
|
111
|
+
if "content" in truncated_item and isinstance(truncated_item["content"], str):
|
|
112
|
+
truncated_item["content"] = self._truncate_string(truncated_item["content"])
|
|
113
|
+
|
|
114
|
+
# Truncate tool_use input (arguments)
|
|
115
|
+
if "input" in truncated_item and isinstance(truncated_item["input"], dict):
|
|
116
|
+
truncated_item["input"] = self._truncate_dict(truncated_item["input"])
|
|
117
|
+
|
|
118
|
+
truncated_items.append(truncated_item)
|
|
119
|
+
else:
|
|
120
|
+
truncated_items.append(item)
|
|
121
|
+
|
|
122
|
+
return truncated_items
|
|
123
|
+
|
|
124
|
+
def _truncate_dict(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
125
|
+
"""Truncate string values in a dict (for tool arguments)."""
|
|
126
|
+
truncated = {}
|
|
127
|
+
for key, value in data.items():
|
|
128
|
+
if isinstance(value, str):
|
|
129
|
+
truncated[key] = self._truncate_string(value)
|
|
130
|
+
elif isinstance(value, dict):
|
|
131
|
+
truncated[key] = self._truncate_dict(value)
|
|
132
|
+
elif isinstance(value, list):
|
|
133
|
+
truncated[key] = self._truncate_list(value)
|
|
134
|
+
else:
|
|
135
|
+
truncated[key] = value
|
|
136
|
+
return truncated
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
__all__ = ["MessageTruncationMiddleware"]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Middleware types and data classes."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TriggerMode(Enum):
|
|
10
|
+
"""Middleware trigger mode for streaming."""
|
|
11
|
+
EVERY_TOKEN = "every_token" # Trigger on every token
|
|
12
|
+
EVERY_N_TOKENS = "every_n_tokens" # Trigger every N tokens
|
|
13
|
+
ON_BOUNDARY = "on_boundary" # Trigger on sentence/paragraph boundaries
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HookAction(Enum):
|
|
17
|
+
"""Control flow action returned by lifecycle hooks.
|
|
18
|
+
|
|
19
|
+
CONTINUE: Proceed with normal execution
|
|
20
|
+
SKIP: Skip the current operation (tool call, etc.)
|
|
21
|
+
RETRY: Retry the current operation (with modified params)
|
|
22
|
+
STOP: Stop the agent execution entirely
|
|
23
|
+
"""
|
|
24
|
+
CONTINUE = "continue"
|
|
25
|
+
SKIP = "skip"
|
|
26
|
+
RETRY = "retry"
|
|
27
|
+
STOP = "stop"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class HookResult:
|
|
32
|
+
"""Result from a lifecycle hook.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
action: Control flow action
|
|
36
|
+
modified_data: Modified data (for RETRY action)
|
|
37
|
+
message: Optional message explaining the action
|
|
38
|
+
metadata: Additional metadata
|
|
39
|
+
"""
|
|
40
|
+
action: HookAction = HookAction.CONTINUE
|
|
41
|
+
modified_data: Any = None
|
|
42
|
+
message: str | None = None
|
|
43
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def proceed(cls) -> "HookResult":
|
|
47
|
+
"""Continue with normal execution."""
|
|
48
|
+
return cls(action=HookAction.CONTINUE)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def skip(cls, message: str | None = None) -> "HookResult":
|
|
52
|
+
"""Skip the current operation."""
|
|
53
|
+
return cls(action=HookAction.SKIP, message=message)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def retry(cls, modified_data: Any = None, message: str | None = None) -> "HookResult":
|
|
57
|
+
"""Retry with optional modified data."""
|
|
58
|
+
return cls(action=HookAction.RETRY, modified_data=modified_data, message=message)
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def stop(cls, message: str | None = None) -> "HookResult":
|
|
62
|
+
"""Stop agent execution."""
|
|
63
|
+
return cls(action=HookAction.STOP, message=message)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class MiddlewareConfig:
|
|
68
|
+
"""Middleware configuration."""
|
|
69
|
+
trigger_mode: TriggerMode = TriggerMode.EVERY_TOKEN
|
|
70
|
+
trigger_n: int = 10 # For EVERY_N_TOKENS mode
|
|
71
|
+
async_mode: bool = True # Execute asynchronously
|
|
72
|
+
priority: int = 0 # Lower priority runs first
|
|
73
|
+
inherit: bool = True # Whether to pass to sub-agents (can be overridden at use() time)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
__all__ = [
|
|
77
|
+
"TriggerMode",
|
|
78
|
+
"HookAction",
|
|
79
|
+
"HookResult",
|
|
80
|
+
"MiddlewareConfig",
|
|
81
|
+
]
|
aury/agents/plugin.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Plugin system for packaging providers, middlewares and tools.
|
|
2
|
+
|
|
3
|
+
The Plugin class provides an optional assembly layer that packages:
|
|
4
|
+
- ContextProviders (context sources)
|
|
5
|
+
- Middlewares (execution hooks)
|
|
6
|
+
- Tools, Skills, SubAgents (direct contributions)
|
|
7
|
+
|
|
8
|
+
Design Goals:
|
|
9
|
+
1. Simple case: Use plugins for packaged functionality
|
|
10
|
+
2. Flexible case: Use providers/middlewares directly
|
|
11
|
+
3. Mixed case: Combine plugins with custom providers/middlewares
|
|
12
|
+
|
|
13
|
+
Example Usage:
|
|
14
|
+
# Simple: use plugins
|
|
15
|
+
agent = ReactAgent.create(
|
|
16
|
+
llm=llm,
|
|
17
|
+
plugins=[CodePlugin()],
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Flexible: use providers/middlewares directly
|
|
21
|
+
agent = ReactAgent.create(
|
|
22
|
+
llm=llm,
|
|
23
|
+
context_providers=[MyCustomProvider()],
|
|
24
|
+
middlewares=[MyMiddleware()],
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Mixed + custom
|
|
28
|
+
agent = ReactAgent.create(
|
|
29
|
+
llm=llm,
|
|
30
|
+
plugins=[CodePlugin()],
|
|
31
|
+
context_providers=[MyRAGProvider()],
|
|
32
|
+
subagents=[researcher_config],
|
|
33
|
+
delegate_tool_class=MyDelegateTool,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
Inheritance Rules:
|
|
37
|
+
- Plugin.inherit: Whether this plugin should be inherited by sub-agents
|
|
38
|
+
- AgentConfig.inherit_plugins: Whether to inherit plugins from parent
|
|
39
|
+
- Both must be True for inheritance to occur
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
from dataclasses import dataclass, field
|
|
45
|
+
from typing import TYPE_CHECKING
|
|
46
|
+
|
|
47
|
+
if TYPE_CHECKING:
|
|
48
|
+
from .core.types.tool import BaseTool
|
|
49
|
+
from .skill import Skill
|
|
50
|
+
from .backends.subagent import AgentConfig as SubAgentConfig
|
|
51
|
+
from .context_providers.base import ContextProvider
|
|
52
|
+
from .middleware.base import Middleware
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# =============================================================================
|
|
56
|
+
# Plugin class
|
|
57
|
+
# =============================================================================
|
|
58
|
+
|
|
59
|
+
# @dataclass
|
|
60
|
+
# class Plugin:
|
|
61
|
+
# """Plugin packages providers, middlewares, tools, skills, and subagents.
|
|
62
|
+
#
|
|
63
|
+
# Attributes:
|
|
64
|
+
# name: Plugin identifier
|
|
65
|
+
# priority: Execution order (lower runs first, default 100)
|
|
66
|
+
# inherit: Whether this plugin should be inherited by sub-agents
|
|
67
|
+
# tools: Tools provided by this plugin
|
|
68
|
+
# skills: Skills provided by this plugin
|
|
69
|
+
# subagents: SubAgent configs provided by this plugin
|
|
70
|
+
# providers: ContextProviders provided by this plugin
|
|
71
|
+
# middlewares: Middlewares provided by this plugin
|
|
72
|
+
#
|
|
73
|
+
# Priority Guidelines:
|
|
74
|
+
# 0-50: Core/framework plugins (run first)
|
|
75
|
+
# 50-100: Standard plugins
|
|
76
|
+
# 100+: User plugins (run last)
|
|
77
|
+
#
|
|
78
|
+
# Example:
|
|
79
|
+
# @dataclass
|
|
80
|
+
# class CodePlugin(Plugin):
|
|
81
|
+
# name: str = "code"
|
|
82
|
+
# priority: int = 50
|
|
83
|
+
# inherit: bool = True
|
|
84
|
+
#
|
|
85
|
+
# def __post_init__(self):
|
|
86
|
+
# self.tools = [
|
|
87
|
+
# FileReadTool(),
|
|
88
|
+
# FileWriteTool(),
|
|
89
|
+
# ShellTool(),
|
|
90
|
+
# ]
|
|
91
|
+
# self.providers = [
|
|
92
|
+
# ProjectContextProvider(),
|
|
93
|
+
# ]
|
|
94
|
+
# self.middlewares = [
|
|
95
|
+
# CodeReviewMiddleware(),
|
|
96
|
+
# ]
|
|
97
|
+
# """
|
|
98
|
+
#
|
|
99
|
+
# name: str
|
|
100
|
+
# priority: int = 100
|
|
101
|
+
# inherit: bool = False
|
|
102
|
+
#
|
|
103
|
+
# tools: list["BaseTool"] = field(default_factory=list)
|
|
104
|
+
# skills: list["Skill"] = field(default_factory=list)
|
|
105
|
+
# subagents: list["SubAgentConfig"] = field(default_factory=list)
|
|
106
|
+
# providers: list["ContextProvider"] = field(default_factory=list)
|
|
107
|
+
# middlewares: list["Middleware"] = field(default_factory=list)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# =============================================================================
|
|
111
|
+
# PluginChain for managing multiple plugins
|
|
112
|
+
# =============================================================================
|
|
113
|
+
|
|
114
|
+
# class PluginChain:
|
|
115
|
+
# """Chain of plugins sorted by priority.
|
|
116
|
+
#
|
|
117
|
+
# Provides methods to:
|
|
118
|
+
# - Collect all tools from plugins
|
|
119
|
+
# - Collect all providers from plugins
|
|
120
|
+
# - Collect all middlewares from plugins
|
|
121
|
+
# - Get inheritable plugins for sub-agents
|
|
122
|
+
# """
|
|
123
|
+
#
|
|
124
|
+
# def __init__(self, plugins: list[Plugin] | None = None):
|
|
125
|
+
# self._plugins: list[Plugin] = []
|
|
126
|
+
# if plugins:
|
|
127
|
+
# for p in plugins:
|
|
128
|
+
# self.add(p)
|
|
129
|
+
#
|
|
130
|
+
# def add(self, plugin: Plugin) -> "PluginChain":
|
|
131
|
+
# """Add plugin and maintain sorted order by priority."""
|
|
132
|
+
# self._plugins.append(plugin)
|
|
133
|
+
# self._plugins.sort(key=lambda p: p.priority)
|
|
134
|
+
# return self
|
|
135
|
+
#
|
|
136
|
+
# def collect_tools(self) -> list["BaseTool"]:
|
|
137
|
+
# """Collect all tools from all plugins."""
|
|
138
|
+
# tools = []
|
|
139
|
+
# for p in self._plugins:
|
|
140
|
+
# tools.extend(p.tools)
|
|
141
|
+
# return tools
|
|
142
|
+
#
|
|
143
|
+
# def collect_providers(self) -> list["ContextProvider"]:
|
|
144
|
+
# """Collect all providers from all plugins."""
|
|
145
|
+
# providers = []
|
|
146
|
+
# for p in self._plugins:
|
|
147
|
+
# providers.extend(p.providers)
|
|
148
|
+
# return providers
|
|
149
|
+
#
|
|
150
|
+
# def collect_middlewares(self) -> list["Middleware"]:
|
|
151
|
+
# """Collect all middlewares from all plugins."""
|
|
152
|
+
# middlewares = []
|
|
153
|
+
# for p in self._plugins:
|
|
154
|
+
# middlewares.extend(p.middlewares)
|
|
155
|
+
# return middlewares
|
|
156
|
+
#
|
|
157
|
+
# def get_inheritable(self) -> list[Plugin]:
|
|
158
|
+
# """Get plugins that should be inherited by sub-agents."""
|
|
159
|
+
# return [p for p in self._plugins if p.inherit]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
__all__: list[str] = [] # Classes are commented out
|