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,460 @@
|
|
|
1
|
+
"""CLI Extension System for custom Agents and Tools.
|
|
2
|
+
|
|
3
|
+
This module provides a plugin/extension system that allows users to register
|
|
4
|
+
custom Agents and Tools that can be used with the CLI commands.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
1. Create a configuration file (e.g., aury.extensions.toml):
|
|
8
|
+
|
|
9
|
+
[agents]
|
|
10
|
+
my_agent = "path.to.module:MyAgentClass"
|
|
11
|
+
|
|
12
|
+
[tools]
|
|
13
|
+
my_tool = "path.to.module:my_tool_function"
|
|
14
|
+
|
|
15
|
+
2. Or programmatically:
|
|
16
|
+
|
|
17
|
+
from aury.agents.cli.extensions import ExtensionRegistry
|
|
18
|
+
registry = ExtensionRegistry()
|
|
19
|
+
registry.register_agent("my_agent", MyAgentClass)
|
|
20
|
+
registry.register_tool("my_tool", my_tool_function)
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import importlib
|
|
25
|
+
import sys
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any, Callable, TYPE_CHECKING
|
|
29
|
+
|
|
30
|
+
from rich.console import Console
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from aury.agents.core.base import BaseAgent
|
|
34
|
+
from aury.agents.tools import BaseTool
|
|
35
|
+
|
|
36
|
+
console = Console()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class ExtensionInfo:
|
|
41
|
+
"""Information about a registered extension."""
|
|
42
|
+
name: str
|
|
43
|
+
module_path: str | None
|
|
44
|
+
class_or_func: Any
|
|
45
|
+
description: str = ""
|
|
46
|
+
source: str = "programmatic" # programmatic, config, entry_point
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ExtensionRegistry:
|
|
50
|
+
"""Registry for custom Agents and Tools.
|
|
51
|
+
|
|
52
|
+
This class provides a central registry for custom extensions that can be
|
|
53
|
+
used with the CLI commands. Extensions can be registered:
|
|
54
|
+
|
|
55
|
+
1. Programmatically via register_agent() / register_tool()
|
|
56
|
+
2. From configuration files (TOML/YAML)
|
|
57
|
+
3. From Python entry points
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
>>> registry = ExtensionRegistry()
|
|
61
|
+
>>> registry.register_agent("my_agent", MyAgent)
|
|
62
|
+
>>> registry.register_tool("my_tool", my_tool_func)
|
|
63
|
+
>>>
|
|
64
|
+
>>> # Load from config
|
|
65
|
+
>>> registry.load_from_config("~/.aury/extensions.toml")
|
|
66
|
+
>>>
|
|
67
|
+
>>> # Get registered extensions
|
|
68
|
+
>>> agent_cls = registry.get_agent("my_agent")
|
|
69
|
+
>>> tool = registry.get_tool("my_tool")
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
_instance: "ExtensionRegistry | None" = None
|
|
73
|
+
|
|
74
|
+
def __new__(cls) -> "ExtensionRegistry":
|
|
75
|
+
"""Singleton pattern for global registry."""
|
|
76
|
+
if cls._instance is None:
|
|
77
|
+
cls._instance = super().__new__(cls)
|
|
78
|
+
cls._instance._init()
|
|
79
|
+
return cls._instance
|
|
80
|
+
|
|
81
|
+
def _init(self) -> None:
|
|
82
|
+
"""Initialize the registry."""
|
|
83
|
+
self._agents: dict[str, ExtensionInfo] = {}
|
|
84
|
+
self._tools: dict[str, ExtensionInfo] = {}
|
|
85
|
+
self._loaded_configs: set[str] = set()
|
|
86
|
+
|
|
87
|
+
# ========== Agent Registration ==========
|
|
88
|
+
|
|
89
|
+
def register_agent(
|
|
90
|
+
self,
|
|
91
|
+
name: str,
|
|
92
|
+
agent_class: type["BaseAgent"],
|
|
93
|
+
description: str = "",
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Register a custom Agent class.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
name: Unique name for the agent
|
|
99
|
+
agent_class: The Agent class (must inherit from BaseAgent)
|
|
100
|
+
description: Optional description
|
|
101
|
+
"""
|
|
102
|
+
self._agents[name] = ExtensionInfo(
|
|
103
|
+
name=name,
|
|
104
|
+
module_path=f"{agent_class.__module__}:{agent_class.__name__}",
|
|
105
|
+
class_or_func=agent_class,
|
|
106
|
+
description=description or getattr(agent_class, "description", ""),
|
|
107
|
+
source="programmatic",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def get_agent(self, name: str) -> type["BaseAgent"] | None:
|
|
111
|
+
"""Get a registered Agent class by name."""
|
|
112
|
+
info = self._agents.get(name)
|
|
113
|
+
if info:
|
|
114
|
+
return info.class_or_func
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
def list_agents(self) -> list[ExtensionInfo]:
|
|
118
|
+
"""List all registered Agents."""
|
|
119
|
+
return list(self._agents.values())
|
|
120
|
+
|
|
121
|
+
# ========== Tool Registration ==========
|
|
122
|
+
|
|
123
|
+
def register_tool(
|
|
124
|
+
self,
|
|
125
|
+
name: str,
|
|
126
|
+
tool: "BaseTool | Callable",
|
|
127
|
+
description: str = "",
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Register a custom Tool.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
name: Unique name for the tool
|
|
133
|
+
tool: Tool instance or function decorated with @tool
|
|
134
|
+
description: Optional description
|
|
135
|
+
"""
|
|
136
|
+
self._tools[name] = ExtensionInfo(
|
|
137
|
+
name=name,
|
|
138
|
+
module_path=self._get_module_path(tool),
|
|
139
|
+
class_or_func=tool,
|
|
140
|
+
description=description or getattr(tool, "description", ""),
|
|
141
|
+
source="programmatic",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def get_tool(self, name: str) -> "BaseTool | Callable | None":
|
|
145
|
+
"""Get a registered Tool by name."""
|
|
146
|
+
info = self._tools.get(name)
|
|
147
|
+
if info:
|
|
148
|
+
return info.class_or_func
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
def list_tools(self) -> list[ExtensionInfo]:
|
|
152
|
+
"""List all registered Tools."""
|
|
153
|
+
return list(self._tools.values())
|
|
154
|
+
|
|
155
|
+
# ========== Config Loading ==========
|
|
156
|
+
|
|
157
|
+
def load_from_config(self, config_path: str | Path) -> int:
|
|
158
|
+
"""Load extensions from a configuration file.
|
|
159
|
+
|
|
160
|
+
Supports TOML format:
|
|
161
|
+
|
|
162
|
+
[agents]
|
|
163
|
+
my_agent = "mymodule.agents:MyAgent"
|
|
164
|
+
|
|
165
|
+
[tools]
|
|
166
|
+
my_tool = "mymodule.tools:my_tool"
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
config_path: Path to configuration file
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Number of extensions loaded
|
|
173
|
+
"""
|
|
174
|
+
config_path = Path(config_path).expanduser()
|
|
175
|
+
|
|
176
|
+
if not config_path.exists():
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
config_key = str(config_path.resolve())
|
|
180
|
+
if config_key in self._loaded_configs:
|
|
181
|
+
return 0
|
|
182
|
+
|
|
183
|
+
self._loaded_configs.add(config_key)
|
|
184
|
+
|
|
185
|
+
count = 0
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
import tomllib
|
|
189
|
+
except ImportError:
|
|
190
|
+
import tomli as tomllib
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
with open(config_path, "rb") as f:
|
|
194
|
+
config = tomllib.load(f)
|
|
195
|
+
|
|
196
|
+
# Load agents
|
|
197
|
+
for name, path in config.get("agents", {}).items():
|
|
198
|
+
try:
|
|
199
|
+
cls = self._import_from_string(path)
|
|
200
|
+
self._agents[name] = ExtensionInfo(
|
|
201
|
+
name=name,
|
|
202
|
+
module_path=path,
|
|
203
|
+
class_or_func=cls,
|
|
204
|
+
source="config",
|
|
205
|
+
)
|
|
206
|
+
count += 1
|
|
207
|
+
except Exception as e:
|
|
208
|
+
console.print(f"[yellow]Warning: Failed to load agent '{name}': {e}[/yellow]")
|
|
209
|
+
|
|
210
|
+
# Load tools
|
|
211
|
+
for name, path in config.get("tools", {}).items():
|
|
212
|
+
try:
|
|
213
|
+
func = self._import_from_string(path)
|
|
214
|
+
self._tools[name] = ExtensionInfo(
|
|
215
|
+
name=name,
|
|
216
|
+
module_path=path,
|
|
217
|
+
class_or_func=func,
|
|
218
|
+
source="config",
|
|
219
|
+
)
|
|
220
|
+
count += 1
|
|
221
|
+
except Exception as e:
|
|
222
|
+
console.print(f"[yellow]Warning: Failed to load tool '{name}': {e}[/yellow]")
|
|
223
|
+
|
|
224
|
+
except Exception as e:
|
|
225
|
+
console.print(f"[yellow]Warning: Failed to load config {config_path}: {e}[/yellow]")
|
|
226
|
+
|
|
227
|
+
return count
|
|
228
|
+
|
|
229
|
+
def load_from_directory(self, directory: str | Path) -> int:
|
|
230
|
+
"""Load extensions from Python files in a directory.
|
|
231
|
+
|
|
232
|
+
Scans for .py files and looks for:
|
|
233
|
+
- Classes with `register_as_agent = True`
|
|
234
|
+
- Functions decorated with `@tool`
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
directory: Directory to scan
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Number of extensions loaded
|
|
241
|
+
"""
|
|
242
|
+
directory = Path(directory).expanduser()
|
|
243
|
+
|
|
244
|
+
if not directory.is_dir():
|
|
245
|
+
return 0
|
|
246
|
+
|
|
247
|
+
count = 0
|
|
248
|
+
|
|
249
|
+
for py_file in directory.glob("*.py"):
|
|
250
|
+
if py_file.name.startswith("_"):
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
count += self._load_from_file(py_file)
|
|
255
|
+
except Exception as e:
|
|
256
|
+
console.print(f"[yellow]Warning: Failed to load {py_file}: {e}[/yellow]")
|
|
257
|
+
|
|
258
|
+
return count
|
|
259
|
+
|
|
260
|
+
def load_from_entry_points(self, group: str = "aury.extensions") -> int:
|
|
261
|
+
"""Load extensions from Python entry points.
|
|
262
|
+
|
|
263
|
+
This allows packages to declare extensions in their pyproject.toml:
|
|
264
|
+
|
|
265
|
+
[project.entry-points."aury.extensions"]
|
|
266
|
+
my_agent = "mypackage:MyAgent"
|
|
267
|
+
my_tool = "mypackage:my_tool"
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
group: Entry point group name
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Number of extensions loaded
|
|
274
|
+
"""
|
|
275
|
+
count = 0
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
from importlib.metadata import entry_points
|
|
279
|
+
|
|
280
|
+
# Python 3.10+ syntax
|
|
281
|
+
eps = entry_points(group=group)
|
|
282
|
+
|
|
283
|
+
for ep in eps:
|
|
284
|
+
try:
|
|
285
|
+
obj = ep.load()
|
|
286
|
+
|
|
287
|
+
# Determine if it's an Agent or Tool
|
|
288
|
+
if self._is_agent_class(obj):
|
|
289
|
+
self._agents[ep.name] = ExtensionInfo(
|
|
290
|
+
name=ep.name,
|
|
291
|
+
module_path=f"{ep.value}",
|
|
292
|
+
class_or_func=obj,
|
|
293
|
+
source="entry_point",
|
|
294
|
+
)
|
|
295
|
+
count += 1
|
|
296
|
+
elif self._is_tool(obj):
|
|
297
|
+
self._tools[ep.name] = ExtensionInfo(
|
|
298
|
+
name=ep.name,
|
|
299
|
+
module_path=f"{ep.value}",
|
|
300
|
+
class_or_func=obj,
|
|
301
|
+
source="entry_point",
|
|
302
|
+
)
|
|
303
|
+
count += 1
|
|
304
|
+
except Exception as e:
|
|
305
|
+
console.print(f"[yellow]Warning: Failed to load entry point '{ep.name}': {e}[/yellow]")
|
|
306
|
+
|
|
307
|
+
except Exception:
|
|
308
|
+
pass # Entry points not available
|
|
309
|
+
|
|
310
|
+
return count
|
|
311
|
+
|
|
312
|
+
def auto_discover(self) -> int:
|
|
313
|
+
"""Auto-discover extensions from common locations.
|
|
314
|
+
|
|
315
|
+
Searches:
|
|
316
|
+
- ~/.aury/extensions.toml
|
|
317
|
+
- ./aury.extensions.toml
|
|
318
|
+
- ~/.aury/extensions/
|
|
319
|
+
- ./extensions/
|
|
320
|
+
- Python entry points
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
Total number of extensions loaded
|
|
324
|
+
"""
|
|
325
|
+
count = 0
|
|
326
|
+
|
|
327
|
+
# Config files
|
|
328
|
+
count += self.load_from_config(Path.home() / ".aury" / "extensions.toml")
|
|
329
|
+
count += self.load_from_config(Path.cwd() / "aury.extensions.toml")
|
|
330
|
+
|
|
331
|
+
# Extension directories
|
|
332
|
+
count += self.load_from_directory(Path.home() / ".aury" / "extensions")
|
|
333
|
+
count += self.load_from_directory(Path.cwd() / "extensions")
|
|
334
|
+
|
|
335
|
+
# Entry points
|
|
336
|
+
count += self.load_from_entry_points()
|
|
337
|
+
|
|
338
|
+
return count
|
|
339
|
+
|
|
340
|
+
# ========== Helper Methods ==========
|
|
341
|
+
|
|
342
|
+
def _import_from_string(self, path: str) -> Any:
|
|
343
|
+
"""Import a class or function from a string like 'module.path:ClassName'."""
|
|
344
|
+
if ":" in path:
|
|
345
|
+
module_path, attr_name = path.rsplit(":", 1)
|
|
346
|
+
else:
|
|
347
|
+
# Assume it's just a module with a default export
|
|
348
|
+
module_path = path
|
|
349
|
+
attr_name = None
|
|
350
|
+
|
|
351
|
+
module = importlib.import_module(module_path)
|
|
352
|
+
|
|
353
|
+
if attr_name:
|
|
354
|
+
return getattr(module, attr_name)
|
|
355
|
+
return module
|
|
356
|
+
|
|
357
|
+
def _get_module_path(self, obj: Any) -> str:
|
|
358
|
+
"""Get module path string for an object."""
|
|
359
|
+
if hasattr(obj, "__module__") and hasattr(obj, "__name__"):
|
|
360
|
+
return f"{obj.__module__}:{obj.__name__}"
|
|
361
|
+
elif hasattr(obj, "__module__") and hasattr(obj, "__class__"):
|
|
362
|
+
return f"{obj.__module__}:{obj.__class__.__name__}"
|
|
363
|
+
return "unknown"
|
|
364
|
+
|
|
365
|
+
def _is_agent_class(self, obj: Any) -> bool:
|
|
366
|
+
"""Check if object is an Agent class."""
|
|
367
|
+
from aury.agents.core.base import BaseAgent
|
|
368
|
+
return isinstance(obj, type) and issubclass(obj, BaseAgent)
|
|
369
|
+
|
|
370
|
+
def _is_tool(self, obj: Any) -> bool:
|
|
371
|
+
"""Check if object is a Tool."""
|
|
372
|
+
from aury.agents.tools import BaseTool
|
|
373
|
+
# Check if it's a BaseTool instance or has tool decorator attributes
|
|
374
|
+
return (
|
|
375
|
+
isinstance(obj, BaseTool) or
|
|
376
|
+
hasattr(obj, "_tool_name") or
|
|
377
|
+
hasattr(obj, "name") and hasattr(obj, "execute")
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
def _load_from_file(self, path: Path) -> int:
|
|
381
|
+
"""Load extensions from a Python file."""
|
|
382
|
+
count = 0
|
|
383
|
+
|
|
384
|
+
# Add parent directory to path
|
|
385
|
+
sys.path.insert(0, str(path.parent))
|
|
386
|
+
|
|
387
|
+
try:
|
|
388
|
+
spec = importlib.util.spec_from_file_location(path.stem, path)
|
|
389
|
+
module = importlib.util.module_from_spec(spec)
|
|
390
|
+
spec.loader.exec_module(module)
|
|
391
|
+
|
|
392
|
+
# Find agents and tools
|
|
393
|
+
for name in dir(module):
|
|
394
|
+
if name.startswith("_"):
|
|
395
|
+
continue
|
|
396
|
+
|
|
397
|
+
obj = getattr(module, name)
|
|
398
|
+
|
|
399
|
+
if self._is_agent_class(obj):
|
|
400
|
+
agent_name = getattr(obj, "name", name.lower())
|
|
401
|
+
self._agents[agent_name] = ExtensionInfo(
|
|
402
|
+
name=agent_name,
|
|
403
|
+
module_path=f"{path}:{name}",
|
|
404
|
+
class_or_func=obj,
|
|
405
|
+
description=getattr(obj, "description", ""),
|
|
406
|
+
source="file",
|
|
407
|
+
)
|
|
408
|
+
count += 1
|
|
409
|
+
|
|
410
|
+
elif self._is_tool(obj):
|
|
411
|
+
tool_name = getattr(obj, "name", getattr(obj, "_tool_name", name.lower()))
|
|
412
|
+
self._tools[tool_name] = ExtensionInfo(
|
|
413
|
+
name=tool_name,
|
|
414
|
+
module_path=f"{path}:{name}",
|
|
415
|
+
class_or_func=obj,
|
|
416
|
+
description=getattr(obj, "description", ""),
|
|
417
|
+
source="file",
|
|
418
|
+
)
|
|
419
|
+
count += 1
|
|
420
|
+
|
|
421
|
+
finally:
|
|
422
|
+
sys.path.pop(0)
|
|
423
|
+
|
|
424
|
+
return count
|
|
425
|
+
|
|
426
|
+
def clear(self) -> None:
|
|
427
|
+
"""Clear all registered extensions."""
|
|
428
|
+
self._agents.clear()
|
|
429
|
+
self._tools.clear()
|
|
430
|
+
self._loaded_configs.clear()
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
# Global registry instance
|
|
434
|
+
registry = ExtensionRegistry()
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# Convenience functions
|
|
438
|
+
def register_agent(name: str, agent_class: type["BaseAgent"], description: str = "") -> None:
|
|
439
|
+
"""Register a custom Agent class globally."""
|
|
440
|
+
registry.register_agent(name, agent_class, description)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def register_tool(name: str, tool: "BaseTool | Callable", description: str = "") -> None:
|
|
444
|
+
"""Register a custom Tool globally."""
|
|
445
|
+
registry.register_tool(name, tool, description)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def get_agent(name: str) -> type["BaseAgent"] | None:
|
|
449
|
+
"""Get a registered Agent by name."""
|
|
450
|
+
return registry.get_agent(name)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def get_tool(name: str) -> "BaseTool | Callable | None":
|
|
454
|
+
"""Get a registered Tool by name."""
|
|
455
|
+
return registry.get_tool(name)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def auto_discover() -> int:
|
|
459
|
+
"""Auto-discover all extensions."""
|
|
460
|
+
return registry.auto_discover()
|
aury/agents/cli/main.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Aury Agent CLI 主入口
|
|
4
|
+
======================
|
|
5
|
+
|
|
6
|
+
使用方式:
|
|
7
|
+
aury-agent chat # 启动交互式对话
|
|
8
|
+
aury-agent workflow run # 运行 Workflow
|
|
9
|
+
aury-agent session list # 列出会话
|
|
10
|
+
aury-agent config show # 显示配置
|
|
11
|
+
"""
|
|
12
|
+
import asyncio
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
import typer
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
from rich.panel import Panel
|
|
20
|
+
|
|
21
|
+
from aury.agents.cli.chat import chat_command
|
|
22
|
+
from aury.agents.cli.workflow import workflow_app
|
|
23
|
+
from aury.agents.cli.session import session_app
|
|
24
|
+
from aury.agents.cli.config import config_app, load_config
|
|
25
|
+
|
|
26
|
+
# 创建主应用
|
|
27
|
+
app = typer.Typer(
|
|
28
|
+
name="aury-agent",
|
|
29
|
+
help="Aury Agent 命令行工具 - AI Agent 框架",
|
|
30
|
+
add_completion=True,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# 注册子命令
|
|
34
|
+
app.add_typer(workflow_app, name="workflow", help="Workflow 相关命令")
|
|
35
|
+
app.add_typer(session_app, name="session", help="会话管理命令")
|
|
36
|
+
app.add_typer(config_app, name="config", help="配置管理命令")
|
|
37
|
+
|
|
38
|
+
# Rich console 用于美化输出
|
|
39
|
+
console = Console()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command()
|
|
43
|
+
def chat(
|
|
44
|
+
agent: Optional[str] = typer.Option(
|
|
45
|
+
None,
|
|
46
|
+
"--agent", "-a",
|
|
47
|
+
help="要使用的 Agent 名称或路径",
|
|
48
|
+
),
|
|
49
|
+
session_id: Optional[str] = typer.Option(
|
|
50
|
+
None,
|
|
51
|
+
"--session", "-s",
|
|
52
|
+
help="会话 ID,用于恢复之前的对话",
|
|
53
|
+
),
|
|
54
|
+
config_file: Optional[Path] = typer.Option(
|
|
55
|
+
None,
|
|
56
|
+
"--config", "-c",
|
|
57
|
+
help="配置文件路径",
|
|
58
|
+
),
|
|
59
|
+
verbose: bool = typer.Option(
|
|
60
|
+
False,
|
|
61
|
+
"--verbose", "-v",
|
|
62
|
+
help="显示详细输出",
|
|
63
|
+
),
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
启动交互式对话。
|
|
67
|
+
|
|
68
|
+
示例:
|
|
69
|
+
aury-agent chat
|
|
70
|
+
aury-agent chat --agent my_agent
|
|
71
|
+
aury-agent chat --session session-123 --verbose
|
|
72
|
+
"""
|
|
73
|
+
# 加载配置
|
|
74
|
+
cfg = load_config(config_file)
|
|
75
|
+
|
|
76
|
+
# 运行交互式对话
|
|
77
|
+
asyncio.run(chat_command(
|
|
78
|
+
agent_name=agent,
|
|
79
|
+
session_id=session_id,
|
|
80
|
+
config=cfg,
|
|
81
|
+
verbose=verbose,
|
|
82
|
+
))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@app.command()
|
|
86
|
+
def run(
|
|
87
|
+
script: Path = typer.Argument(
|
|
88
|
+
...,
|
|
89
|
+
help="要运行的 Agent 脚本路径",
|
|
90
|
+
),
|
|
91
|
+
input_data: Optional[str] = typer.Option(
|
|
92
|
+
None,
|
|
93
|
+
"--input", "-i",
|
|
94
|
+
help="输入数据(JSON 格式)",
|
|
95
|
+
),
|
|
96
|
+
config_file: Optional[Path] = typer.Option(
|
|
97
|
+
None,
|
|
98
|
+
"--config", "-c",
|
|
99
|
+
help="配置文件路径",
|
|
100
|
+
),
|
|
101
|
+
):
|
|
102
|
+
"""
|
|
103
|
+
运行 Agent 脚本。
|
|
104
|
+
|
|
105
|
+
示例:
|
|
106
|
+
aury-agent run agent.py
|
|
107
|
+
aury-agent run agent.py --input '{"query": "hello"}'
|
|
108
|
+
"""
|
|
109
|
+
import json
|
|
110
|
+
import importlib.util
|
|
111
|
+
|
|
112
|
+
# 加载配置
|
|
113
|
+
cfg = load_config(config_file)
|
|
114
|
+
|
|
115
|
+
# 加载脚本
|
|
116
|
+
if not script.exists():
|
|
117
|
+
console.print(f"[red]错误: 脚本文件不存在: {script}[/red]")
|
|
118
|
+
raise typer.Exit(1)
|
|
119
|
+
|
|
120
|
+
# 解析输入
|
|
121
|
+
input_dict = {}
|
|
122
|
+
if input_data:
|
|
123
|
+
try:
|
|
124
|
+
input_dict = json.loads(input_data)
|
|
125
|
+
except json.JSONDecodeError as e:
|
|
126
|
+
console.print(f"[red]错误: 无效的 JSON 输入: {e}[/red]")
|
|
127
|
+
raise typer.Exit(1)
|
|
128
|
+
|
|
129
|
+
console.print(f"[green]运行脚本: {script}[/green]")
|
|
130
|
+
|
|
131
|
+
# 动态导入并运行
|
|
132
|
+
spec = importlib.util.spec_from_file_location("agent_script", script)
|
|
133
|
+
module = importlib.util.module_from_spec(spec)
|
|
134
|
+
spec.loader.exec_module(module)
|
|
135
|
+
|
|
136
|
+
# 查找并运行 main 函数
|
|
137
|
+
if hasattr(module, "main"):
|
|
138
|
+
asyncio.run(module.main())
|
|
139
|
+
else:
|
|
140
|
+
console.print("[yellow]警告: 脚本中没有 main 函数[/yellow]")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@app.command()
|
|
144
|
+
def version():
|
|
145
|
+
"""显示版本信息。"""
|
|
146
|
+
from aury.agents import __version__
|
|
147
|
+
|
|
148
|
+
console.print(Panel(
|
|
149
|
+
f"[bold blue]Aury Agent Framework[/bold blue]\n"
|
|
150
|
+
f"版本: {__version__}",
|
|
151
|
+
title="版本信息",
|
|
152
|
+
))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@app.command()
|
|
156
|
+
def info():
|
|
157
|
+
"""显示系统信息和配置状态。"""
|
|
158
|
+
import platform
|
|
159
|
+
|
|
160
|
+
console.print(Panel(
|
|
161
|
+
f"[bold]系统信息[/bold]\n"
|
|
162
|
+
f"Python: {platform.python_version()}\n"
|
|
163
|
+
f"平台: {platform.platform()}\n\n"
|
|
164
|
+
f"[bold]Aury Agent[/bold]\n"
|
|
165
|
+
f"配置目录: ~/.aury/\n"
|
|
166
|
+
f"会话目录: ~/.aury/sessions/",
|
|
167
|
+
title="系统信息",
|
|
168
|
+
))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@app.callback()
|
|
172
|
+
def main_callback(
|
|
173
|
+
ctx: typer.Context,
|
|
174
|
+
):
|
|
175
|
+
"""
|
|
176
|
+
Aury Agent 命令行工具。
|
|
177
|
+
|
|
178
|
+
使用 --help 查看各子命令的帮助信息。
|
|
179
|
+
"""
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def main():
|
|
184
|
+
"""CLI 入口点。"""
|
|
185
|
+
app()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
if __name__ == "__main__":
|
|
189
|
+
main()
|