"""Repository contract for model-stream truncation events.""" from __future__ import annotations from abc import ABC, abstractmethod from dataclasses import dataclass from datetime import datetime from typing import Any # Hard retention cap. The user-facing promise is "最多存一个月" of truncation # records; the SQL repository enforces it on every insert so no scheduler is # required for the cap to hold. RETENTION_DAYS = 30 @dataclass class StreamTruncationEvent: """In-memory shape of a truncation event before persistence.""" id: str created_at: datetime model_name: str | None = None provider_completion_id: str | None = None run_id: str | None = None # silent_no_terminal / incomplete_stream_error / length_truncated / # content_filter / midstream_error event_type: str = "silent_no_terminal" strict_mode: bool = False finish_reason: str = "none" status: str = "success" error_type: str | None = None chunk_count: int = 0 duration_ms: int = 0 first_chunk_ms: int = -1 detail: str | None = None @dataclass class StreamTruncationQuery: """Filter set for the admin list endpoint.""" model_name: str | None = None event_type: str | None = None since: datetime | None = None until: datetime | None = None limit: int = 100 offset: int = 0 class StreamTruncationStore(ABC): """Persistence contract for the model-layer recorder + admin API.""" @abstractmethod async def record(self, event: StreamTruncationEvent) -> None: """Persist one event and enforce the retention cap. Must never raise.""" raise NotImplementedError @abstractmethod async def list(self, query: StreamTruncationQuery) -> list[dict[str, Any]]: """Return matching rows (newest first), paginated by ``query``.""" raise NotImplementedError @abstractmethod async def count(self, query: StreamTruncationQuery) -> int: """Return the unpaginated row count for ``query``.""" raise NotImplementedError @abstractmethod async def model_breakdown(self, query: StreamTruncationQuery) -> list[dict[str, Any]]: """Per-model aggregate: ``[{model_name, event_type counts..., total, last_at}]``.""" raise NotImplementedError @abstractmethod async def purge_before(self, cutoff: datetime) -> int: """Delete rows older than ``cutoff``; return the deleted row count.""" raise NotImplementedError