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
aury/agents/core/base.py
ADDED
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
"""Base agent abstract class.
|
|
2
|
+
|
|
3
|
+
All agents (ReactAgent, WorkflowAgent) inherit from BaseAgent and use
|
|
4
|
+
the same constructor signature:
|
|
5
|
+
|
|
6
|
+
def __init__(self, ctx: InvocationContext, config: AgentConfig | None = None)
|
|
7
|
+
|
|
8
|
+
This enables:
|
|
9
|
+
- Unified agent creation via AgentFactory
|
|
10
|
+
- WorkflowAgent as SubAgent of ReactAgent and vice versa
|
|
11
|
+
- Consistent service access through InvocationContext
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from enum import Enum
|
|
19
|
+
from typing import Any, AsyncIterator, Callable, ClassVar, Literal, TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ToolInjectionMode(Enum):
|
|
23
|
+
"""How tools are provided to LLM.
|
|
24
|
+
|
|
25
|
+
FUNCTION_CALL: Use native LLM function calling (tools parameter)
|
|
26
|
+
PROMPT: Inject tool schemas as text in system prompt
|
|
27
|
+
"""
|
|
28
|
+
FUNCTION_CALL = "function_call"
|
|
29
|
+
PROMPT = "prompt"
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from .context import InvocationContext
|
|
33
|
+
from .types.block import BlockEvent, BlockKind, Persistence
|
|
34
|
+
from .types.action import ActionEvent, ActionCollector
|
|
35
|
+
from .types.session import Session
|
|
36
|
+
from .event_bus import EventBus, Bus, Events
|
|
37
|
+
from ..backends.state import StateBackend
|
|
38
|
+
from ..middleware import MiddlewareChain
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class AgentConfig:
|
|
43
|
+
"""Agent configuration.
|
|
44
|
+
|
|
45
|
+
Used by both ReactAgent and WorkflowAgent.
|
|
46
|
+
|
|
47
|
+
Note: LLM parameters (temperature, max_tokens, timeout, retries) are
|
|
48
|
+
configured on LLMProvider, not here. Tool timeout is configured on
|
|
49
|
+
each tool's ToolConfig, not here.
|
|
50
|
+
"""
|
|
51
|
+
name: str = "default"
|
|
52
|
+
max_steps: int = 50
|
|
53
|
+
|
|
54
|
+
# System prompt configuration
|
|
55
|
+
system_prompt: str | None = None
|
|
56
|
+
|
|
57
|
+
# Thinking configuration (for models that support extended thinking)
|
|
58
|
+
enable_thinking: bool = False # Whether to request thinking output from LLM
|
|
59
|
+
reasoning_effort: str | None = None # Reasoning effort: "low", "medium", "high", "max", "auto"
|
|
60
|
+
stream_thinking: bool = True # Whether to stream thinking in real-time
|
|
61
|
+
|
|
62
|
+
# Tool execution configuration
|
|
63
|
+
parallel_tool_execution: bool = True # Execute multiple tools in parallel
|
|
64
|
+
|
|
65
|
+
# Tool injection mode
|
|
66
|
+
tool_mode: ToolInjectionMode = ToolInjectionMode.FUNCTION_CALL
|
|
67
|
+
|
|
68
|
+
# Extra metadata for agent-specific config
|
|
69
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
70
|
+
|
|
71
|
+
def to_dict(self) -> dict[str, Any]:
|
|
72
|
+
return {
|
|
73
|
+
"name": self.name,
|
|
74
|
+
"max_steps": self.max_steps,
|
|
75
|
+
"system_prompt": self.system_prompt,
|
|
76
|
+
"enable_thinking": self.enable_thinking,
|
|
77
|
+
"reasoning_effort": self.reasoning_effort,
|
|
78
|
+
"stream_thinking": self.stream_thinking,
|
|
79
|
+
"parallel_tool_execution": self.parallel_tool_execution,
|
|
80
|
+
"tool_mode": self.tool_mode.value,
|
|
81
|
+
"metadata": self.metadata,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class BaseAgent(ABC):
|
|
86
|
+
"""Abstract base class for all agents.
|
|
87
|
+
|
|
88
|
+
All agents use the same constructor signature:
|
|
89
|
+
__init__(self, ctx: InvocationContext, config: AgentConfig | None = None)
|
|
90
|
+
|
|
91
|
+
This enables unified agent creation and interoperability:
|
|
92
|
+
- ReactAgent can delegate to WorkflowAgent
|
|
93
|
+
- WorkflowAgent nodes can use ReactAgent
|
|
94
|
+
- AgentFactory creates any agent type uniformly
|
|
95
|
+
|
|
96
|
+
Class-level attributes (override in subclasses):
|
|
97
|
+
name: Agent unique identifier
|
|
98
|
+
description: Human-readable description
|
|
99
|
+
agent_type: "react" or "workflow"
|
|
100
|
+
sub_agents: List of agent classes that can be delegated to
|
|
101
|
+
user_input_block: Function returning BlockEvent for HITL input
|
|
102
|
+
history_inherit_mode: How to inherit history on switch
|
|
103
|
+
memory_merge_mode: How to merge memory when completing as SubAgent
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
# ========== Class-level config (override in subclasses) ==========
|
|
107
|
+
name: ClassVar[str] = "base" # Agent unique identifier
|
|
108
|
+
description: ClassVar[str] = "" # Agent description
|
|
109
|
+
|
|
110
|
+
# Agent type - use any string for custom types
|
|
111
|
+
# Built-in types: "react", "workflow"
|
|
112
|
+
# Custom types: "custom", "data_processor", "summarizer", etc.
|
|
113
|
+
agent_type: ClassVar[str] = "custom"
|
|
114
|
+
|
|
115
|
+
# SubAgent configuration - list of agent classes that can be delegated to
|
|
116
|
+
sub_agents: ClassVar[list[type["BaseAgent"]]] = []
|
|
117
|
+
|
|
118
|
+
# HITL input block generator (called when agent needs initial input)
|
|
119
|
+
user_input_block: ClassVar[Callable[[], "BlockEvent"] | None] = None
|
|
120
|
+
|
|
121
|
+
# History inherit mode when switched to ("full", "summary", "none")
|
|
122
|
+
history_inherit_mode: ClassVar[Literal["full", "summary", "none"]] = "summary"
|
|
123
|
+
|
|
124
|
+
# Memory merge mode when completing as SubAgent ("merge", "summarize", "discard")
|
|
125
|
+
memory_merge_mode: ClassVar[Literal["merge", "summarize", "discard"]] = "summarize"
|
|
126
|
+
|
|
127
|
+
# ========== Instance initialization ==========
|
|
128
|
+
|
|
129
|
+
def __init__(
|
|
130
|
+
self,
|
|
131
|
+
ctx: "InvocationContext",
|
|
132
|
+
config: AgentConfig | None = None,
|
|
133
|
+
):
|
|
134
|
+
"""Initialize agent with context and config.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
ctx: InvocationContext containing all services (storage, bus, llm, tools, etc.)
|
|
138
|
+
config: Agent configuration (optional, uses defaults if not provided)
|
|
139
|
+
"""
|
|
140
|
+
self._ctx = ctx
|
|
141
|
+
self.config = config or AgentConfig()
|
|
142
|
+
|
|
143
|
+
# Abort signal (delegates to ctx.abort_*)
|
|
144
|
+
self._abort = asyncio.Event()
|
|
145
|
+
|
|
146
|
+
# Current state
|
|
147
|
+
self._running = False
|
|
148
|
+
|
|
149
|
+
# Action collector for current run
|
|
150
|
+
self._action_collector: "ActionCollector | None" = None
|
|
151
|
+
|
|
152
|
+
# Runtime config overrides (set by run(), cleared after execution)
|
|
153
|
+
self._run_config: dict[str, Any] = {}
|
|
154
|
+
|
|
155
|
+
# ========== Properties for service access ==========
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def ctx(self) -> "InvocationContext":
|
|
159
|
+
"""Get invocation context."""
|
|
160
|
+
return self._ctx
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def bus(self) -> "Bus":
|
|
164
|
+
"""Get event bus from context."""
|
|
165
|
+
return self._ctx.bus
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def session(self) -> "Session":
|
|
169
|
+
"""Get session from context."""
|
|
170
|
+
return self._ctx.session
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def middleware(self) -> "MiddlewareChain | None":
|
|
174
|
+
"""Get middleware chain from context."""
|
|
175
|
+
return self._ctx.middleware
|
|
176
|
+
|
|
177
|
+
async def run(
|
|
178
|
+
self,
|
|
179
|
+
input: Any,
|
|
180
|
+
*,
|
|
181
|
+
# Runtime config overrides (takes precedence over AgentConfig)
|
|
182
|
+
enable_thinking: bool | None = None,
|
|
183
|
+
reasoning_effort: str | None = None,
|
|
184
|
+
stream_thinking: bool | None = None,
|
|
185
|
+
llm: "LLMProvider | None" = None,
|
|
186
|
+
# Internal
|
|
187
|
+
_force_own_queue: bool = False,
|
|
188
|
+
) -> AsyncIterator["BlockEvent | ActionEvent"]:
|
|
189
|
+
"""Main entry point for agent execution.
|
|
190
|
+
|
|
191
|
+
This is a unified wrapper that:
|
|
192
|
+
1. Sets up emit queue via ContextVar (or reuses parent's if nested)
|
|
193
|
+
2. Runs _execute() which uses ctx.emit() internally
|
|
194
|
+
3. Yields BlockEvent and non-internal ActionEvent
|
|
195
|
+
4. Collects ActionEvents for result aggregation
|
|
196
|
+
|
|
197
|
+
Subclasses should implement _execute(), not run().
|
|
198
|
+
Uses event-driven asyncio.wait for minimal latency streaming.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
input: Input to process (usually PromptInput)
|
|
202
|
+
enable_thinking: Override config.enable_thinking for this run
|
|
203
|
+
reasoning_effort: Override config.reasoning_effort for this run
|
|
204
|
+
stream_thinking: Override config.stream_thinking for this run
|
|
205
|
+
llm: Override context LLM provider for this run
|
|
206
|
+
_force_own_queue: Internal flag to force creating own queue
|
|
207
|
+
(used by delegate tool to capture events)
|
|
208
|
+
|
|
209
|
+
Yields:
|
|
210
|
+
BlockEvent for UI streaming
|
|
211
|
+
ActionEvent (if internal=False) for external actions
|
|
212
|
+
|
|
213
|
+
Example:
|
|
214
|
+
# Use thinking for complex tasks
|
|
215
|
+
async for event in agent.run(
|
|
216
|
+
"Solve this problem...",
|
|
217
|
+
enable_thinking=True,
|
|
218
|
+
reasoning_effort="high",
|
|
219
|
+
):
|
|
220
|
+
print(event)
|
|
221
|
+
|
|
222
|
+
# Switch LLM for specific runs
|
|
223
|
+
async for event in agent.run(
|
|
224
|
+
"Quick task",
|
|
225
|
+
llm=faster_llm_provider,
|
|
226
|
+
):
|
|
227
|
+
print(event)
|
|
228
|
+
"""
|
|
229
|
+
from .context import _emit_queue_var, _set_current_ctx, _reset_current_ctx
|
|
230
|
+
from .types.block import BlockEvent
|
|
231
|
+
from .types.action import ActionEvent, ActionCollector
|
|
232
|
+
|
|
233
|
+
# Store runtime overrides for this run
|
|
234
|
+
self._run_config = {
|
|
235
|
+
"enable_thinking": enable_thinking,
|
|
236
|
+
"reasoning_effort": reasoning_effort,
|
|
237
|
+
"stream_thinking": stream_thinking,
|
|
238
|
+
"llm": llm,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
# Auto-detect parent queue (nested agent call)
|
|
242
|
+
# Skip if _force_own_queue is True (delegate tool needs to capture events)
|
|
243
|
+
if not _force_own_queue:
|
|
244
|
+
try:
|
|
245
|
+
parent_queue = _emit_queue_var.get()
|
|
246
|
+
# Has parent - passthrough mode, reuse parent queue
|
|
247
|
+
# Still set our ctx (child agent has its own ctx)
|
|
248
|
+
ctx_token = _set_current_ctx(self._ctx)
|
|
249
|
+
try:
|
|
250
|
+
await self._execute(input)
|
|
251
|
+
finally:
|
|
252
|
+
_reset_current_ctx(ctx_token)
|
|
253
|
+
return
|
|
254
|
+
except LookupError:
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
# No parent - create own queue and yield events
|
|
258
|
+
queue: asyncio.Queue[BlockEvent | ActionEvent] = asyncio.Queue()
|
|
259
|
+
queue_token = _emit_queue_var.set(queue)
|
|
260
|
+
ctx_token = _set_current_ctx(self._ctx)
|
|
261
|
+
|
|
262
|
+
# Create action collector for this run
|
|
263
|
+
self._action_collector = ActionCollector()
|
|
264
|
+
|
|
265
|
+
def process_event(event: BlockEvent | ActionEvent):
|
|
266
|
+
"""Process event: collect actions, decide if should yield."""
|
|
267
|
+
if isinstance(event, ActionEvent):
|
|
268
|
+
self._action_collector.collect(event)
|
|
269
|
+
# Only yield non-internal actions
|
|
270
|
+
return not event.internal
|
|
271
|
+
return True # Always yield BlockEvents
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
# Start execution task
|
|
275
|
+
exec_task = asyncio.create_task(self._execute(input))
|
|
276
|
+
get_task: asyncio.Task | None = None
|
|
277
|
+
|
|
278
|
+
# Collect and yield events (streaming)
|
|
279
|
+
while True:
|
|
280
|
+
# First drain any pending items from queue (non-blocking)
|
|
281
|
+
while True:
|
|
282
|
+
try:
|
|
283
|
+
event = queue.get_nowait()
|
|
284
|
+
if process_event(event):
|
|
285
|
+
yield event
|
|
286
|
+
except asyncio.QueueEmpty:
|
|
287
|
+
break
|
|
288
|
+
|
|
289
|
+
# Exit if task is done and queue is empty
|
|
290
|
+
if exec_task.done() and queue.empty():
|
|
291
|
+
break
|
|
292
|
+
|
|
293
|
+
# Create get_task if needed
|
|
294
|
+
if get_task is None or get_task.done():
|
|
295
|
+
get_task = asyncio.create_task(queue.get())
|
|
296
|
+
|
|
297
|
+
# Wait for EITHER: queue item OR exec_task completion
|
|
298
|
+
# This is event-driven - no polling, no timeout delays
|
|
299
|
+
done, _ = await asyncio.wait(
|
|
300
|
+
{get_task, exec_task},
|
|
301
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
if get_task in done:
|
|
305
|
+
# Got an item from queue
|
|
306
|
+
try:
|
|
307
|
+
event = get_task.result()
|
|
308
|
+
if process_event(event):
|
|
309
|
+
yield event
|
|
310
|
+
get_task = None # Will create new one in next iteration
|
|
311
|
+
except asyncio.CancelledError:
|
|
312
|
+
pass
|
|
313
|
+
# If only exec_task completed, loop will drain queue and exit
|
|
314
|
+
|
|
315
|
+
# Cancel pending get_task if any
|
|
316
|
+
if get_task and not get_task.done():
|
|
317
|
+
get_task.cancel()
|
|
318
|
+
try:
|
|
319
|
+
await get_task
|
|
320
|
+
except asyncio.CancelledError:
|
|
321
|
+
pass
|
|
322
|
+
|
|
323
|
+
# Final drain after task completion
|
|
324
|
+
while not queue.empty():
|
|
325
|
+
try:
|
|
326
|
+
event = queue.get_nowait()
|
|
327
|
+
if process_event(event):
|
|
328
|
+
yield event
|
|
329
|
+
except asyncio.QueueEmpty:
|
|
330
|
+
break
|
|
331
|
+
|
|
332
|
+
# Check for exceptions in execution
|
|
333
|
+
await exec_task
|
|
334
|
+
|
|
335
|
+
finally:
|
|
336
|
+
_reset_current_ctx(ctx_token)
|
|
337
|
+
_emit_queue_var.reset(queue_token)
|
|
338
|
+
self._run_config = {} # Clear runtime overrides
|
|
339
|
+
|
|
340
|
+
@abstractmethod
|
|
341
|
+
async def _execute(self, input: Any) -> None:
|
|
342
|
+
"""Execute agent logic. Subclasses implement this.
|
|
343
|
+
|
|
344
|
+
Use self.ctx.emit(block) to send streaming output.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
input: Input to process (usually PromptInput)
|
|
348
|
+
"""
|
|
349
|
+
pass
|
|
350
|
+
|
|
351
|
+
def cancel(self, abort_chain: bool = False) -> None:
|
|
352
|
+
"""Cancel current execution.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
abort_chain: If True, abort entire invocation chain (SubAgents too)
|
|
356
|
+
"""
|
|
357
|
+
self._abort.set()
|
|
358
|
+
if self._ctx:
|
|
359
|
+
if abort_chain:
|
|
360
|
+
self._ctx.abort_chain.set()
|
|
361
|
+
else:
|
|
362
|
+
self._ctx.abort_self.set()
|
|
363
|
+
|
|
364
|
+
def reset(self) -> None:
|
|
365
|
+
"""Reset agent state for new execution."""
|
|
366
|
+
self._abort.clear()
|
|
367
|
+
self._running = False
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def is_running(self) -> bool:
|
|
371
|
+
"""Check if agent is currently running."""
|
|
372
|
+
return self._running
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def is_cancelled(self) -> bool:
|
|
376
|
+
"""Check if agent has been cancelled."""
|
|
377
|
+
if self._ctx and self._ctx.is_aborted:
|
|
378
|
+
return True
|
|
379
|
+
return self._abort.is_set()
|
|
380
|
+
|
|
381
|
+
# ========== Emit Helper Methods (Syntax Sugar) ==========
|
|
382
|
+
|
|
383
|
+
async def emit(self, event: "BlockEvent | ActionEvent") -> None:
|
|
384
|
+
"""Emit a BlockEvent or ActionEvent.
|
|
385
|
+
|
|
386
|
+
Convenience method that delegates to ctx.emit().
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
event: BlockEvent or ActionEvent to emit
|
|
390
|
+
"""
|
|
391
|
+
await self._ctx.emit(event)
|
|
392
|
+
|
|
393
|
+
async def emit_text(
|
|
394
|
+
self,
|
|
395
|
+
content: str,
|
|
396
|
+
block_id: str | None = None,
|
|
397
|
+
delta: bool = False,
|
|
398
|
+
parent_id: str | None = None,
|
|
399
|
+
) -> str:
|
|
400
|
+
"""Emit a text block.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
content: Text content
|
|
404
|
+
block_id: Optional block ID (auto-generated if not provided)
|
|
405
|
+
delta: If True, emit as DELTA (append), else APPLY (create/replace)
|
|
406
|
+
parent_id: Optional parent block ID for nesting
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
The block_id used
|
|
410
|
+
|
|
411
|
+
Example:
|
|
412
|
+
# Create new text block
|
|
413
|
+
block_id = await self.emit_text("Hello")
|
|
414
|
+
|
|
415
|
+
# Stream append to existing block
|
|
416
|
+
await self.emit_text(" World", block_id=block_id, delta=True)
|
|
417
|
+
"""
|
|
418
|
+
from .types.block import BlockEvent, BlockKind, BlockOp
|
|
419
|
+
from .types.session import generate_id
|
|
420
|
+
|
|
421
|
+
bid = block_id or generate_id("blk")
|
|
422
|
+
await self._ctx.emit(BlockEvent(
|
|
423
|
+
block_id=bid,
|
|
424
|
+
parent_id=parent_id,
|
|
425
|
+
kind=BlockKind.TEXT,
|
|
426
|
+
op=BlockOp.DELTA if delta else BlockOp.APPLY,
|
|
427
|
+
data={"content": content},
|
|
428
|
+
))
|
|
429
|
+
return bid
|
|
430
|
+
|
|
431
|
+
async def emit_thinking(
|
|
432
|
+
self,
|
|
433
|
+
content: str,
|
|
434
|
+
block_id: str | None = None,
|
|
435
|
+
delta: bool = False,
|
|
436
|
+
) -> str:
|
|
437
|
+
"""Emit a thinking block (collapsible in UI).
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
content: Thinking content
|
|
441
|
+
block_id: Optional block ID
|
|
442
|
+
delta: If True, emit as DELTA
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
The block_id used
|
|
446
|
+
"""
|
|
447
|
+
from .types.block import BlockEvent, BlockKind, BlockOp
|
|
448
|
+
from .types.session import generate_id
|
|
449
|
+
|
|
450
|
+
bid = block_id or generate_id("blk")
|
|
451
|
+
await self._ctx.emit(BlockEvent(
|
|
452
|
+
block_id=bid,
|
|
453
|
+
kind=BlockKind.THINKING,
|
|
454
|
+
op=BlockOp.DELTA if delta else BlockOp.APPLY,
|
|
455
|
+
data={"content": content},
|
|
456
|
+
))
|
|
457
|
+
return bid
|
|
458
|
+
|
|
459
|
+
async def emit_error(
|
|
460
|
+
self,
|
|
461
|
+
message: str,
|
|
462
|
+
code: str = "error",
|
|
463
|
+
recoverable: bool = True,
|
|
464
|
+
block_id: str | None = None,
|
|
465
|
+
) -> str:
|
|
466
|
+
"""Emit an error block.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
message: Error message
|
|
470
|
+
code: Error code
|
|
471
|
+
recoverable: Whether the error is recoverable
|
|
472
|
+
block_id: Optional block ID
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
The block_id used
|
|
476
|
+
"""
|
|
477
|
+
from .types.block import BlockEvent, BlockKind, BlockOp
|
|
478
|
+
from .types.session import generate_id
|
|
479
|
+
|
|
480
|
+
bid = block_id or generate_id("blk")
|
|
481
|
+
await self._ctx.emit(BlockEvent(
|
|
482
|
+
block_id=bid,
|
|
483
|
+
kind=BlockKind.ERROR,
|
|
484
|
+
op=BlockOp.APPLY,
|
|
485
|
+
data={
|
|
486
|
+
"code": code,
|
|
487
|
+
"message": message,
|
|
488
|
+
"recoverable": recoverable,
|
|
489
|
+
},
|
|
490
|
+
))
|
|
491
|
+
return bid
|
|
492
|
+
|
|
493
|
+
async def emit_artifact(
|
|
494
|
+
self,
|
|
495
|
+
artifact_id: str,
|
|
496
|
+
title: str | None = None,
|
|
497
|
+
summary: str | None = None,
|
|
498
|
+
block_id: str | None = None,
|
|
499
|
+
) -> str:
|
|
500
|
+
"""Emit an artifact reference block.
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
artifact_id: ID of the artifact to reference
|
|
504
|
+
title: Optional title for display
|
|
505
|
+
summary: Optional summary for display
|
|
506
|
+
block_id: Optional block ID
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
The block_id used
|
|
510
|
+
"""
|
|
511
|
+
from .types.block import BlockEvent, BlockKind, BlockOp
|
|
512
|
+
from .types.session import generate_id
|
|
513
|
+
|
|
514
|
+
bid = block_id or generate_id("blk")
|
|
515
|
+
await self._ctx.emit(BlockEvent(
|
|
516
|
+
block_id=bid,
|
|
517
|
+
kind=BlockKind.ARTIFACT,
|
|
518
|
+
op=BlockOp.APPLY,
|
|
519
|
+
data={
|
|
520
|
+
"artifact_id": artifact_id,
|
|
521
|
+
"title": title,
|
|
522
|
+
"summary": summary,
|
|
523
|
+
},
|
|
524
|
+
))
|
|
525
|
+
return bid
|
|
526
|
+
|
|
527
|
+
@property
|
|
528
|
+
def action_collector(self) -> "ActionCollector | None":
|
|
529
|
+
"""Get action collector from current run.
|
|
530
|
+
|
|
531
|
+
Use this after run() completes to access collected actions and results.
|
|
532
|
+
|
|
533
|
+
Example:
|
|
534
|
+
async for event in agent.run(input):
|
|
535
|
+
...
|
|
536
|
+
result = agent.action_collector.get_merged_result()
|
|
537
|
+
"""
|
|
538
|
+
return self._action_collector
|
|
539
|
+
|
|
540
|
+
async def pause(self) -> str:
|
|
541
|
+
"""Pause execution and return invocation ID for later resume.
|
|
542
|
+
|
|
543
|
+
Returns:
|
|
544
|
+
Invocation ID for resuming
|
|
545
|
+
|
|
546
|
+
Raises:
|
|
547
|
+
NotImplementedError: If agent doesn't support pause
|
|
548
|
+
"""
|
|
549
|
+
raise NotImplementedError("Agent does not support pause/resume")
|
|
550
|
+
|
|
551
|
+
async def resume(self, invocation_id: str) -> AsyncIterator["BlockEvent"]:
|
|
552
|
+
"""Resume paused execution.
|
|
553
|
+
|
|
554
|
+
Args:
|
|
555
|
+
invocation_id: ID from pause()
|
|
556
|
+
|
|
557
|
+
Yields:
|
|
558
|
+
BlockEvent streaming events
|
|
559
|
+
|
|
560
|
+
Raises:
|
|
561
|
+
NotImplementedError: If agent doesn't support pause
|
|
562
|
+
"""
|
|
563
|
+
raise NotImplementedError("Agent does not support pause/resume")
|
|
564
|
+
|
|
565
|
+
async def _check_abort(self) -> bool:
|
|
566
|
+
"""Check if execution should be aborted.
|
|
567
|
+
|
|
568
|
+
Returns:
|
|
569
|
+
True if should abort
|
|
570
|
+
"""
|
|
571
|
+
if self._ctx and self._ctx.is_aborted:
|
|
572
|
+
return True
|
|
573
|
+
return self._abort.is_set()
|