feat(plugins): improve plugin creation devex with @hook and @tool decorators#1740
feat(plugins): improve plugin creation devex with @hook and @tool decorators#1740
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
d652f8e to
00f08ea
Compare
00f08ea to
655ffd3
Compare
655ffd3 to
f2f74e4
Compare
|
/strands don't say mutable or immutable for in the comment. just say this is a list of hooks/tools the plugin provides, autogenerated from decorators |
|
/strands for discovering decorated methods, instead of using dir, let's use MRO (method resolution order), so we keep the order of hooks as they are defined in the class. See example code def _discover_decorated_methods(self) -> None:
"""Scan class for @hook and @tool decorated methods in declaration order."""
seen = set()
# Walk MRO so parent class hooks come first, child overrides win
for cls in reversed(type(self).__mro__):
for name, attr in cls.__dict__.items():
if name in seen:
continue
seen.add(name)
# Get the bound method from self
try:
bound = getattr(self, name)
except Exception:
continue
if hasattr(bound, "_hook_event_types") and callable(bound):
self._hooks.append(bound)
if isinstance(bound, DecoratedFunctionTool):
self._tools.append(bound) |
|
Updated hook/tool discovery to use MRO instead of |
- Create @hook decorator for declarative hook registration in plugins - Convert Plugin from Protocol to base class (breaking change) - Add auto-discovery of @hook and @tool decorated methods in Plugin.__init__() - Add auto-registration of hooks and tools in Plugin.init_plugin() - Support union types for multiple event types (e.g., BeforeModelCallEvent | AfterModelCallEvent) - Export hook from strands.plugins and strands namespaces - Update existing tests to use inheritance-based approach - Add comprehensive test coverage for new functionality BREAKING CHANGE: Plugin is now a base class instead of a Protocol. Existing plugins must inherit from Plugin instead of just implementing the protocol.
…properties Address PR feedback: - Add public 'hooks' and 'tools' properties returning tuples for user customization - Move hook/tool auto-registration from Plugin.init_plugin() to _PluginRegistry.add_and_init() - Remove need for super().init_plugin(agent) - users only implement custom logic - Update steering handler to use new simpler pattern - Update all tests to use registry-based registration This simplifies plugin development: - Before: Users had to call super().init_plugin(agent) for auto-registration - After: init_plugin() is purely for custom logic, registry handles auto-registration
Address additional PR feedback: - Make hooks and tools properties return mutable lists for filtering/customization - Fix type annotation: _hook_event_types is list[type[TEvent]] not list[TEvent] - Export @hook from top-level strands package (from strands import hook) - Fix docstring typo: 'argument' -> 'attribute' - Add tests for filtering hooks and tools
- Fix Plugin class docstring: 'read-only' -> 'mutable for filtering' - Add _type_inference.py to AGENTS.md hooks directory listing
Remove mutable/immutable language, just describe what they are: - List of hooks/tools the plugin provides - Auto-discovered from decorated methods
- Replace dir() with __mro__ iteration to maintain declaration order - Parent class hooks come first, child overrides win - Add test verifying definition order is preserved (not alphabetical)
784016a to
703cece
Compare
|
Review Update - Commit All feedback from @mkmeral has been addressed:
Architecture ImprovementsThe refactoring brought a cleaner design:
Recommendation: ✅ Ready for approval - Clean implementation with all feedback incorporated. |
Motivation
Currently, plugin authors must manually register hooks in their
init_plugin()method, which is verbose and error-prone:This PR enables declarative hook registration using a
@hookdecorator, making plugin development more intuitive and reducing boilerplate:Resolves: #1739
Public API Changes
New
@hookdecoratorThe
@hookdecorator marks methods for automatic registration: