"""Extension system types and interfaces.
Extensions are Python modules that can:
- Subscribe to agent lifecycle events
- Register custom LLM-callable tools
- Register custom commands
- Access agent context and UI primitives
"""
from dataclasses import dataclass
from enum import Enum
from typing import (TYPE_CHECKING, Any, Awaitable, Callable, Dict, List,
Optional, Protocol, TypeVar, Union)
if TYPE_CHECKING:
from ..core.agent import Agent
from ..tools.base import Tool
# ============================================================================
# Events
# ============================================================================
[docs]
class EventType(str, Enum):
"""Agent lifecycle event types."""
# Session events
SESSION_START = "session_start"
SESSION_END = "session_end"
# Agent events
AGENT_START = "agent_start"
AGENT_END = "agent_end"
TURN_START = "turn_start"
TURN_END = "turn_end"
# Tool events
TOOL_CALL = "tool_call"
TOOL_RESULT = "tool_result"
# Input event
USER_INPUT = "user_input"
[docs]
@dataclass
class Event:
"""Base event class."""
type: EventType
data: Dict[str, Any]
[docs]
@dataclass
class SessionStartEvent(Event):
"""Fired when session starts."""
[docs]
def __init__(self):
super().__init__(EventType.SESSION_START, {})
[docs]
@dataclass
class SessionEndEvent(Event):
"""Fired when session ends."""
[docs]
def __init__(self):
super().__init__(EventType.SESSION_END, {})
[docs]
@dataclass
class AgentStartEvent(Event):
"""Fired when agent loop starts."""
[docs]
def __init__(self, prompt: str):
super().__init__(EventType.AGENT_START, {"prompt": prompt})
@property
def prompt(self) -> str:
return self.data["prompt"]
[docs]
@dataclass
class AgentEndEvent(Event):
"""Fired when agent loop ends."""
[docs]
def __init__(self, messages: List[Any]):
super().__init__(EventType.AGENT_END, {"messages": messages})
@property
def messages(self) -> List[Any]:
return self.data["messages"]
[docs]
@dataclass
class TurnStartEvent(Event):
"""Fired at the start of each turn."""
[docs]
def __init__(self, turn_index: int):
super().__init__(EventType.TURN_START, {"turn_index": turn_index})
@property
def turn_index(self) -> int:
return self.data["turn_index"]
[docs]
@dataclass
class TurnEndEvent(Event):
"""Fired at the end of each turn."""
[docs]
def __init__(self, turn_index: int, message: Any):
super().__init__(EventType.TURN_END, {
"turn_index": turn_index,
"message": message
})
@property
def turn_index(self) -> int:
return self.data["turn_index"]
@property
def message(self) -> Any:
return self.data["message"]
# ============================================================================
# Extension Context
# ============================================================================
[docs]
class ExtensionContext:
"""Context passed to extension event handlers.
Provides access to agent state and operations.
"""
[docs]
def __init__(
self,
agent: 'Agent',
cwd: str,
tools: List['Tool']
):
self.agent = agent
self.cwd = cwd
self.tools = tools
@property
def messages(self) -> List[Any]:
"""Get current conversation messages."""
return self.agent.messages
[docs]
def notify(self, message: str, type: str = "info") -> None:
"""Show a notification to the user."""
# Simple implementation - print to console
prefix = {
"info": "ℹ️ ",
"warning": "⚠️ ",
"error": "❌ "
}.get(type, "")
print(f"{prefix}{message}")
# ============================================================================
# Tool Definition
# ============================================================================
# ============================================================================
# Command Definition
# ============================================================================
[docs]
@dataclass
class CommandDefinition:
"""Definition for a custom command."""
name: str
description: str
# async function(ctx, args) -> None
handler: Callable[[ExtensionContext, str], Awaitable[None]]
# ============================================================================
# Event Handler Types
# ============================================================================
EventHandler = Callable[[Event, ExtensionContext], Awaitable[None]]
# ============================================================================
# Extension API
# ============================================================================
[docs]
class ExtensionAPI(Protocol):
"""API provided to extensions during setup.
Extensions use this to register tools, commands, and event handlers.
"""
[docs]
def register_command(self, command: CommandDefinition) -> None:
"""Register a custom command."""
...
[docs]
def on(self, event_type: EventType, handler: EventHandler) -> None:
"""Subscribe to an event."""
...
[docs]
def notify(self, message: str, type: str = "info") -> None:
"""Show a notification."""
...
# ============================================================================
# Extension Factory
# ============================================================================
ExtensionSetup = Callable[[ExtensionAPI], Awaitable[None]]
# ============================================================================
# Loaded Extension
# ============================================================================
[docs]
@dataclass
class LoadedExtension:
"""A loaded extension with its registered items."""
path: str
name: str
tools: Dict[str, ToolDefinition]
commands: Dict[str, CommandDefinition]
handlers: Dict[EventType, List[EventHandler]]