mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-20 03:26:18 +00:00
* feat(extensions): expose incremental run evidence reader * fix(extensions): address run evidence review feedback * docs(extensions): clarify run deletion reconciliation * docs(migrations): align current head documentation * fix(extensions): isolate run evidence event reads * test: avoid pinning run change migration to latest head --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
94 lines
2.9 KiB
Python
94 lines
2.9 KiB
Python
"""Stable, read-only run evidence contracts for extension services."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Protocol
|
|
|
|
|
|
class InvalidRunEvidenceCursor(ValueError):
|
|
"""The cursor is malformed, unsupported, or belongs to another scope."""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RunStatusView:
|
|
"""Authoritative lifecycle projection for one visible run."""
|
|
|
|
thread_id: str = ""
|
|
run_id: str = ""
|
|
status: str = ""
|
|
created_at: str = ""
|
|
updated_at: str = ""
|
|
error: str | None = None
|
|
stop_reason: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RunEventView:
|
|
"""Persisted evidence envelope; ``seq`` is monotonic within its thread.
|
|
|
|
Reader-returned content and metadata are detached from host storage.
|
|
Fields are frozen, but nested payload containers may be modified locally.
|
|
"""
|
|
|
|
thread_id: str = ""
|
|
run_id: str = ""
|
|
seq: int = 0
|
|
event_type: str = ""
|
|
category: str = ""
|
|
content: Any = None
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
created_at: str = ""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RunPage:
|
|
"""Changed runs plus the opaque cursor represented by this page."""
|
|
|
|
items: tuple[RunStatusView, ...] = ()
|
|
next_cursor: str | None = None
|
|
has_more: bool = False
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RunEventPage:
|
|
"""One forward page from a known run's persisted event stream."""
|
|
|
|
items: tuple[RunEventView, ...] = ()
|
|
next_after_seq: int | None = None
|
|
has_more: bool = False
|
|
|
|
|
|
class RunEvidenceReader(Protocol):
|
|
"""Host-bound evidence reader; implementations never expose writes."""
|
|
|
|
async def list_changed_runs(self, *, cursor: str | None, limit: int) -> RunPage:
|
|
"""Return visible runs changed after ``cursor`` in stable order.
|
|
|
|
Callers persist ``next_cursor`` only after their own output is durable.
|
|
Reusing the input cursor is valid and may replay items. An empty page
|
|
means caught up; unsupported hosts omit the reader instead. Deletions
|
|
do not produce tombstones in this feed. Consumers reconciling a run
|
|
they already know must treat ``get_run_status(...) is None`` as absent.
|
|
"""
|
|
raise NotImplementedError("the host does not provide changed-run discovery")
|
|
|
|
async def list_run_events(
|
|
self,
|
|
*,
|
|
thread_id: str,
|
|
run_id: str,
|
|
after_seq: int | None,
|
|
limit: int,
|
|
) -> RunEventPage:
|
|
"""Return events with thread-scoped ``seq > after_seq``.
|
|
|
|
A missing or invisible run returns an empty page, preventing identity
|
|
probing across the reader's host-bound scope.
|
|
"""
|
|
raise NotImplementedError("the host does not provide run-event reading")
|
|
|
|
async def get_run_status(self, *, thread_id: str, run_id: str) -> RunStatusView | None:
|
|
"""Return authoritative status, or ``None`` when not visible."""
|
|
raise NotImplementedError("the host does not provide run-status reading")
|