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,53 @@
|
|
|
1
|
+
"""Message service protocol."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from abc import abstractmethod
|
|
5
|
+
from typing import Any, Protocol, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ..types.block import BlockEvent, PersistedBlock
|
|
9
|
+
from ..types.message import Message
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MessageService(Protocol):
|
|
13
|
+
"""Protocol for message/block management.
|
|
14
|
+
|
|
15
|
+
Handles message and block persistence.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
async def append(
|
|
20
|
+
self,
|
|
21
|
+
session_id: str,
|
|
22
|
+
invocation_id: str,
|
|
23
|
+
block: "BlockEvent",
|
|
24
|
+
) -> "PersistedBlock":
|
|
25
|
+
"""Append a block to the message history."""
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
async def list_blocks(
|
|
30
|
+
self,
|
|
31
|
+
session_id: str,
|
|
32
|
+
invocation_id: str | None = None,
|
|
33
|
+
limit: int = 100,
|
|
34
|
+
) -> list["PersistedBlock"]:
|
|
35
|
+
"""List blocks for a session/invocation."""
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
async def update_block(
|
|
40
|
+
self,
|
|
41
|
+
block_id: str,
|
|
42
|
+
data: dict[str, Any],
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Update a block's data."""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
async def get_block(self, block_id: str) -> "PersistedBlock | None":
|
|
49
|
+
"""Get a specific block."""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
__all__ = ["MessageService"]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Session service protocol."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from abc import abstractmethod
|
|
5
|
+
from typing import Protocol, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ..types.session import Session, ControlFrame
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SessionService(Protocol):
|
|
12
|
+
"""Protocol for session management.
|
|
13
|
+
|
|
14
|
+
Handles session CRUD and control stack operations.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
async def create(self, root_agent_id: str, **kwargs) -> "Session":
|
|
19
|
+
"""Create a new session."""
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
async def get(self, session_id: str) -> "Session | None":
|
|
24
|
+
"""Get session by ID."""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
async def update(self, session: "Session") -> None:
|
|
29
|
+
"""Update session."""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
async def delete(self, session_id: str) -> bool:
|
|
34
|
+
"""Delete session."""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
async def list(self, limit: int = 100, offset: int = 0) -> list["Session"]:
|
|
39
|
+
"""List sessions."""
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
async def push_control(self, session_id: str, frame: "ControlFrame") -> None:
|
|
44
|
+
"""Push control frame to session's control stack."""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
async def pop_control(self, session_id: str) -> "ControlFrame | None":
|
|
49
|
+
"""Pop control frame from session's control stack."""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
__all__ = ["SessionService"]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Control flow signals for agent execution.
|
|
2
|
+
|
|
3
|
+
These are NOT exceptions - they are control flow signals that inherit from
|
|
4
|
+
BaseException to avoid being caught by generic `except Exception` handlers.
|
|
5
|
+
|
|
6
|
+
Similar to KeyboardInterrupt and SystemExit, these signals control execution
|
|
7
|
+
flow rather than indicate errors.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SuspendSignal(BaseException):
|
|
16
|
+
"""Base class for suspension signals.
|
|
17
|
+
|
|
18
|
+
Signals that execution should be suspended (not failed).
|
|
19
|
+
Inherits from BaseException so `except Exception` won't catch it.
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
try:
|
|
23
|
+
await agent.run()
|
|
24
|
+
except SuspendSignal as s:
|
|
25
|
+
# Handle suspension (HITL, pause, etc.)
|
|
26
|
+
handle_suspend(s)
|
|
27
|
+
except Exception as e:
|
|
28
|
+
# Handle actual errors
|
|
29
|
+
handle_error(e)
|
|
30
|
+
"""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class HITLSuspend(SuspendSignal):
|
|
36
|
+
"""Signal for Human-in-the-Loop suspension.
|
|
37
|
+
|
|
38
|
+
Raised when agent needs human input to continue.
|
|
39
|
+
Contains all information needed to:
|
|
40
|
+
1. Display the request to user
|
|
41
|
+
2. Resume execution after user responds
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
request_id: Unique ID for matching response
|
|
45
|
+
request_type: Type of request (ask_user, confirm, form, etc.)
|
|
46
|
+
message: Display message for user
|
|
47
|
+
options: Optional list of choices
|
|
48
|
+
node_id: Workflow node ID if triggered from workflow
|
|
49
|
+
tool_name: Tool name if triggered from tool
|
|
50
|
+
block_id: Associated UI block ID
|
|
51
|
+
metadata: Additional context
|
|
52
|
+
"""
|
|
53
|
+
request_id: str
|
|
54
|
+
request_type: str = "ask_user"
|
|
55
|
+
message: str | None = None
|
|
56
|
+
options: list[str] | None = None
|
|
57
|
+
node_id: str | None = None
|
|
58
|
+
tool_name: str | None = None
|
|
59
|
+
block_id: str | None = None
|
|
60
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
61
|
+
|
|
62
|
+
def __post_init__(self):
|
|
63
|
+
# Initialize BaseException with a message
|
|
64
|
+
super().__init__(f"HITL suspend: {self.request_type} ({self.request_id})")
|
|
65
|
+
|
|
66
|
+
def to_dict(self) -> dict[str, Any]:
|
|
67
|
+
"""Convert to dictionary for storage/transmission."""
|
|
68
|
+
return {
|
|
69
|
+
"request_id": self.request_id,
|
|
70
|
+
"request_type": self.request_type,
|
|
71
|
+
"message": self.message,
|
|
72
|
+
"options": self.options,
|
|
73
|
+
"node_id": self.node_id,
|
|
74
|
+
"tool_name": self.tool_name,
|
|
75
|
+
"block_id": self.block_id,
|
|
76
|
+
"metadata": self.metadata,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_dict(cls, data: dict[str, Any]) -> "HITLSuspend":
|
|
81
|
+
"""Create from dictionary."""
|
|
82
|
+
return cls(
|
|
83
|
+
request_id=data["request_id"],
|
|
84
|
+
request_type=data.get("request_type", "ask_user"),
|
|
85
|
+
message=data.get("message"),
|
|
86
|
+
options=data.get("options"),
|
|
87
|
+
node_id=data.get("node_id"),
|
|
88
|
+
tool_name=data.get("tool_name"),
|
|
89
|
+
block_id=data.get("block_id"),
|
|
90
|
+
metadata=data.get("metadata", {}),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class PauseSuspend(SuspendSignal):
|
|
95
|
+
"""Signal for manual pause (user-initiated).
|
|
96
|
+
|
|
97
|
+
Raised when user requests to pause execution.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(self, reason: str = "User requested pause"):
|
|
101
|
+
self.reason = reason
|
|
102
|
+
super().__init__(reason)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
__all__ = [
|
|
106
|
+
"SuspendSignal",
|
|
107
|
+
"HITLSuspend",
|
|
108
|
+
"PauseSuspend",
|
|
109
|
+
]
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""State management with checkpoint support.
|
|
2
|
+
|
|
3
|
+
State provides layered storage with three partitions:
|
|
4
|
+
- vars: User/developer variables for prompt formatting
|
|
5
|
+
- data: Data flow shared between ReactAgent and WorkflowAgent
|
|
6
|
+
- execution: Execution state (step, message_ids, pending_request, etc.)
|
|
7
|
+
|
|
8
|
+
Supports path-based access, pattern matching, and checkpoint/restore.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any, TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ..backends.state import StateBackend
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class State:
|
|
19
|
+
"""Layered state management with checkpoint support.
|
|
20
|
+
|
|
21
|
+
State is NOT persisted on every set() call. Instead:
|
|
22
|
+
- set() writes to in-memory buffer
|
|
23
|
+
- checkpoint() persists buffer to backend
|
|
24
|
+
- restore() loads from backend
|
|
25
|
+
|
|
26
|
+
This allows efficient batching and recovery from interrupts.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
state = State(backend, session_id)
|
|
30
|
+
await state.restore() # Load existing state
|
|
31
|
+
|
|
32
|
+
state.set("vars.user_name", "高鑫")
|
|
33
|
+
state.set("workflow.node1_output", result)
|
|
34
|
+
|
|
35
|
+
await state.checkpoint() # Persist changes
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
backend: "StateBackend",
|
|
41
|
+
session_id: str,
|
|
42
|
+
*,
|
|
43
|
+
initial_data: dict[str, Any] | None = None,
|
|
44
|
+
):
|
|
45
|
+
"""Initialize state.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
backend: Storage backend for persistence
|
|
49
|
+
session_id: Session identifier (used as storage key)
|
|
50
|
+
initial_data: Optional initial state data
|
|
51
|
+
"""
|
|
52
|
+
self._backend = backend
|
|
53
|
+
self._session_id = session_id
|
|
54
|
+
self._invocation_id: str | None = None
|
|
55
|
+
self._data: dict[str, Any] = initial_data or {
|
|
56
|
+
"vars": {},
|
|
57
|
+
"data": {},
|
|
58
|
+
"execution": {},
|
|
59
|
+
}
|
|
60
|
+
self._dirty = False
|
|
61
|
+
|
|
62
|
+
# ========== Partition Access ==========
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def vars(self) -> dict[str, Any]:
|
|
66
|
+
"""User/developer variables (for prompt formatting).
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
prompt = template.format(**state.vars)
|
|
70
|
+
"""
|
|
71
|
+
if "vars" not in self._data:
|
|
72
|
+
self._data["vars"] = {}
|
|
73
|
+
return self._data["vars"]
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def data(self) -> dict[str, Any]:
|
|
77
|
+
"""Data flow shared between ReactAgent and WorkflowAgent.
|
|
78
|
+
|
|
79
|
+
Used for:
|
|
80
|
+
- Workflow node outputs
|
|
81
|
+
- Tool results that need to be shared
|
|
82
|
+
- Any data passed between agents
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
state.data["search_results"] = results
|
|
86
|
+
state.data["node1_output"] = output
|
|
87
|
+
"""
|
|
88
|
+
if "data" not in self._data:
|
|
89
|
+
self._data["data"] = {}
|
|
90
|
+
return self._data["data"]
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def execution(self) -> dict[str, Any]:
|
|
94
|
+
"""Execution state for current invocation.
|
|
95
|
+
|
|
96
|
+
Used for:
|
|
97
|
+
- step: Current step number
|
|
98
|
+
- message_ids: References to raw messages
|
|
99
|
+
- pending_request: HITL request if suspended
|
|
100
|
+
- current_node: Workflow current node
|
|
101
|
+
- completed_nodes: Workflow completed nodes
|
|
102
|
+
|
|
103
|
+
Example:
|
|
104
|
+
state.execution["step"] = 5
|
|
105
|
+
state.execution["message_ids"] = ["msg_001", "msg_002"]
|
|
106
|
+
"""
|
|
107
|
+
if "execution" not in self._data:
|
|
108
|
+
self._data["execution"] = {}
|
|
109
|
+
return self._data["execution"]
|
|
110
|
+
|
|
111
|
+
# ========== Path Operations ==========
|
|
112
|
+
|
|
113
|
+
def get(self, path: str, default: Any = None) -> Any:
|
|
114
|
+
"""Get value by path.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
path: Dot-separated path (e.g., "vars.user_name")
|
|
118
|
+
default: Default value if path not found
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Value at path, or default
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
state.get("vars.user_name") # → "高鑫"
|
|
125
|
+
state.get("workflow.node1") # → {...}
|
|
126
|
+
state.get("agent.missing", 0) # → 0
|
|
127
|
+
"""
|
|
128
|
+
parts = path.split(".")
|
|
129
|
+
current = self._data
|
|
130
|
+
|
|
131
|
+
for part in parts:
|
|
132
|
+
if isinstance(current, dict) and part in current:
|
|
133
|
+
current = current[part]
|
|
134
|
+
else:
|
|
135
|
+
return default
|
|
136
|
+
|
|
137
|
+
return current
|
|
138
|
+
|
|
139
|
+
def set(self, path: str, value: Any) -> None:
|
|
140
|
+
"""Set value by path (writes to buffer, not persisted).
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
path: Dot-separated path (e.g., "vars.user_name")
|
|
144
|
+
value: Value to set
|
|
145
|
+
|
|
146
|
+
Example:
|
|
147
|
+
state.set("vars.user_name", "高鑫")
|
|
148
|
+
state.set("workflow.node1_output", result)
|
|
149
|
+
"""
|
|
150
|
+
parts = path.split(".")
|
|
151
|
+
current = self._data
|
|
152
|
+
|
|
153
|
+
# Navigate to parent, creating dicts as needed
|
|
154
|
+
for part in parts[:-1]:
|
|
155
|
+
if part not in current:
|
|
156
|
+
current[part] = {}
|
|
157
|
+
current = current[part]
|
|
158
|
+
|
|
159
|
+
# Set value
|
|
160
|
+
current[parts[-1]] = value
|
|
161
|
+
self._dirty = True
|
|
162
|
+
|
|
163
|
+
def delete(self, path: str) -> bool:
|
|
164
|
+
"""Delete value by path.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
path: Dot-separated path
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
True if deleted, False if not found
|
|
171
|
+
"""
|
|
172
|
+
parts = path.split(".")
|
|
173
|
+
current = self._data
|
|
174
|
+
|
|
175
|
+
# Navigate to parent
|
|
176
|
+
for part in parts[:-1]:
|
|
177
|
+
if isinstance(current, dict) and part in current:
|
|
178
|
+
current = current[part]
|
|
179
|
+
else:
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
# Delete
|
|
183
|
+
if isinstance(current, dict) and parts[-1] in current:
|
|
184
|
+
del current[parts[-1]]
|
|
185
|
+
self._dirty = True
|
|
186
|
+
return True
|
|
187
|
+
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
def match(self, pattern: str) -> dict[str, Any]:
|
|
191
|
+
"""Get values matching prefix pattern.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
pattern: Prefix pattern ending with '*' (e.g., "workflow.*")
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Dict of matching key-value pairs (keys without prefix)
|
|
198
|
+
|
|
199
|
+
Example:
|
|
200
|
+
state.match("workflow.*") # → {"node1_output": ..., "node2_output": ...}
|
|
201
|
+
state.match("vars.*") # → {"user_name": ..., "project_name": ...}
|
|
202
|
+
"""
|
|
203
|
+
if not pattern.endswith("*"):
|
|
204
|
+
# Exact match, return single value wrapped
|
|
205
|
+
val = self.get(pattern)
|
|
206
|
+
return {pattern.split(".")[-1]: val} if val is not None else {}
|
|
207
|
+
|
|
208
|
+
# Prefix match
|
|
209
|
+
prefix = pattern[:-1] # Remove '*'
|
|
210
|
+
if prefix.endswith("."):
|
|
211
|
+
prefix = prefix[:-1] # Remove trailing '.'
|
|
212
|
+
|
|
213
|
+
parent = self.get(prefix)
|
|
214
|
+
if isinstance(parent, dict):
|
|
215
|
+
return parent.copy()
|
|
216
|
+
|
|
217
|
+
return {}
|
|
218
|
+
|
|
219
|
+
def has(self, path: str) -> bool:
|
|
220
|
+
"""Check if path exists.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
path: Dot-separated path
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
True if path exists
|
|
227
|
+
"""
|
|
228
|
+
return self.get(path) is not None
|
|
229
|
+
|
|
230
|
+
def clear(self, partition: str | None = None) -> None:
|
|
231
|
+
"""Clear state.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
partition: If provided, clear only that partition.
|
|
235
|
+
If None, clear all partitions.
|
|
236
|
+
"""
|
|
237
|
+
if partition:
|
|
238
|
+
if partition in self._data:
|
|
239
|
+
self._data[partition] = {}
|
|
240
|
+
self._dirty = True
|
|
241
|
+
else:
|
|
242
|
+
self._data = {
|
|
243
|
+
"vars": {},
|
|
244
|
+
"data": {},
|
|
245
|
+
"execution": {},
|
|
246
|
+
}
|
|
247
|
+
self._dirty = True
|
|
248
|
+
|
|
249
|
+
# ========== Persistence ==========
|
|
250
|
+
|
|
251
|
+
def set_invocation_id(self, invocation_id: str) -> None:
|
|
252
|
+
"""Set current invocation ID for persistence.
|
|
253
|
+
|
|
254
|
+
State is persisted per invocation, not per session.
|
|
255
|
+
"""
|
|
256
|
+
self._invocation_id = invocation_id
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def is_dirty(self) -> bool:
|
|
260
|
+
"""Check if state has unsaved changes."""
|
|
261
|
+
return self._dirty
|
|
262
|
+
|
|
263
|
+
async def checkpoint(self) -> None:
|
|
264
|
+
"""Persist current state to backend.
|
|
265
|
+
|
|
266
|
+
State is saved per invocation_id (not session_id).
|
|
267
|
+
|
|
268
|
+
Call at key points:
|
|
269
|
+
- After step completion
|
|
270
|
+
- After tool execution
|
|
271
|
+
- Before HITL suspend
|
|
272
|
+
"""
|
|
273
|
+
if not self._dirty:
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
# Use invocation_id if set, otherwise fall back to session_id
|
|
277
|
+
key = self._invocation_id or self._session_id
|
|
278
|
+
await self._backend.set("state", key, self._data)
|
|
279
|
+
self._dirty = False
|
|
280
|
+
|
|
281
|
+
async def restore(self, invocation_id: str | None = None) -> bool:
|
|
282
|
+
"""Restore state from backend.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
invocation_id: Specific invocation to restore. If None, uses current.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
True if state was restored, False if no saved state
|
|
289
|
+
"""
|
|
290
|
+
key = invocation_id or self._invocation_id or self._session_id
|
|
291
|
+
data = await self._backend.get("state", key)
|
|
292
|
+
if data:
|
|
293
|
+
self._data = data
|
|
294
|
+
# Ensure all partitions exist
|
|
295
|
+
for partition in ("vars", "data", "execution"):
|
|
296
|
+
if partition not in self._data:
|
|
297
|
+
self._data[partition] = {}
|
|
298
|
+
self._dirty = False
|
|
299
|
+
if invocation_id:
|
|
300
|
+
self._invocation_id = invocation_id
|
|
301
|
+
return True
|
|
302
|
+
return False
|
|
303
|
+
|
|
304
|
+
# ========== Utility ==========
|
|
305
|
+
|
|
306
|
+
def to_dict(self) -> dict[str, Any]:
|
|
307
|
+
"""Export state as dict."""
|
|
308
|
+
return self._data.copy()
|
|
309
|
+
|
|
310
|
+
def update(self, data: dict[str, Any]) -> None:
|
|
311
|
+
"""Bulk update state.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
data: Dict with partition keys (vars, data, execution)
|
|
315
|
+
"""
|
|
316
|
+
for key in ("vars", "data", "execution"):
|
|
317
|
+
if key in data:
|
|
318
|
+
self._data[key].update(data[key])
|
|
319
|
+
self._dirty = True
|
|
320
|
+
|
|
321
|
+
def __repr__(self) -> str:
|
|
322
|
+
dirty_marker = " (dirty)" if self._dirty else ""
|
|
323
|
+
return f"<State session={self._session_id}{dirty_marker}>"
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# Convenience type for state isolation in SubAgents
|
|
327
|
+
class StateSnapshot:
|
|
328
|
+
"""Immutable snapshot of state for isolation."""
|
|
329
|
+
|
|
330
|
+
def __init__(self, data: dict[str, Any]):
|
|
331
|
+
self._data = data
|
|
332
|
+
|
|
333
|
+
@property
|
|
334
|
+
def vars(self) -> dict[str, Any]:
|
|
335
|
+
return self._data.get("vars", {}).copy()
|
|
336
|
+
|
|
337
|
+
@property
|
|
338
|
+
def data(self) -> dict[str, Any]:
|
|
339
|
+
return self._data.get("data", {}).copy()
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def execution(self) -> dict[str, Any]:
|
|
343
|
+
return self._data.get("execution", {}).copy()
|
|
344
|
+
|
|
345
|
+
def get(self, path: str, default: Any = None) -> Any:
|
|
346
|
+
parts = path.split(".")
|
|
347
|
+
current = self._data
|
|
348
|
+
for part in parts:
|
|
349
|
+
if isinstance(current, dict) and part in current:
|
|
350
|
+
current = current[part]
|
|
351
|
+
else:
|
|
352
|
+
return default
|
|
353
|
+
return current
|
|
354
|
+
|
|
355
|
+
def to_dict(self) -> dict[str, Any]:
|
|
356
|
+
return {
|
|
357
|
+
"vars": self.vars,
|
|
358
|
+
"data": self.data,
|
|
359
|
+
"execution": self.execution,
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
__all__ = ["State", "StateSnapshot"]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Type definitions for Aury Agent Framework."""
|
|
2
|
+
from .session import (
|
|
3
|
+
generate_id,
|
|
4
|
+
InvocationState,
|
|
5
|
+
InvocationMode,
|
|
6
|
+
ControlFrame,
|
|
7
|
+
Session,
|
|
8
|
+
Invocation,
|
|
9
|
+
)
|
|
10
|
+
from .subagent import (
|
|
11
|
+
SubAgentMode,
|
|
12
|
+
SubAgentInput,
|
|
13
|
+
SubAgentMetadata,
|
|
14
|
+
SubAgentResult,
|
|
15
|
+
)
|
|
16
|
+
from .block import (
|
|
17
|
+
BlockKind,
|
|
18
|
+
BlockOp,
|
|
19
|
+
Persistence,
|
|
20
|
+
ActorInfo,
|
|
21
|
+
BlockEvent,
|
|
22
|
+
PersistedBlock,
|
|
23
|
+
BlockHandle,
|
|
24
|
+
BlockAggregator,
|
|
25
|
+
BlockMerger,
|
|
26
|
+
register_merger,
|
|
27
|
+
get_merger,
|
|
28
|
+
# Helper functions
|
|
29
|
+
text_block,
|
|
30
|
+
text_delta,
|
|
31
|
+
thinking_block,
|
|
32
|
+
thinking_delta,
|
|
33
|
+
tool_use_block,
|
|
34
|
+
tool_use_patch,
|
|
35
|
+
tool_result_block,
|
|
36
|
+
error_block,
|
|
37
|
+
)
|
|
38
|
+
from .message import (
|
|
39
|
+
MessageRole,
|
|
40
|
+
Message,
|
|
41
|
+
PromptInput,
|
|
42
|
+
)
|
|
43
|
+
from .tool import (
|
|
44
|
+
ToolInfo,
|
|
45
|
+
ToolContext,
|
|
46
|
+
ToolResult,
|
|
47
|
+
ToolInvocationState,
|
|
48
|
+
ToolInvocation,
|
|
49
|
+
BaseTool,
|
|
50
|
+
ToolConfig,
|
|
51
|
+
)
|
|
52
|
+
from .action import (
|
|
53
|
+
ActionType,
|
|
54
|
+
ActionEvent,
|
|
55
|
+
ActionCollector,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
__all__ = [
|
|
59
|
+
# Session
|
|
60
|
+
"generate_id",
|
|
61
|
+
"InvocationState",
|
|
62
|
+
"InvocationMode",
|
|
63
|
+
"ControlFrame",
|
|
64
|
+
"Session",
|
|
65
|
+
"Invocation",
|
|
66
|
+
# SubAgent
|
|
67
|
+
"SubAgentMode",
|
|
68
|
+
"SubAgentInput",
|
|
69
|
+
"SubAgentMetadata",
|
|
70
|
+
"SubAgentResult",
|
|
71
|
+
# Block
|
|
72
|
+
"BlockKind",
|
|
73
|
+
"BlockOp",
|
|
74
|
+
"Persistence",
|
|
75
|
+
"ActorInfo",
|
|
76
|
+
"BlockEvent",
|
|
77
|
+
"PersistedBlock",
|
|
78
|
+
"BlockHandle",
|
|
79
|
+
"BlockAggregator",
|
|
80
|
+
"BlockMerger",
|
|
81
|
+
"register_merger",
|
|
82
|
+
"get_merger",
|
|
83
|
+
"text_block",
|
|
84
|
+
"text_delta",
|
|
85
|
+
"thinking_block",
|
|
86
|
+
"thinking_delta",
|
|
87
|
+
"tool_use_block",
|
|
88
|
+
"tool_use_patch",
|
|
89
|
+
"tool_result_block",
|
|
90
|
+
"error_block",
|
|
91
|
+
# Message
|
|
92
|
+
"MessageRole",
|
|
93
|
+
"Message",
|
|
94
|
+
"PromptInput",
|
|
95
|
+
# Tool
|
|
96
|
+
"ToolInfo",
|
|
97
|
+
"ToolContext",
|
|
98
|
+
"ToolResult",
|
|
99
|
+
"ToolInvocationState",
|
|
100
|
+
"ToolInvocation",
|
|
101
|
+
"BaseTool",
|
|
102
|
+
"ToolConfig",
|
|
103
|
+
# Action
|
|
104
|
+
"ActionType",
|
|
105
|
+
"ActionEvent",
|
|
106
|
+
"ActionCollector",
|
|
107
|
+
]
|