zett_agent.extensions.internal_message 源代码
"""Priority inbox for extension-generated instructions."""
from __future__ import annotations
from collections import deque
from dataclasses import dataclass, field
from threading import Lock
from typing import TYPE_CHECKING
from ..exceptions import AgentProtocolError
from ..messages import AgentMessage
from .base import AgentExtension
from .events import ExtensionEvent, InternalMessageEvent
from .external import ExternalEvent
if TYPE_CHECKING:
from ..agent import AgentRunConfig, AgentRunContext
INTERNAL_MESSAGE_EVENT_NAME = "internal_message"
@dataclass(slots=True)
class _Inbox:
context: AgentRunContext
messages: deque[AgentMessage] = field(default_factory=deque)
accepting: bool = True
[文档]
class InternalMessageExtension(AgentExtension):
"""Receive internal instructions from publish() or external event delivery.
Examples:
Usage::
await context.publish(InternalMessageEvent(AgentMessage(content="Check the result.")))
External callers use name="internal_message" and a payload containing
session_id, content, and optionally request_id. The Agent owns the per-run
processing limit. Each new user input starts a separate run or stream.
"""
def __init__(self) -> None:
self._inboxes: dict[AgentRunContext, _Inbox] = {}
self._lock = Lock()
[文档]
def open(self, context: AgentRunContext) -> None:
"""Open a request inbox before lifecycle hooks run."""
with self._lock:
if context in self._inboxes:
raise AgentProtocolError("Internal inbox is already active")
self._inboxes[context] = _Inbox(context)
[文档]
def close(self, context: AgentRunContext) -> None:
"""Release queued input on every exit, including broken cleanup hooks."""
with self._lock:
self._inboxes.pop(context, None)
[文档]
def accept(self, config: AgentRunConfig | None, event: ExternalEvent) -> bool:
"""Accept internal text for an unambiguously identified active request."""
if event.name != INTERNAL_MESSAGE_EVENT_NAME:
return False
if config is None:
return False
session_id = config.session_id
request_id = config.request_id
content = event.payload.get("content")
if not isinstance(session_id, str) or not session_id.strip():
return False
if request_id is not None and (not isinstance(request_id, str) or not request_id.strip()):
return False
if not isinstance(content, str) or not content.strip():
return False
with self._lock:
matches = [
inbox
for inbox in self._inboxes.values()
if inbox.accepting
and inbox.context.session_id == session_id
and (request_id is None or inbox.context.config.request_id == request_id)
]
if len(matches) != 1:
return False
matches[0].messages.append(AgentMessage(content=content))
return True
[文档]
async def on_event(self, context: AgentRunContext, event: ExtensionEvent) -> None:
"""Append a typed internal instruction to its originating request."""
if isinstance(event, InternalMessageEvent):
with self._lock:
inbox = self._inboxes.get(context)
if inbox is None or not inbox.accepting:
raise AgentProtocolError("Internal inbox is closed")
inbox.messages.append(event.message)
[文档]
def next_message(self, context: AgentRunContext) -> AgentMessage | None:
"""Take the next internal instruction, or atomically close an empty inbox."""
with self._lock:
inbox = self._inboxes[context]
if inbox.messages:
return inbox.messages.popleft()
inbox.accepting = False
return None