refactor(harness): update modules to use new runtime imports

Update import paths across harness modules:
- agents/lead_agent/prompt.py
- agents/middlewares/ (memory, thread_data, uploads)
- client.py - enhanced with new capabilities
- community/aio_sandbox/
- config/app_config.py - remove deprecated configs
- sandbox/tools.py
- tools/builtins/ (invoke_acp_agent, present_file)
- uploads/manager.py

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rayhpeng 2026-04-22 11:29:12 +08:00
parent 5f2f1941e9
commit e3e00af51d
11 changed files with 121 additions and 31 deletions

View File

@ -519,7 +519,7 @@ def _get_memory_context(agent_name: str | None = None) -> str:
try:
from deerflow.agents.memory import format_memory_for_injection, get_memory_data
from deerflow.config.memory_config import get_memory_config
from deerflow.runtime.user_context import get_effective_user_id
from deerflow.runtime.actor_context import get_effective_user_id
config = get_memory_config()
if not config.enabled or not config.injection_enabled:

View File

@ -11,7 +11,7 @@ from langgraph.runtime import Runtime
from deerflow.agents.memory.queue import get_memory_queue
from deerflow.config.memory_config import get_memory_config
from deerflow.runtime.user_context import get_effective_user_id
from deerflow.runtime.actor_context import get_effective_user_id
logger = logging.getLogger(__name__)

View File

@ -10,7 +10,7 @@ from langgraph.runtime import Runtime
from deerflow.agents.thread_state import ThreadDataState
from deerflow.config.paths import Paths, get_paths
from deerflow.runtime.user_context import get_effective_user_id
from deerflow.runtime.actor_context import get_effective_user_id
logger = logging.getLogger(__name__)

View File

@ -10,7 +10,7 @@ from langchain_core.messages import HumanMessage
from langgraph.runtime import Runtime
from deerflow.config.paths import Paths, get_paths
from deerflow.runtime.user_context import get_effective_user_id
from deerflow.runtime.actor_context import get_effective_user_id
from deerflow.utils.file_conversion import extract_outline
logger = logging.getLogger(__name__)

View File

@ -26,6 +26,7 @@ from collections.abc import Generator, Sequence
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Literal
from typing_extensions import TypedDict
from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware
@ -40,7 +41,7 @@ from deerflow.config.app_config import get_app_config, reload_app_config
from deerflow.config.extensions_config import ExtensionsConfig, SkillStateConfig, get_extensions_config, reload_extensions_config
from deerflow.config.paths import get_paths
from deerflow.models import create_chat_model
from deerflow.runtime.user_context import get_effective_user_id
from deerflow.runtime.actor_context import get_effective_user_id
from deerflow.skills.installer import install_skill_from_archive
from deerflow.uploads.manager import (
claim_unique_filename,
@ -59,6 +60,14 @@ logger = logging.getLogger(__name__)
StreamEventType = Literal["values", "messages-tuple", "custom", "end"]
class AgentContext(TypedDict, total=False):
"""Typed runtime context passed into LangGraph agent execution."""
thread_id: str
agent_name: str
sandbox_id: str
@dataclass
class StreamEvent:
"""A single event from the streaming agent response.
@ -143,12 +152,17 @@ class DeerFlowClient:
"""
if config_path is not None:
reload_app_config(config_path)
from store.config.app_config import reload_app_config as reload_storage_app_config
reload_storage_app_config(config_path)
self._app_config = get_app_config()
if agent_name is not None and not AGENT_NAME_PATTERN.match(agent_name):
raise ValueError(f"Invalid agent name '{agent_name}'. Must match pattern: {AGENT_NAME_PATTERN.pattern}")
self._checkpointer = checkpointer
self._default_checkpointer = None
self._default_checkpointer_resource = None
self._model_name = model_name
self._thinking_enabled = thinking_enabled
self._subagent_enabled = subagent_enabled
@ -207,6 +221,97 @@ class DeerFlowClient:
recursion_limit=overrides.get("recursion_limit", 100),
)
@staticmethod
def _resolve_sqlite_conn_str(connection_string: str) -> str:
"""Normalize sqlite connection strings to a filesystem path string."""
if connection_string == ":memory:":
return connection_string
return str(Path(connection_string).expanduser())
@staticmethod
def _ensure_sqlite_parent_dir(connection_string: str) -> None:
"""Create parent directory for a sqlite file if needed."""
if connection_string == ":memory:":
return
Path(connection_string).expanduser().parent.mkdir(parents=True, exist_ok=True)
def _get_configured_checkpointer(self):
"""Build or reuse the client fallback checkpointer from storage config."""
if self._default_checkpointer is not None:
return self._default_checkpointer
config = self._resolve_checkpointer_config()
self._default_checkpointer = self._build_configured_checkpointer(config)
return self._default_checkpointer
@staticmethod
def _resolve_checkpointer_config() -> dict[str, str]:
"""Resolve checkpointer backend from the unified storage config entry."""
from store.config.app_config import get_app_config as get_storage_app_config
storage = get_storage_app_config().storage
driver = storage.driver
if driver == "sqlite":
return {"backend": "sqlite", "connection_string": storage.sqlite_storage_path}
if driver == "postgres":
return {
"backend": "postgres",
"connection_string": (
f"postgresql://{storage.username}:{storage.password}@{storage.host}:{storage.port}/{storage.db_name}"
),
}
if driver == "mysql":
raise ValueError("DeerFlowClient does not support a MySQL checkpointer")
raise ValueError(f"Unsupported storage driver for checkpointer: {driver}")
def _build_configured_checkpointer(self, config: dict[str, str]):
"""Build a sync checkpointer using the embedded client's config."""
backend = config["backend"]
connection_string = config.get("connection_string", "")
if backend == "memory":
from langgraph.checkpoint.memory import InMemorySaver
return InMemorySaver()
if backend == "sqlite":
try:
from langgraph.checkpoint.sqlite import SqliteSaver
except ImportError as exc:
raise ImportError("SQLite checkpointer requires langgraph-checkpoint-sqlite") from exc
if not connection_string:
raise ValueError("connection_string is required for sqlite checkpointer")
conn_str = self._resolve_sqlite_conn_str(connection_string)
self._ensure_sqlite_parent_dir(conn_str)
resource = SqliteSaver.from_conn_string(conn_str)
saver = resource.__enter__()
saver.setup()
self._default_checkpointer_resource = resource
return saver
if backend == "postgres":
try:
from langgraph.checkpoint.postgres import PostgresSaver
except ImportError as exc:
raise ImportError("Postgres checkpointer requires langgraph-checkpoint-postgres") from exc
if not connection_string:
raise ValueError("connection_string is required for postgres checkpointer")
resource = PostgresSaver.from_conn_string(connection_string)
saver = resource.__enter__()
saver.setup()
self._default_checkpointer_resource = resource
return saver
raise ValueError(f"Unsupported checkpointer type: {backend}")
def _get_active_checkpointer(self):
"""Return the explicitly injected or config-derived checkpointer."""
if self._checkpointer is not None:
return self._checkpointer
return self._get_configured_checkpointer()
def _ensure_agent(self, config: RunnableConfig):
"""Create (or recreate) the agent when config-dependent params change."""
cfg = config.get("configurable", {})
@ -238,12 +343,9 @@ class DeerFlowClient:
available_skills=self._available_skills,
),
"state_schema": ThreadState,
"context_schema": AgentContext,
}
checkpointer = self._checkpointer
if checkpointer is None:
from deerflow.runtime.checkpointer import get_checkpointer
checkpointer = get_checkpointer()
checkpointer = self._get_active_checkpointer()
if checkpointer is not None:
kwargs["checkpointer"] = checkpointer
@ -373,11 +475,9 @@ class DeerFlowClient:
Dict with "thread_list" key containing list of thread info dicts,
sorted by thread creation time descending.
"""
checkpointer = self._checkpointer
checkpointer = self._get_active_checkpointer()
if checkpointer is None:
from deerflow.runtime.checkpointer.provider import get_checkpointer
checkpointer = get_checkpointer()
return {"thread_list": []}
thread_info_map = {}
@ -428,11 +528,9 @@ class DeerFlowClient:
Returns:
Dict containing the thread's full checkpoint history.
"""
checkpointer = self._checkpointer
checkpointer = self._get_active_checkpointer()
if checkpointer is None:
from deerflow.runtime.checkpointer.provider import get_checkpointer
checkpointer = get_checkpointer()
return {"thread_id": thread_id, "checkpoints": []}
config = {"configurable": {"thread_id": thread_id}}
checkpoints = []

View File

@ -27,7 +27,7 @@ except ImportError: # pragma: no cover - Windows fallback
from deerflow.config import get_app_config
from deerflow.config.paths import VIRTUAL_PATH_PREFIX, get_paths
from deerflow.runtime.user_context import get_effective_user_id
from deerflow.runtime.actor_context import get_effective_user_id
from deerflow.sandbox.sandbox import Sandbox
from deerflow.sandbox.sandbox_provider import SandboxProvider

View File

@ -9,8 +9,6 @@ from dotenv import load_dotenv
from pydantic import BaseModel, ConfigDict, Field
from deerflow.config.acp_config import load_acp_config_from_dict
from deerflow.config.checkpointer_config import CheckpointerConfig, load_checkpointer_config_from_dict
from deerflow.config.database_config import DatabaseConfig
from deerflow.config.extensions_config import ExtensionsConfig
from deerflow.config.guardrails_config import GuardrailsConfig, load_guardrails_config_from_dict
from deerflow.config.memory_config import MemoryConfig, load_memory_config_from_dict
@ -58,9 +56,7 @@ class AppConfig(BaseModel):
subagents: SubagentsAppConfig = Field(default_factory=SubagentsAppConfig, description="Subagent runtime configuration")
guardrails: GuardrailsConfig = Field(default_factory=GuardrailsConfig, description="Guardrail middleware configuration")
model_config = ConfigDict(extra="allow", frozen=False)
database: DatabaseConfig = Field(default_factory=DatabaseConfig, description="Unified database backend configuration")
run_events: RunEventsConfig = Field(default_factory=RunEventsConfig, description="Run event storage configuration")
checkpointer: CheckpointerConfig | None = Field(default=None, description="Checkpointer configuration")
stream_bridge: StreamBridgeConfig | None = Field(default=None, description="Stream bridge configuration")
@classmethod
@ -133,10 +129,6 @@ class AppConfig(BaseModel):
if "guardrails" in config_data:
load_guardrails_config_from_dict(config_data["guardrails"])
# Load checkpointer config if present
if "checkpointer" in config_data:
load_checkpointer_config_from_dict(config_data["checkpointer"])
# Load stream bridge config if present
if "stream_bridge" in config_data:
load_stream_bridge_config_from_dict(config_data["stream_bridge"])

View File

@ -200,7 +200,7 @@ def _get_acp_workspace_host_path(thread_id: str | None = None) -> str | None:
if thread_id is not None:
try:
from deerflow.config.paths import get_paths
from deerflow.runtime.user_context import get_effective_user_id
from deerflow.runtime.actor_context import get_effective_user_id
host_path = get_paths().acp_workspace_dir(thread_id, user_id=get_effective_user_id())
if host_path.exists():

View File

@ -33,7 +33,7 @@ def _get_work_dir(thread_id: str | None) -> str:
An absolute physical filesystem path to use as the working directory.
"""
from deerflow.config.paths import get_paths
from deerflow.runtime.user_context import get_effective_user_id
from deerflow.runtime.actor_context import get_effective_user_id
paths = get_paths()
if thread_id:

View File

@ -8,7 +8,7 @@ from langgraph.typing import ContextT
from deerflow.agents.thread_state import ThreadState
from deerflow.config.paths import VIRTUAL_PATH_PREFIX, get_paths
from deerflow.runtime.user_context import get_effective_user_id
from deerflow.runtime.actor_context import get_effective_user_id
OUTPUTS_VIRTUAL_PREFIX = f"{VIRTUAL_PATH_PREFIX}/outputs"

View File

@ -10,7 +10,7 @@ from pathlib import Path
from urllib.parse import quote
from deerflow.config.paths import VIRTUAL_PATH_PREFIX, get_paths
from deerflow.runtime.user_context import get_effective_user_id
from deerflow.runtime.actor_context import get_effective_user_id
class PathTraversalError(ValueError):