Files
EvoScientist-Multi/EvoScientist/gateway/types.py
T
dinos bd307f3a11 refactor: LangGraph gateway layer for UI-agnostic graph and thread access (#295)
* feat(gateway): graph gateway protocol

* refactor(cli): wire gateway in cli/tui

* refactor(gateway): centralize runtime gateway init

* chore(gateway): restrict RunRequest message type

* feat(gateway): add langgraph server gateway

* chore(cli): tighten serve runtime state typing

* refactor(cli): route async task state reads through graph gateway

* refactor(gateway): support graph targets in server gateway

* refactor(cli): route session commands through graph gateway

* refactor(cli): fold thread store under graph gateway

* refactor(gateway): route graph state access through gateway

* refactor(channels): wire graph gateway

* refactor(memory): preserve graph threads for cloning

* feat(gateway): add thread cloning

* fix(tui): pass effective workspace for thread creation

* chore(memory): add workspare dir to memory worker metadata

* fix(sessions): filter preloaded UUID registy entries by the current scope

* test(fakes): use https

* refactor(consumer): consolidate imports

* fix(stream): optional summarization event

* fix(gateway): resolve abbreviated thread IDs by search

* fix(gateway): page server thread listings

* fix(gateway): emit pending interrupt events

* style: fmt

* feat(gateway): persist workspace_dir & model in thread metadata

* fix(gateway): page server thread prefix resolution

* fix(gateway): expose server thread list metadata

* refactor: add back type def

* refactor: tighten types

* revert: add back worker thread deletion

The worker thread forking changes are out of scope for now, so to
maintain parity with the existing behavior we'll leave this intact.

* fix(gateway): apply compaction to server thread history

* refactor(stream): restore direct summary replay suppression

* fix(gateway): preserve compaction state and server stream output

* fix(gateway): close local stream generator on cancellation

---------

Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com>
2026-06-22 13:54:14 +00:00

176 lines
4.9 KiB
Python

"""Shared types for graph/thread gateway implementations."""
from __future__ import annotations
from collections.abc import AsyncIterator
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Protocol, TypeAlias
from langgraph.types import Command
if TYPE_CHECKING:
from langgraph.graph.state import CompiledStateGraph
GraphEvent: TypeAlias = dict[str, Any]
GraphRunInput: TypeAlias = str | Command
GraphStateValues: TypeAlias = dict[str, Any]
DEFAULT_GRAPH_ID = "EvoScientist"
@dataclass(frozen=True, slots=True)
class GraphTarget:
"""Identifies the graph/workspace a thread operation targets.
``local_graph`` is the in-process execution handle required only by the
local backend. Server backends select execution via ``graph_id``.
"""
graph_id: str = DEFAULT_GRAPH_ID
workspace_dir: str | None = None
local_graph: CompiledStateGraph | None = None
@dataclass(frozen=True, slots=True)
class RunRequest:
"""A graph turn request, independent of the UI that initiated it."""
message: GraphRunInput
thread_id: str
metadata: dict[str, Any] | None = None
media: list[str] | None = None
target: GraphTarget | None = None
@dataclass(frozen=True, slots=True)
class ThreadResolution:
"""Result of resolving an exact or prefix thread id."""
thread_id: str | None
matches: tuple[str, ...] = ()
@property
def found(self) -> bool:
return self.thread_id is not None
@property
def ambiguous(self) -> bool:
return self.thread_id is None and bool(self.matches)
class ThreadStore(Protocol):
"""Thread persistence operations used by graph gateways."""
def generate_thread_id(self) -> str:
"""Generate a new thread id."""
async def list_threads(
self,
*,
limit: int = 20,
include_message_count: bool = False,
include_preview: bool = False,
) -> list[dict[str, Any]]:
"""Return persisted threads."""
async def resolve_thread_id_prefix(
self,
thread_id_or_prefix: str,
) -> tuple[str | None, list[str]]:
"""Resolve an exact or prefix thread id."""
async def get_thread_metadata(self, thread_id: str) -> dict[str, Any] | None:
"""Return persisted metadata for a thread, if available."""
async def get_thread_messages(self, thread_id: str) -> list[Any]:
"""Return persisted messages for a thread."""
async def thread_exists(self, thread_id: str) -> bool:
"""Return whether a thread exists."""
async def delete_thread(self, thread_id: str) -> bool:
"""Delete a thread and its persisted state."""
class GraphGateway(Protocol):
"""One authority for graph runs and thread lifecycle operations."""
async def create_thread(
self,
target: GraphTarget | None = None,
*,
metadata: dict[str, Any] | None = None,
) -> str:
"""Create or reserve a new thread id."""
async def list_threads(
self,
*,
limit: int = 20,
include_message_count: bool = False,
include_preview: bool = False,
target: GraphTarget | None = None,
) -> list[dict[str, Any]]:
"""Return user-facing threads for the active backend."""
async def resolve_thread(
self,
thread_id_or_prefix: str,
target: GraphTarget | None = None,
) -> ThreadResolution:
"""Resolve a thread id or prefix."""
async def get_thread_metadata(
self,
thread_id: str,
target: GraphTarget | None = None,
) -> dict[str, Any] | None:
"""Return persisted metadata for a thread, if available."""
async def get_thread_messages(
self,
thread_id: str,
target: GraphTarget | None = None,
) -> list[Any]:
"""Return persisted messages for a thread."""
async def thread_exists(
self,
thread_id: str,
target: GraphTarget | None = None,
) -> bool:
"""Return whether a thread exists in the active backend."""
async def delete_thread(
self,
thread_id: str,
target: GraphTarget | None = None,
) -> bool:
"""Delete a thread and its persisted state."""
async def clone_thread(
self,
source_thread_id: str,
*,
metadata: dict[str, Any] | None = None,
target: GraphTarget | None = None,
) -> str:
"""Clone a thread and return the cloned thread id."""
def stream_events(self, request: RunRequest) -> AsyncIterator[GraphEvent]:
"""Stream normalized graph events for the request target."""
async def get_state_values(
self,
target: GraphTarget,
thread_id: str,
) -> GraphStateValues:
"""Return the graph state values for a thread."""
async def update_state_values(
self,
target: GraphTarget,
thread_id: str,
values: GraphStateValues,
) -> None:
"""Update graph state values for a thread."""