mirror of
https://github.com/langgenius/dify.git
synced 2026-05-11 14:58:23 +08:00
Port the complete infrastructure for agent sandbox execution and skill system: Sandbox & Virtual Environment (core/sandbox/, core/virtual_environment/): - Sandbox entity with lifecycle management (ready/failed/cancelled states) - SandboxBuilder with fluent API for configuring providers - 5 VM providers: Local, SSH, Docker, E2B, AWS CodeInterpreter - VirtualEnvironment base with command execution, file transfer, transport layers - Channel transport: pipe, queue, socket implementations - Bash session management and DifyCli binary integration - Storage: archive storage, file storage, noop storage, presign storage - Initializers: DifyCli, AppAssets, DraftAppAssets, Skills - Inspector: file browser, archive/runtime source, script utils - Security: encryption utils, debug helpers Skill & App Assets (core/skill/, core/app_assets/, core/app_bundle/): - Skill entity and manager - App asset accessor, builder pipeline (file, skill builders) - App bundle source zip extractor - Storage and converter utilities API Endpoints: - CLI API blueprint (controllers/cli_api/) for sandbox callback - Sandbox provider management (workspace/sandbox_providers) - Sandbox file browser (console/sandbox_files) - App asset management (console/app/app_asset) - Skill management (console/app/skills) - Storage file endpoints (controllers/files/storage_files) Services: - Sandbox service, provider service, file service - App asset service, app bundle service Config: - CliApiConfig, CreatorsPlatformConfig, CollaborationConfig - FILES_API_URL for sandbox file access Note: Controller route registration temporarily commented out (marked TODO) pending resolution of deep dependency chains (socketio, workflow_comment, command node, etc.). Core sandbox modules are fully ported and syntax-validated. 110 files changed, 10,549 insertions. Made-with: Cursor
164 lines
5.0 KiB
Python
164 lines
5.0 KiB
Python
"""
|
|
Type-safe attribute storage inspired by Netty's AttributeKey/AttributeMap pattern.
|
|
|
|
Provides loosely-coupled typed attribute storage where only code with access
|
|
to the same AttrKey instance can read/write the corresponding attribute.
|
|
|
|
SESSION_KEY: AttrKey[Session] = AttrKey("session", Session)
|
|
attrs = AttrMap()
|
|
attrs.set(SESSION_KEY, session)
|
|
session = attrs.get(SESSION_KEY) # -> Session (raises if not set)
|
|
session = attrs.get_or_none(SESSION_KEY) # -> Session | None
|
|
|
|
Note: AttrMap is NOT thread-safe. Each instance should be confined to a single
|
|
thread/context (e.g., one AttrMap per Sandbox/VirtualEnvironment instance).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Generic, TypeVar, cast, final, overload
|
|
|
|
T = TypeVar("T")
|
|
D = TypeVar("D")
|
|
|
|
|
|
@final
|
|
class AttrKey(Generic[T]):
|
|
"""
|
|
A type-safe key for attribute storage.
|
|
|
|
Identity-based: different AttrKey instances with same name are distinct keys.
|
|
This enables different modules to define keys independently without collision.
|
|
"""
|
|
|
|
__slots__ = ("_name", "_type")
|
|
|
|
def __init__(self, name: str, type_: type[T]) -> None:
|
|
self._name = name
|
|
self._type = type_
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
return self._name
|
|
|
|
@property
|
|
def type_(self) -> type[T]:
|
|
return self._type
|
|
|
|
def __repr__(self) -> str:
|
|
return f"AttrKey({self._name!r}, {self._type.__name__})"
|
|
|
|
def __hash__(self) -> int:
|
|
return id(self)
|
|
|
|
def __eq__(self, other: object) -> bool:
|
|
return self is other
|
|
|
|
|
|
class AttrMapKeyError(KeyError):
|
|
"""Raised when a required attribute is not set."""
|
|
|
|
key: AttrKey[Any]
|
|
|
|
def __init__(self, key: AttrKey[Any]) -> None:
|
|
self.key = key
|
|
super().__init__(f"Required attribute '{key.name}' (type: {key.type_.__name__}) is not set")
|
|
|
|
|
|
class AttrMapTypeError(TypeError):
|
|
"""Raised when attribute value type doesn't match the key's declared type."""
|
|
|
|
key: AttrKey[Any]
|
|
expected_type: type[Any]
|
|
actual_type: type[Any]
|
|
|
|
def __init__(self, key: AttrKey[Any], expected_type: type[Any], actual_type: type[Any]) -> None:
|
|
self.key = key
|
|
self.expected_type = expected_type
|
|
self.actual_type = actual_type
|
|
super().__init__(
|
|
f"Attribute '{key.name}' expects type '{expected_type.__name__}', got '{actual_type.__name__}'"
|
|
)
|
|
|
|
|
|
@final
|
|
class AttrMap:
|
|
"""
|
|
Thread-confined container for storing typed attributes using AttrKey instances.
|
|
|
|
NOT thread-safe. Each instance should be owned by a single context
|
|
(e.g., one AttrMap per Sandbox/VirtualEnvironment instance).
|
|
"""
|
|
|
|
__slots__ = ("_data",)
|
|
|
|
def __init__(self) -> None:
|
|
self._data: dict[AttrKey[Any], Any] = {}
|
|
|
|
def set(self, key: AttrKey[T], value: T, *, validate: bool = True) -> None:
|
|
"""
|
|
Store an attribute. Raises AttrMapTypeError if validate=True and type mismatches.
|
|
|
|
Note: Runtime validation only checks outer type (e.g., `list` not `list[str]`).
|
|
"""
|
|
if validate and not isinstance(value, key.type_):
|
|
raise AttrMapTypeError(key, key.type_, type(value))
|
|
self._data[key] = value
|
|
|
|
def get(self, key: AttrKey[T]) -> T:
|
|
"""Retrieve an attribute. Raises AttrMapKeyError if not set."""
|
|
if key not in self._data:
|
|
raise AttrMapKeyError(key)
|
|
return cast(T, self._data[key])
|
|
|
|
def get_or_none(self, key: AttrKey[T]) -> T | None:
|
|
"""Retrieve an attribute, returning None if not set."""
|
|
return cast(T | None, self._data.get(key))
|
|
|
|
@overload
|
|
def get_or_default(self, key: AttrKey[T], default: T) -> T: ...
|
|
|
|
@overload
|
|
def get_or_default(self, key: AttrKey[T], default: D) -> T | D: ...
|
|
|
|
def get_or_default(self, key: AttrKey[T], default: T | D) -> T | D:
|
|
"""Retrieve an attribute, returning default if not set."""
|
|
if key in self._data:
|
|
return cast(T, self._data[key])
|
|
return default
|
|
|
|
def has(self, key: AttrKey[Any]) -> bool:
|
|
"""Check if an attribute is set."""
|
|
return key in self._data
|
|
|
|
def remove(self, key: AttrKey[Any]) -> bool:
|
|
"""Remove an attribute. Returns True if it was present."""
|
|
if key in self._data:
|
|
del self._data[key]
|
|
return True
|
|
return False
|
|
|
|
def set_if_absent(self, key: AttrKey[T], value: T, *, validate: bool = True) -> T:
|
|
"""
|
|
Set attribute only if not already set. Returns existing or newly set value.
|
|
|
|
Raises AttrMapTypeError if validate=True and type mismatches.
|
|
"""
|
|
if key in self._data:
|
|
return cast(T, self._data[key])
|
|
if validate and not isinstance(value, key.type_):
|
|
raise AttrMapTypeError(key, key.type_, type(value))
|
|
self._data[key] = value
|
|
return value
|
|
|
|
def clear(self) -> None:
|
|
"""Remove all attributes."""
|
|
self._data.clear()
|
|
|
|
def __len__(self) -> int:
|
|
return len(self._data)
|
|
|
|
def __repr__(self) -> str:
|
|
keys = [k.name for k in self._data]
|
|
return f"AttrMap({keys})"
|