AnoobFeng 446fa03801
fix(context): resolve context compress bug (#4065)
* fix(runtime): persist original human input outside model sanitization

* refactor(history): load thread messages by global event sequence

* fix(frontend): make summarization rescue a transient history bridge

* fix(frontend): old message not append tail

1. add identity anchor
2. add bridgeOrder

* fix(frontend): lint error fix

* fix: address review feedback and harden pagination coverage

- defer transient history ref writes until after render commit
- cover large middleware-only history scans
- verify infinite-query refetch recalculates page cursors
- document AI event types and anchor-weaving differences

* fix: harden message pagination and enrichment

- append unmatched live tails after canonical history
- warn and stop when pagination has_more lacks a cursor
- deep-copy restored UI messages to isolate model-facing content
- log invalid event sequence and non-advancing cursor errors
- pass user_id explicitly through event-store history queries
- cover middleware-only AI runs across memory, JSONL, and DB stores

* fix: address pagination review feedback

* fix(frontend): checkpoint has unknow redener content, optimize the anchor policy

* fix(frontend): unit test issue missed previously, remove the TanStack cache trimming

* fix(gateway): harden message history queries and provenance

- reject externally forged original_user_content metadata
- validate provenance metadata in upload and sanitization middleware
- make run lookups fail closed by default
- batch feedback queries by run ID
- align memory message filtering with persistent stores
2026-07-14 10:43:13 +08:00

237 lines
7.0 KiB
Python

"""Abstract interface for run metadata storage.
RunManager depends on this interface. Implementations:
- MemoryRunStore: in-memory dict (development, tests)
- Future: RunRepository backed by SQLAlchemy ORM
All methods accept an optional user_id for user isolation.
When user_id is None, no user filtering is applied (single-user mode).
"""
from __future__ import annotations
import abc
from typing import Any
class RunStore(abc.ABC):
@abc.abstractmethod
async def put(
self,
run_id: str,
*,
thread_id: str,
assistant_id: str | None = None,
user_id: str | None = None,
model_name: str | None = None,
status: str = "pending",
multitask_strategy: str = "reject",
metadata: dict[str, Any] | None = None,
kwargs: dict[str, Any] | None = None,
error: str | None = None,
created_at: str | None = None,
owner_worker_id: str | None = None,
lease_expires_at: str | None = None,
) -> None:
pass
@abc.abstractmethod
async def get(
self,
run_id: str,
*,
user_id: str | None = None,
) -> dict[str, Any] | None:
pass
@abc.abstractmethod
async def list_by_thread(
self,
thread_id: str,
*,
user_id: str | None = None,
limit: int = 100,
) -> list[dict[str, Any]]:
pass
async def list_successful_regenerate_sources(
self,
thread_id: str,
*,
user_id: str | None = None,
) -> set[str]:
"""Return source run IDs superseded by successful regenerations.
Implementations must inspect the complete thread and must not apply the
normal bounded run-list limit.
"""
raise NotImplementedError
async def get_many_by_thread(
self,
thread_id: str,
run_ids: set[str],
*,
user_id: str | None = None,
) -> dict[str, dict[str, Any]]:
"""Batch-load selected runs belonging to one thread."""
raise NotImplementedError
@abc.abstractmethod
async def update_status(
self,
run_id: str,
status: str,
*,
error: str | None = None,
) -> bool | None:
"""Update a run status.
Returns ``False`` when the store can prove no row was updated. Older or
lightweight stores may return ``None`` when they cannot report rowcount.
"""
pass
@abc.abstractmethod
async def delete(self, run_id: str) -> None:
pass
@abc.abstractmethod
async def update_model_name(
self,
run_id: str,
model_name: str | None,
) -> None:
"""Update the model_name field for an existing run."""
pass
@abc.abstractmethod
async def update_run_completion(
self,
run_id: str,
*,
status: str,
total_input_tokens: int = 0,
total_output_tokens: int = 0,
total_tokens: int = 0,
llm_call_count: int = 0,
lead_agent_tokens: int = 0,
subagent_tokens: int = 0,
middleware_tokens: int = 0,
token_usage_by_model: dict[str, dict[str, int]] | None = None,
message_count: int = 0,
last_ai_message: str | None = None,
first_human_message: str | None = None,
error: str | None = None,
) -> bool | None:
"""Persist final completion fields.
Returns ``False`` when the store can prove no row was updated.
"""
pass
async def update_run_progress(
self,
run_id: str,
*,
total_input_tokens: int | None = None,
total_output_tokens: int | None = None,
total_tokens: int | None = None,
llm_call_count: int | None = None,
lead_agent_tokens: int | None = None,
subagent_tokens: int | None = None,
middleware_tokens: int | None = None,
token_usage_by_model: dict[str, dict[str, int]] | None = None,
message_count: int | None = None,
last_ai_message: str | None = None,
first_human_message: str | None = None,
) -> None:
"""Persist a best-effort running snapshot without changing run status."""
return None
@abc.abstractmethod
async def list_pending(self, *, before: str | None = None) -> list[dict[str, Any]]:
pass
@abc.abstractmethod
async def list_inflight(self, *, before: str | None = None) -> list[dict[str, Any]]:
"""Return persisted runs that are still ``pending`` or ``running``."""
pass
@abc.abstractmethod
async def aggregate_tokens_by_thread(self, thread_id: str, *, include_active: bool = False) -> dict[str, Any]:
"""Aggregate token usage for completed runs in a thread.
Returns a dict with keys: total_tokens, total_input_tokens,
total_output_tokens, total_runs, by_model (model_name → {tokens, runs}),
by_caller ({lead_agent, subagent, middleware}).
"""
pass
@abc.abstractmethod
async def update_lease(
self,
run_id: str,
*,
owner_worker_id: str,
lease_expires_at: str,
) -> bool:
"""Renew the lease on an active run. Returns ``False`` when no row matched."""
pass
@abc.abstractmethod
async def claim_for_takeover(
self,
run_id: str,
*,
grace_seconds: int,
error: str,
) -> bool:
"""Atomically mark an expired-lease active run as ``error``.
Only rows whose lease has expired past *grace_seconds* (or whose
lease is NULL — pre-ownership data) are updated. The conditional
WHERE closes the race between the caller's stale read of the lease
and a concurrent heartbeat renewal by the owning worker.
Returns ``False`` when:
- the run is no longer ``pending`` / ``running``,
- the lease is still valid (owner heartbeat is alive), or
- the row doesn't exist.
"""
pass
@abc.abstractmethod
async def list_inflight_with_expired_lease(
self,
*,
before: str | None = None,
grace_seconds: int = 10,
) -> list[dict[str, Any]]:
"""Return active runs whose lease has expired (or is NULL for pre-ownership rows)."""
pass
@abc.abstractmethod
async def create_run_atomic(
self,
run_id: str,
*,
thread_id: str,
owner_worker_id: str,
lease_expires_at: str | None,
multitask_strategy: str = "reject",
assistant_id: str | None = None,
user_id: str | None = None,
model_name: str | None = None,
metadata: dict[str, Any] | None = None,
kwargs: dict[str, Any] | None = None,
created_at: str | None = None,
grace_seconds: int = 10,
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
"""Atomically create a run row with cross-process thread-uniqueness.
Returns ``(new_run_dict, claimed_run_dicts)``.
Raises ``IntegrityError`` on conflict for ``reject`` strategy.
"""
pass