rayhpeng 9d0a42c1fb refactor(runtime): restructure runs module with new execution architecture
Major refactoring of deerflow/runtime/:
- runs/callbacks/ - new callback system (builder, events, title, tokens)
- runs/internal/ - execution internals (executor, supervisor, stream_logic, registry)
- runs/internal/execution/ - execution artifacts and events handling
- runs/facade.py - high-level run facade
- runs/observer.py - run observation protocol
- runs/types.py - type definitions
- runs/store/ - simplified store interfaces (create, delete, query, event)

Refactor stream_bridge/:
- Replace old providers with contract.py and exceptions.py
- Remove async_provider.py, base.py, memory.py

Add documentation:
- README.md and README_zh.md for runtime module

Remove deprecated:
- manager.py moved to internal/
- worker.py, schemas.py
- user_context.py

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-22 11:28:01 +08:00

118 lines
3.1 KiB
Python

"""Request/task-scoped actor context for runtime-facing user isolation.
This module defines a runtime-owned context bridge that lower layers can
depend on without importing the auth plugin. The app/auth boundary maps
``request.user`` into :class:`ActorContext` and binds it before entering
runtime-facing code.
"""
from __future__ import annotations
from contextvars import ContextVar, Token
from dataclasses import dataclass
from typing import Final
@dataclass(frozen=True)
class ActorContext:
user_id: str | None = None
# Future extension points:
# subject_id: str | None = None
# tenant_id: str | None = None
# scopes: frozenset[str] = frozenset()
# auth_source: str | None = None
_current_actor: Final[ContextVar[ActorContext | None]] = ContextVar(
"deerflow_actor_context",
default=None,
)
def bind_actor_context(actor: ActorContext) -> Token[ActorContext | None]:
"""Bind the current actor for this async task."""
return _current_actor.set(actor)
def reset_actor_context(token: Token[ActorContext | None]) -> None:
"""Restore the actor context captured by ``token``."""
_current_actor.reset(token)
def get_actor_context() -> ActorContext | None:
"""Return the current actor context, or ``None`` if unset."""
return _current_actor.get()
def require_actor_context() -> ActorContext:
"""Return the current actor context, or raise if unset."""
actor = _current_actor.get()
if actor is None:
raise RuntimeError("runtime accessed without actor context")
return actor
DEFAULT_USER_ID: Final[str] = "default"
def get_effective_user_id() -> str:
"""Return the effective user id, or ``DEFAULT_USER_ID`` if unset."""
actor = _current_actor.get()
if actor is None or actor.user_id is None:
return DEFAULT_USER_ID
return str(actor.user_id)
class _AutoSentinel:
"""Singleton marker meaning 'resolve user_id from actor context'."""
_instance: _AutoSentinel | None = None
def __new__(cls) -> _AutoSentinel:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __repr__(self) -> str:
return "<AUTO>"
AUTO: Final[_AutoSentinel] = _AutoSentinel()
def resolve_user_id(
value: str | None | _AutoSentinel,
*,
method_name: str = "repository method",
) -> str | None:
"""Resolve a repository ``user_id`` argument against the current actor."""
if isinstance(value, _AutoSentinel):
actor = _current_actor.get()
if actor is None or actor.user_id is None:
raise RuntimeError(
f"{method_name} called with user_id=AUTO but no actor context is set; "
"pass an explicit user_id, bind ActorContext at the app/runtime boundary, "
"or opt out with user_id=None for migration/CLI paths."
)
return str(actor.user_id)
return value
__all__ = [
"AUTO",
"ActorContext",
"DEFAULT_USER_ID",
"bind_actor_context",
"get_actor_context",
"get_effective_user_id",
"require_actor_context",
"reset_actor_context",
"resolve_user_id",
]