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.
Files changed (149) hide show
  1. aury/__init__.py +2 -0
  2. aury/agents/__init__.py +55 -0
  3. aury/agents/a2a/__init__.py +168 -0
  4. aury/agents/backends/__init__.py +196 -0
  5. aury/agents/backends/artifact/__init__.py +9 -0
  6. aury/agents/backends/artifact/memory.py +130 -0
  7. aury/agents/backends/artifact/types.py +133 -0
  8. aury/agents/backends/code/__init__.py +65 -0
  9. aury/agents/backends/file/__init__.py +11 -0
  10. aury/agents/backends/file/local.py +66 -0
  11. aury/agents/backends/file/types.py +40 -0
  12. aury/agents/backends/invocation/__init__.py +8 -0
  13. aury/agents/backends/invocation/memory.py +81 -0
  14. aury/agents/backends/invocation/types.py +110 -0
  15. aury/agents/backends/memory/__init__.py +8 -0
  16. aury/agents/backends/memory/memory.py +179 -0
  17. aury/agents/backends/memory/types.py +136 -0
  18. aury/agents/backends/message/__init__.py +9 -0
  19. aury/agents/backends/message/memory.py +122 -0
  20. aury/agents/backends/message/types.py +124 -0
  21. aury/agents/backends/sandbox.py +275 -0
  22. aury/agents/backends/session/__init__.py +8 -0
  23. aury/agents/backends/session/memory.py +93 -0
  24. aury/agents/backends/session/types.py +124 -0
  25. aury/agents/backends/shell/__init__.py +11 -0
  26. aury/agents/backends/shell/local.py +110 -0
  27. aury/agents/backends/shell/types.py +55 -0
  28. aury/agents/backends/shell.py +209 -0
  29. aury/agents/backends/snapshot/__init__.py +19 -0
  30. aury/agents/backends/snapshot/git.py +95 -0
  31. aury/agents/backends/snapshot/hybrid.py +125 -0
  32. aury/agents/backends/snapshot/memory.py +86 -0
  33. aury/agents/backends/snapshot/types.py +59 -0
  34. aury/agents/backends/state/__init__.py +29 -0
  35. aury/agents/backends/state/composite.py +49 -0
  36. aury/agents/backends/state/file.py +57 -0
  37. aury/agents/backends/state/memory.py +52 -0
  38. aury/agents/backends/state/sqlite.py +262 -0
  39. aury/agents/backends/state/types.py +178 -0
  40. aury/agents/backends/subagent/__init__.py +165 -0
  41. aury/agents/cli/__init__.py +41 -0
  42. aury/agents/cli/chat.py +239 -0
  43. aury/agents/cli/config.py +236 -0
  44. aury/agents/cli/extensions.py +460 -0
  45. aury/agents/cli/main.py +189 -0
  46. aury/agents/cli/session.py +337 -0
  47. aury/agents/cli/workflow.py +276 -0
  48. aury/agents/context_providers/__init__.py +66 -0
  49. aury/agents/context_providers/artifact.py +299 -0
  50. aury/agents/context_providers/base.py +177 -0
  51. aury/agents/context_providers/memory.py +70 -0
  52. aury/agents/context_providers/message.py +130 -0
  53. aury/agents/context_providers/skill.py +50 -0
  54. aury/agents/context_providers/subagent.py +46 -0
  55. aury/agents/context_providers/tool.py +68 -0
  56. aury/agents/core/__init__.py +83 -0
  57. aury/agents/core/base.py +573 -0
  58. aury/agents/core/context.py +797 -0
  59. aury/agents/core/context_builder.py +303 -0
  60. aury/agents/core/event_bus/__init__.py +15 -0
  61. aury/agents/core/event_bus/bus.py +203 -0
  62. aury/agents/core/factory.py +169 -0
  63. aury/agents/core/isolator.py +97 -0
  64. aury/agents/core/logging.py +95 -0
  65. aury/agents/core/parallel.py +194 -0
  66. aury/agents/core/runner.py +139 -0
  67. aury/agents/core/services/__init__.py +5 -0
  68. aury/agents/core/services/file_session.py +144 -0
  69. aury/agents/core/services/message.py +53 -0
  70. aury/agents/core/services/session.py +53 -0
  71. aury/agents/core/signals.py +109 -0
  72. aury/agents/core/state.py +363 -0
  73. aury/agents/core/types/__init__.py +107 -0
  74. aury/agents/core/types/action.py +176 -0
  75. aury/agents/core/types/artifact.py +135 -0
  76. aury/agents/core/types/block.py +736 -0
  77. aury/agents/core/types/message.py +350 -0
  78. aury/agents/core/types/recall.py +144 -0
  79. aury/agents/core/types/session.py +257 -0
  80. aury/agents/core/types/subagent.py +154 -0
  81. aury/agents/core/types/tool.py +205 -0
  82. aury/agents/eval/__init__.py +331 -0
  83. aury/agents/hitl/__init__.py +57 -0
  84. aury/agents/hitl/ask_user.py +242 -0
  85. aury/agents/hitl/compaction.py +230 -0
  86. aury/agents/hitl/exceptions.py +87 -0
  87. aury/agents/hitl/permission.py +617 -0
  88. aury/agents/hitl/revert.py +216 -0
  89. aury/agents/llm/__init__.py +31 -0
  90. aury/agents/llm/adapter.py +367 -0
  91. aury/agents/llm/openai.py +294 -0
  92. aury/agents/llm/provider.py +476 -0
  93. aury/agents/mcp/__init__.py +153 -0
  94. aury/agents/memory/__init__.py +46 -0
  95. aury/agents/memory/compaction.py +394 -0
  96. aury/agents/memory/manager.py +465 -0
  97. aury/agents/memory/processor.py +177 -0
  98. aury/agents/memory/store.py +187 -0
  99. aury/agents/memory/types.py +137 -0
  100. aury/agents/messages/__init__.py +40 -0
  101. aury/agents/messages/config.py +47 -0
  102. aury/agents/messages/raw_store.py +224 -0
  103. aury/agents/messages/store.py +118 -0
  104. aury/agents/messages/types.py +88 -0
  105. aury/agents/middleware/__init__.py +31 -0
  106. aury/agents/middleware/base.py +341 -0
  107. aury/agents/middleware/chain.py +342 -0
  108. aury/agents/middleware/message.py +129 -0
  109. aury/agents/middleware/message_container.py +126 -0
  110. aury/agents/middleware/raw_message.py +153 -0
  111. aury/agents/middleware/truncation.py +139 -0
  112. aury/agents/middleware/types.py +81 -0
  113. aury/agents/plugin.py +162 -0
  114. aury/agents/react/__init__.py +4 -0
  115. aury/agents/react/agent.py +1923 -0
  116. aury/agents/sandbox/__init__.py +23 -0
  117. aury/agents/sandbox/local.py +239 -0
  118. aury/agents/sandbox/remote.py +200 -0
  119. aury/agents/sandbox/types.py +115 -0
  120. aury/agents/skill/__init__.py +16 -0
  121. aury/agents/skill/loader.py +180 -0
  122. aury/agents/skill/types.py +83 -0
  123. aury/agents/tool/__init__.py +39 -0
  124. aury/agents/tool/builtin/__init__.py +23 -0
  125. aury/agents/tool/builtin/ask_user.py +155 -0
  126. aury/agents/tool/builtin/bash.py +107 -0
  127. aury/agents/tool/builtin/delegate.py +726 -0
  128. aury/agents/tool/builtin/edit.py +121 -0
  129. aury/agents/tool/builtin/plan.py +277 -0
  130. aury/agents/tool/builtin/read.py +91 -0
  131. aury/agents/tool/builtin/thinking.py +111 -0
  132. aury/agents/tool/builtin/yield_result.py +130 -0
  133. aury/agents/tool/decorator.py +252 -0
  134. aury/agents/tool/set.py +204 -0
  135. aury/agents/usage/__init__.py +12 -0
  136. aury/agents/usage/tracker.py +236 -0
  137. aury/agents/workflow/__init__.py +85 -0
  138. aury/agents/workflow/adapter.py +268 -0
  139. aury/agents/workflow/dag.py +116 -0
  140. aury/agents/workflow/dsl.py +575 -0
  141. aury/agents/workflow/executor.py +659 -0
  142. aury/agents/workflow/expression.py +136 -0
  143. aury/agents/workflow/parser.py +182 -0
  144. aury/agents/workflow/state.py +145 -0
  145. aury/agents/workflow/types.py +86 -0
  146. aury_agent-0.0.4.dist-info/METADATA +90 -0
  147. aury_agent-0.0.4.dist-info/RECORD +149 -0
  148. aury_agent-0.0.4.dist-info/WHEEL +4 -0
  149. aury_agent-0.0.4.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,130 @@
1
+ """Yield result tool - return control to parent agent.
2
+
3
+ Only available in DELEGATED mode sub-agents.
4
+ Dynamically injected when control is transferred.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ from ...core.logging import tool_logger as logger
11
+ from ...core.types.tool import BaseTool, ToolContext, ToolResult
12
+ from ...core.types.block import BlockEvent, BlockKind, BlockOp
13
+
14
+
15
+ class YieldResultTool(BaseTool):
16
+ """Return control to parent agent with results.
17
+
18
+ This tool is only available to sub-agents running in DELEGATED mode.
19
+ It is dynamically injected when control is transferred via delegate().
20
+
21
+ When called:
22
+ 1. Pops current frame from control_stack
23
+ 2. Passes result to parent agent
24
+ 3. Resumes parent invocation
25
+ """
26
+
27
+ _name = "yield_result"
28
+
29
+ def __init__(self, parent_invocation_id: str):
30
+ """Initialize with parent context.
31
+
32
+ Args:
33
+ parent_invocation_id: ID of the parent invocation to return to
34
+ """
35
+ self.parent_invocation_id = parent_invocation_id
36
+
37
+ @property
38
+ def name(self) -> str:
39
+ return self._name
40
+
41
+ @property
42
+ def description(self) -> str:
43
+ return """Return control to the parent agent with task results.
44
+
45
+ Use this when the delegated task is complete and you want to
46
+ return the results to the agent that delegated to you.
47
+
48
+ This will end your current session and resume the parent."""
49
+
50
+ @property
51
+ def parameters(self) -> dict[str, Any]:
52
+ return {
53
+ "type": "object",
54
+ "properties": {
55
+ "result": {
56
+ "type": "string",
57
+ "description": "Summary of what was accomplished",
58
+ },
59
+ "data": {
60
+ "type": "object",
61
+ "description": "Structured data to pass back to parent",
62
+ },
63
+ "status": {
64
+ "type": "string",
65
+ "enum": ["completed", "failed", "cancelled"],
66
+ "description": "Status of the delegated task",
67
+ "default": "completed",
68
+ },
69
+ },
70
+ "required": ["result"],
71
+ }
72
+
73
+ async def execute(
74
+ self,
75
+ params: dict[str, Any],
76
+ ctx: ToolContext,
77
+ ) -> ToolResult:
78
+ """Execute yield - return to parent."""
79
+ result_text = params.get("result", "")
80
+ data = params.get("data", {})
81
+ status = params.get("status", "completed")
82
+
83
+ logger.info(
84
+ "Yielding result to parent",
85
+ extra={
86
+ "parent_invocation_id": self.parent_invocation_id,
87
+ "status": status,
88
+ },
89
+ )
90
+
91
+ # Emit YIELD block
92
+ await self._emit_yield_block(ctx, result_text, data, status)
93
+
94
+ # Note: Real implementation would:
95
+ # 1. Pop current frame from session.control_stack
96
+ # 2. Serialize result for parent
97
+ # 3. Update state to trigger parent resumption
98
+ # 4. End current invocation
99
+
100
+ return ToolResult(output=f"Returning to parent agent with status: {status}\nResult: {result_text}")
101
+
102
+ async def _emit_yield_block(
103
+ self,
104
+ ctx: ToolContext,
105
+ result: str,
106
+ data: dict[str, Any],
107
+ status: str,
108
+ ) -> None:
109
+ """Emit YIELD block."""
110
+ emit = getattr(ctx, 'emit', None)
111
+ if emit is None:
112
+ return
113
+
114
+ block = BlockEvent(
115
+ kind=BlockKind.YIELD,
116
+ op=BlockOp.APPLY,
117
+ data={
118
+ "result": result,
119
+ "data": data,
120
+ "status": status,
121
+ "parent_invocation_id": self.parent_invocation_id,
122
+ },
123
+ session_id=ctx.session_id,
124
+ invocation_id=ctx.invocation_id,
125
+ )
126
+
127
+ await emit(block)
128
+
129
+
130
+ __all__ = ["YieldResultTool"]
@@ -0,0 +1,252 @@
1
+ """Tool decorators for creating tools from functions."""
2
+ from __future__ import annotations
3
+
4
+ import asyncio
5
+ import inspect
6
+ from functools import wraps
7
+ from typing import Any, Callable, get_type_hints, get_origin, get_args, Union
8
+
9
+ from ..core.types.tool import BaseTool, ToolContext, ToolResult, ToolInfo
10
+
11
+
12
+ def _type_to_schema(t: type) -> dict[str, Any]:
13
+ """Convert Python type to JSON Schema."""
14
+ # Handle None/NoneType
15
+ if t is type(None):
16
+ return {"type": "null"}
17
+
18
+ # Handle Optional (Union with None)
19
+ origin = get_origin(t)
20
+ if origin is Union:
21
+ args = get_args(t)
22
+ # Filter out None
23
+ non_none = [a for a in args if a is not type(None)]
24
+ if len(non_none) == 1:
25
+ # Optional[X] -> X with nullable
26
+ schema = _type_to_schema(non_none[0])
27
+ return schema
28
+ # Union of multiple types
29
+ return {"oneOf": [_type_to_schema(a) for a in non_none]}
30
+
31
+ # Handle list
32
+ if origin is list:
33
+ args = get_args(t)
34
+ if args:
35
+ return {"type": "array", "items": _type_to_schema(args[0])}
36
+ return {"type": "array"}
37
+
38
+ # Handle dict
39
+ if origin is dict:
40
+ args = get_args(t)
41
+ if len(args) == 2:
42
+ return {
43
+ "type": "object",
44
+ "additionalProperties": _type_to_schema(args[1]),
45
+ }
46
+ return {"type": "object"}
47
+
48
+ # Basic types
49
+ type_map = {
50
+ str: {"type": "string"},
51
+ int: {"type": "integer"},
52
+ float: {"type": "number"},
53
+ bool: {"type": "boolean"},
54
+ list: {"type": "array"},
55
+ dict: {"type": "object"},
56
+ Any: {},
57
+ }
58
+
59
+ return type_map.get(t, {"type": "string"})
60
+
61
+
62
+ def _parse_docstring(docstring: str | None) -> tuple[str, dict[str, str]]:
63
+ """Parse docstring to extract description and param descriptions.
64
+
65
+ Returns:
66
+ Tuple of (main description, {param_name: param_description})
67
+ """
68
+ if not docstring:
69
+ return "", {}
70
+
71
+ lines = docstring.strip().split("\n")
72
+ description_lines = []
73
+ param_docs: dict[str, str] = {}
74
+
75
+ in_params = False
76
+ current_param = None
77
+
78
+ for line in lines:
79
+ stripped = line.strip()
80
+
81
+ # Check for param section
82
+ if stripped.lower().startswith(("args:", "arguments:", "parameters:", "params:")):
83
+ in_params = True
84
+ continue
85
+
86
+ if stripped.lower().startswith(("returns:", "return:", "raises:", "yields:")):
87
+ in_params = False
88
+ current_param = None
89
+ continue
90
+
91
+ if in_params:
92
+ # Check for param definition: "param_name: description" or "param_name (type): description"
93
+ if ":" in stripped and not stripped.startswith(" "):
94
+ parts = stripped.split(":", 1)
95
+ param_name = parts[0].strip()
96
+ # Remove type annotation if present
97
+ if "(" in param_name:
98
+ param_name = param_name.split("(")[0].strip()
99
+ param_docs[param_name] = parts[1].strip() if len(parts) > 1 else ""
100
+ current_param = param_name
101
+ elif current_param and stripped:
102
+ # Continuation of previous param description
103
+ param_docs[current_param] += " " + stripped
104
+ else:
105
+ description_lines.append(stripped)
106
+
107
+ description = " ".join(description_lines).strip()
108
+ return description, param_docs
109
+
110
+
111
+ def tool(
112
+ func: Callable | None = None,
113
+ *,
114
+ name: str | None = None,
115
+ description: str | None = None,
116
+ parameters: dict[str, Any] | None = None,
117
+ ) -> Callable[[Callable], BaseTool] | BaseTool:
118
+ """Decorator to create a Tool from a function.
119
+
120
+ The function can be sync or async. Parameters are automatically converted
121
+ to JSON Schema based on type hints, unless manually specified.
122
+
123
+ Args:
124
+ name: Tool name (defaults to function name)
125
+ description: Tool description (defaults to docstring)
126
+ parameters: Manual JSON Schema for parameters (optional)
127
+
128
+ Returns:
129
+ Decorated function as a Tool
130
+
131
+ Example:
132
+ @tool
133
+ def greet(name: str) -> str:
134
+ return f"Hello, {name}!"
135
+
136
+ @tool(name="calculator", description="Perform calculations")
137
+ def calculate(expression: str) -> str:
138
+ return str(eval(expression))
139
+
140
+ @tool(parameters={"type": "object", "properties": {...}})
141
+ def custom_tool(arg: str) -> str:
142
+ return arg
143
+ """
144
+ def decorator(fn: Callable) -> BaseTool:
145
+ tool_name = name or fn.__name__
146
+
147
+ # Parse docstring for descriptions
148
+ doc_desc, param_docs = _parse_docstring(fn.__doc__)
149
+ tool_desc = description or doc_desc or f"Tool: {tool_name}"
150
+
151
+ # Use manual parameters or auto-generate
152
+ if parameters is not None:
153
+ parameters_schema = parameters
154
+ else:
155
+ # Get type hints and signature
156
+ try:
157
+ hints = get_type_hints(fn)
158
+ except Exception:
159
+ hints = {}
160
+
161
+ sig = inspect.signature(fn)
162
+
163
+ # Build parameters schema
164
+ properties: dict[str, Any] = {}
165
+ required: list[str] = []
166
+
167
+ for param_name, param in sig.parameters.items():
168
+ # Skip special parameters
169
+ if param_name in ("self", "cls", "ctx", "context"):
170
+ continue
171
+
172
+ # Get type
173
+ param_type = hints.get(param_name, str)
174
+ schema = _type_to_schema(param_type)
175
+
176
+ # Add description from docstring
177
+ if param_name in param_docs:
178
+ schema["description"] = param_docs[param_name]
179
+
180
+ properties[param_name] = schema
181
+
182
+ # Check if required
183
+ if param.default is inspect.Parameter.empty:
184
+ required.append(param_name)
185
+
186
+ parameters_schema = {
187
+ "type": "object",
188
+ "properties": properties,
189
+ "required": required,
190
+ }
191
+
192
+ sig = inspect.signature(fn)
193
+
194
+ # Check if function accepts ctx
195
+ accepts_ctx = "ctx" in sig.parameters or "context" in sig.parameters
196
+ ctx_param_name = "ctx" if "ctx" in sig.parameters else "context"
197
+
198
+ class FunctionTool:
199
+ """Tool created from decorated function."""
200
+
201
+ @property
202
+ def name(self) -> str:
203
+ return tool_name
204
+
205
+ @property
206
+ def description(self) -> str:
207
+ return tool_desc
208
+
209
+ @property
210
+ def parameters(self) -> dict[str, Any]:
211
+ return parameters_schema
212
+
213
+ async def execute(
214
+ self,
215
+ params: dict[str, Any],
216
+ ctx: ToolContext,
217
+ ) -> ToolResult:
218
+ """Execute the wrapped function."""
219
+ try:
220
+ # Add context if function accepts it
221
+ if accepts_ctx:
222
+ params = {**params, ctx_param_name: ctx}
223
+
224
+ # Call function
225
+ result = fn(**params)
226
+
227
+ # Await if coroutine
228
+ if asyncio.iscoroutine(result):
229
+ result = await result
230
+
231
+ # Convert result to ToolResult
232
+ if isinstance(result, ToolResult):
233
+ return result
234
+
235
+ return ToolResult(output=str(result))
236
+
237
+ except Exception as e:
238
+ return ToolResult.error(str(e))
239
+
240
+ def get_info(self) -> ToolInfo:
241
+ return ToolInfo(
242
+ name=tool_name,
243
+ description=tool_desc,
244
+ parameters=parameters_schema,
245
+ )
246
+
247
+ return FunctionTool()
248
+
249
+ # Support both @tool and @tool(...) usage
250
+ if func is not None:
251
+ return decorator(func)
252
+ return decorator
@@ -0,0 +1,204 @@
1
+ """ToolSet - A collection of tools."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, Callable
5
+
6
+ from ..core.types.tool import BaseTool, ToolInfo
7
+
8
+
9
+ class ToolSet:
10
+ """A collection of tools with lookup, filtering, and merging capabilities.
11
+
12
+ ToolSet provides:
13
+ - Fast lookup by tool ID
14
+ - Lazy instantiation with caching
15
+ - Filtering (include/exclude)
16
+ - Merging multiple sets
17
+
18
+ Usage:
19
+ # From list
20
+ tools = ToolSet.from_list([tool1, tool2])
21
+
22
+ # Manual construction
23
+ tools = ToolSet()
24
+ tools.add(my_tool)
25
+
26
+ # Filtering
27
+ subset = tools.filtered(include=["read_file", "write_file"])
28
+
29
+ # Merging
30
+ combined = tools.merge(other_tools)
31
+ """
32
+
33
+ def __init__(self) -> None:
34
+ self._tools: dict[str, Callable[[], BaseTool]] = {}
35
+ self._instances: dict[str, BaseTool] = {} # Cached instances
36
+
37
+ @classmethod
38
+ def from_list(cls, tools: list[BaseTool]) -> "ToolSet":
39
+ """Create ToolSet from a list of tools.
40
+
41
+ Args:
42
+ tools: List of tool instances
43
+
44
+ Returns:
45
+ New ToolSet containing all tools
46
+ """
47
+ ts = cls()
48
+ for tool in tools:
49
+ ts.add(tool)
50
+ return ts
51
+
52
+ def add(
53
+ self,
54
+ tool: BaseTool | Callable[[], BaseTool],
55
+ replace: bool = False,
56
+ ) -> None:
57
+ """Add a tool to the set.
58
+
59
+ Args:
60
+ tool: Tool instance or factory function
61
+ replace: Allow replacing existing tool
62
+ """
63
+ # If it's a BaseTool instance, wrap it in a lambda
64
+ if isinstance(tool, BaseTool):
65
+ tool_name = tool.name
66
+ factory: Callable[[], BaseTool] = lambda t=tool: t
67
+ else:
68
+ # It's a factory, call it once to get the name
69
+ instance = tool()
70
+ tool_name = instance.name
71
+ factory = tool
72
+
73
+ if tool_name in self._tools and not replace:
74
+ raise ValueError(f"Tool already registered: {tool_name}")
75
+
76
+ self._tools[tool_name] = factory
77
+ # Clear cached instance
78
+ if tool_name in self._instances:
79
+ del self._instances[tool_name]
80
+
81
+ def remove(self, tool_id: str) -> bool:
82
+ """Remove a tool from the set.
83
+
84
+ Args:
85
+ tool_id: Tool identifier to remove
86
+
87
+ Returns:
88
+ True if tool was removed, False if not found
89
+ """
90
+ if tool_id in self._tools:
91
+ del self._tools[tool_id]
92
+ if tool_id in self._instances:
93
+ del self._instances[tool_id]
94
+ return True
95
+ return False
96
+
97
+ def get(self, tool_id: str, cached: bool = True) -> BaseTool:
98
+ """Get a tool instance.
99
+
100
+ Args:
101
+ tool_id: Tool identifier
102
+ cached: Whether to use cached instance
103
+
104
+ Returns:
105
+ BaseTool instance
106
+
107
+ Raises:
108
+ KeyError: If tool not found
109
+ """
110
+ if tool_id not in self._tools:
111
+ raise KeyError(f"Tool not found: {tool_id}")
112
+
113
+ if cached and tool_id in self._instances:
114
+ return self._instances[tool_id]
115
+
116
+ instance = self._tools[tool_id]()
117
+ if cached:
118
+ self._instances[tool_id] = instance
119
+
120
+ return instance
121
+
122
+ def has(self, tool_id: str) -> bool:
123
+ """Check if tool is registered."""
124
+ return tool_id in self._tools
125
+
126
+ def all(self, cached: bool = True) -> list[BaseTool]:
127
+ """Get all registered tools.
128
+
129
+ Args:
130
+ cached: Whether to use cached instances
131
+ """
132
+ return [self.get(tid, cached=cached) for tid in self._tools]
133
+
134
+ def all_info(self) -> list[ToolInfo]:
135
+ """Get info for all registered tools."""
136
+ return [tool.get_info() for tool in self.all()]
137
+
138
+ def ids(self) -> list[str]:
139
+ """Get all registered tool IDs."""
140
+ return list(self._tools.keys())
141
+
142
+ def filtered(
143
+ self,
144
+ include: list[str] | None = None,
145
+ exclude: list[str] | None = None,
146
+ ) -> "ToolSet":
147
+ """Create a filtered copy of this set.
148
+
149
+ Args:
150
+ include: Only include these tools (None = all)
151
+ exclude: Exclude these tools
152
+
153
+ Returns:
154
+ New ToolSet with filtered tools
155
+ """
156
+ filtered = ToolSet()
157
+
158
+ for tool_id, factory in self._tools.items():
159
+ # Check include filter
160
+ if include is not None and tool_id not in include:
161
+ continue
162
+
163
+ # Check exclude filter
164
+ if exclude is not None and tool_id in exclude:
165
+ continue
166
+
167
+ filtered._tools[tool_id] = factory
168
+
169
+ return filtered
170
+
171
+ def merge(self, other: "ToolSet", replace: bool = False) -> "ToolSet":
172
+ """Merge another ToolSet into this one.
173
+
174
+ Args:
175
+ other: ToolSet to merge
176
+ replace: Allow replacing existing tools
177
+
178
+ Returns:
179
+ Self for chaining
180
+ """
181
+ for tool_id, factory in other._tools.items():
182
+ if tool_id not in self._tools or replace:
183
+ self._tools[tool_id] = factory
184
+ return self
185
+
186
+ def copy(self) -> "ToolSet":
187
+ """Create a copy of this set."""
188
+ new = ToolSet()
189
+ new._tools = dict(self._tools)
190
+ return new
191
+
192
+ def clear(self) -> None:
193
+ """Clear all tools."""
194
+ self._tools.clear()
195
+ self._instances.clear()
196
+
197
+ def __len__(self) -> int:
198
+ return len(self._tools)
199
+
200
+ def __contains__(self, tool_id: str) -> bool:
201
+ return tool_id in self._tools
202
+
203
+ def __iter__(self):
204
+ return iter(self.all())
@@ -0,0 +1,12 @@
1
+ """Usage tracking for cost management."""
2
+ from .tracker import (
3
+ UsageType,
4
+ UsageEntry,
5
+ UsageTracker,
6
+ )
7
+
8
+ __all__ = [
9
+ "UsageType",
10
+ "UsageEntry",
11
+ "UsageTracker",
12
+ ]