| from typing import NotRequired, override |
|
|
| from langchain.agents import AgentState |
| from langchain.agents.middleware import AgentMiddleware |
| from langgraph.runtime import Runtime |
|
|
| from src.agents.thread_state import ThreadDataState |
| from src.config.paths import Paths, get_paths |
| from src.utils.runtime import get_runtime_thread_id |
|
|
|
|
| class ThreadDataMiddlewareState(AgentState): |
| """Compatible with the `ThreadState` schema.""" |
|
|
| thread_data: NotRequired[ThreadDataState | None] |
|
|
|
|
| class ThreadDataMiddleware(AgentMiddleware[ThreadDataMiddlewareState]): |
| """Create thread data directories for each thread execution. |
| |
| Creates the following directory structure: |
| - {base_dir}/threads/{thread_id}/user-data/workspace |
| - {base_dir}/threads/{thread_id}/user-data/uploads |
| - {base_dir}/threads/{thread_id}/user-data/outputs |
| |
| Lifecycle Management: |
| - With lazy_init=True (default): Only compute paths, directories created on-demand |
| - With lazy_init=False: Eagerly create directories in before_agent() |
| """ |
|
|
| state_schema = ThreadDataMiddlewareState |
|
|
| def __init__(self, base_dir: str | None = None, lazy_init: bool = True): |
| """Initialize the middleware. |
| |
| Args: |
| base_dir: Base directory for thread data. Defaults to Paths resolution. |
| lazy_init: If True, defer directory creation until needed. |
| If False, create directories eagerly in before_agent(). |
| Default is True for optimal performance. |
| """ |
| super().__init__() |
| self._paths = Paths(base_dir) if base_dir else get_paths() |
| self._lazy_init = lazy_init |
|
|
| def _get_thread_paths(self, thread_id: str) -> dict[str, str]: |
| """Get the paths for a thread's data directories. |
| |
| Args: |
| thread_id: The thread ID. |
| |
| Returns: |
| Dictionary with workspace_path, uploads_path, and outputs_path. |
| """ |
| return { |
| "workspace_path": str(self._paths.sandbox_work_dir(thread_id)), |
| "uploads_path": str(self._paths.sandbox_uploads_dir(thread_id)), |
| "outputs_path": str(self._paths.sandbox_outputs_dir(thread_id)), |
| } |
|
|
| def _create_thread_directories(self, thread_id: str) -> dict[str, str]: |
| """Create the thread data directories. |
| |
| Args: |
| thread_id: The thread ID. |
| |
| Returns: |
| Dictionary with the created directory paths. |
| """ |
| self._paths.ensure_thread_dirs(thread_id) |
| return self._get_thread_paths(thread_id) |
|
|
| @override |
| def before_agent(self, state: ThreadDataMiddlewareState, runtime: Runtime) -> dict | None: |
| thread_id = get_runtime_thread_id(runtime) |
| if thread_id is None: |
| return None |
|
|
| if self._lazy_init: |
| |
| paths = self._get_thread_paths(thread_id) |
| else: |
| |
| paths = self._create_thread_directories(thread_id) |
| print(f"Created thread data directories for thread {thread_id}") |
|
|
| return { |
| "thread_data": { |
| **paths, |
| } |
| } |
|
|