- harness/es.py: append-only event store + проекции (образец — кооп-портал) - harness/levels.py: уровни 1с/1м/1ч/1д, контекст ∝ масштабу - harness/scheduler.py: уровневые потоки, идемпотентные тики, absorbed-стабы - harness/isolated_runner.py + _llm_worker.py: LLM-тик в сабпроцессе (глобальный asyncio-рантайм Strands не переживает 2+ create_harness) - cli.py: init/task/tasks/status/events + requeue (написан самим минутным агентом) - 9router: отдельный ключ sk-simbiot, модель auto (glm-5.3 первым)
308 lines
7.4 KiB
Python
308 lines
7.4 KiB
Python
from typing import Any, List, Generic, TypeVar, Optional, cast
|
|
from typing_extensions import Protocol, override, runtime_checkable
|
|
|
|
from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage
|
|
|
|
__all__ = [
|
|
"SyncPage",
|
|
"AsyncPage",
|
|
"SyncCursorPage",
|
|
"AsyncCursorPage",
|
|
"SyncConversationCursorPage",
|
|
"AsyncConversationCursorPage",
|
|
"SyncNextCursorPage",
|
|
"AsyncNextCursorPage",
|
|
"SyncTokenPage",
|
|
"AsyncTokenPage",
|
|
]
|
|
|
|
_T = TypeVar("_T")
|
|
|
|
|
|
@runtime_checkable
|
|
class CursorPageItem(Protocol):
|
|
id: Optional[str]
|
|
|
|
|
|
class SyncPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
|
|
"""Note: no pagination actually occurs yet, this is for forwards-compatibility."""
|
|
|
|
data: List[_T]
|
|
object: str
|
|
|
|
@override
|
|
def _get_page_items(self) -> List[_T]:
|
|
data = self.data
|
|
if not data:
|
|
return []
|
|
return data
|
|
|
|
@override
|
|
def next_page_info(self) -> None:
|
|
"""
|
|
This page represents a response that isn't actually paginated at the API level
|
|
so there will never be a next page.
|
|
"""
|
|
return None
|
|
|
|
|
|
class AsyncPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
|
|
"""Note: no pagination actually occurs yet, this is for forwards-compatibility."""
|
|
|
|
data: List[_T]
|
|
object: str
|
|
|
|
@override
|
|
def _get_page_items(self) -> List[_T]:
|
|
data = self.data
|
|
if not data:
|
|
return []
|
|
return data
|
|
|
|
@override
|
|
def next_page_info(self) -> None:
|
|
"""
|
|
This page represents a response that isn't actually paginated at the API level
|
|
so there will never be a next page.
|
|
"""
|
|
return None
|
|
|
|
|
|
class SyncCursorPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
|
|
data: List[_T]
|
|
has_more: Optional[bool] = None
|
|
|
|
@override
|
|
def _get_page_items(self) -> List[_T]:
|
|
data = self.data
|
|
if not data:
|
|
return []
|
|
return data
|
|
|
|
@override
|
|
def has_next_page(self) -> bool:
|
|
has_more = self.has_more
|
|
if has_more is not None and has_more is False:
|
|
return False
|
|
|
|
return super().has_next_page()
|
|
|
|
@override
|
|
def next_page_info(self) -> Optional[PageInfo]:
|
|
data = self.data
|
|
if not data:
|
|
return None
|
|
|
|
item = cast(Any, data[-1])
|
|
if not isinstance(item, CursorPageItem) or item.id is None:
|
|
# TODO emit warning log
|
|
return None
|
|
|
|
return PageInfo(params={"after": item.id})
|
|
|
|
|
|
class AsyncCursorPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
|
|
data: List[_T]
|
|
has_more: Optional[bool] = None
|
|
|
|
@override
|
|
def _get_page_items(self) -> List[_T]:
|
|
data = self.data
|
|
if not data:
|
|
return []
|
|
return data
|
|
|
|
@override
|
|
def has_next_page(self) -> bool:
|
|
has_more = self.has_more
|
|
if has_more is not None and has_more is False:
|
|
return False
|
|
|
|
return super().has_next_page()
|
|
|
|
@override
|
|
def next_page_info(self) -> Optional[PageInfo]:
|
|
data = self.data
|
|
if not data:
|
|
return None
|
|
|
|
item = cast(Any, data[-1])
|
|
if not isinstance(item, CursorPageItem) or item.id is None:
|
|
# TODO emit warning log
|
|
return None
|
|
|
|
return PageInfo(params={"after": item.id})
|
|
|
|
|
|
class SyncConversationCursorPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
|
|
data: List[_T]
|
|
has_more: Optional[bool] = None
|
|
last_id: Optional[str] = None
|
|
|
|
@override
|
|
def _get_page_items(self) -> List[_T]:
|
|
data = self.data
|
|
if not data:
|
|
return []
|
|
return data
|
|
|
|
@override
|
|
def has_next_page(self) -> bool:
|
|
has_more = self.has_more
|
|
if has_more is not None and has_more is False:
|
|
return False
|
|
|
|
return super().has_next_page()
|
|
|
|
@override
|
|
def next_page_info(self) -> Optional[PageInfo]:
|
|
last_id = self.last_id
|
|
if not last_id:
|
|
return None
|
|
|
|
return PageInfo(params={"after": last_id})
|
|
|
|
|
|
class AsyncConversationCursorPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
|
|
data: List[_T]
|
|
has_more: Optional[bool] = None
|
|
last_id: Optional[str] = None
|
|
|
|
@override
|
|
def _get_page_items(self) -> List[_T]:
|
|
data = self.data
|
|
if not data:
|
|
return []
|
|
return data
|
|
|
|
@override
|
|
def has_next_page(self) -> bool:
|
|
has_more = self.has_more
|
|
if has_more is not None and has_more is False:
|
|
return False
|
|
|
|
return super().has_next_page()
|
|
|
|
@override
|
|
def next_page_info(self) -> Optional[PageInfo]:
|
|
last_id = self.last_id
|
|
if not last_id:
|
|
return None
|
|
|
|
return PageInfo(params={"after": last_id})
|
|
|
|
|
|
class SyncNextCursorPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
|
|
data: List[_T]
|
|
has_more: Optional[bool] = None
|
|
next: Optional[str] = None
|
|
|
|
@override
|
|
def _get_page_items(self) -> List[_T]:
|
|
data = self.data
|
|
if not data:
|
|
return []
|
|
return data
|
|
|
|
@override
|
|
def has_next_page(self) -> bool:
|
|
has_more = self.has_more
|
|
if has_more is not None and has_more is False:
|
|
return False
|
|
|
|
return super().has_next_page()
|
|
|
|
@override
|
|
def next_page_info(self) -> Optional[PageInfo]:
|
|
next = self.next
|
|
if not next:
|
|
return None
|
|
|
|
return PageInfo(params={"after": next})
|
|
|
|
|
|
class AsyncNextCursorPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
|
|
data: List[_T]
|
|
has_more: Optional[bool] = None
|
|
next: Optional[str] = None
|
|
|
|
@override
|
|
def _get_page_items(self) -> List[_T]:
|
|
data = self.data
|
|
if not data:
|
|
return []
|
|
return data
|
|
|
|
@override
|
|
def has_next_page(self) -> bool:
|
|
has_more = self.has_more
|
|
if has_more is not None and has_more is False:
|
|
return False
|
|
|
|
return super().has_next_page()
|
|
|
|
@override
|
|
def next_page_info(self) -> Optional[PageInfo]:
|
|
next = self.next
|
|
if not next:
|
|
return None
|
|
|
|
return PageInfo(params={"after": next})
|
|
|
|
|
|
class SyncTokenPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
|
|
data: List[_T]
|
|
has_more: Optional[bool] = None
|
|
next: Optional[str] = None
|
|
|
|
@override
|
|
def _get_page_items(self) -> List[_T]:
|
|
data = self.data
|
|
if not data:
|
|
return []
|
|
return data
|
|
|
|
@override
|
|
def has_next_page(self) -> bool:
|
|
has_more = self.has_more
|
|
if has_more is not None and has_more is False:
|
|
return False
|
|
|
|
return super().has_next_page()
|
|
|
|
@override
|
|
def next_page_info(self) -> Optional[PageInfo]:
|
|
next = self.next
|
|
if not next:
|
|
return None
|
|
|
|
return PageInfo(params={"page": next})
|
|
|
|
|
|
class AsyncTokenPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
|
|
data: List[_T]
|
|
has_more: Optional[bool] = None
|
|
next: Optional[str] = None
|
|
|
|
@override
|
|
def _get_page_items(self) -> List[_T]:
|
|
data = self.data
|
|
if not data:
|
|
return []
|
|
return data
|
|
|
|
@override
|
|
def has_next_page(self) -> bool:
|
|
has_more = self.has_more
|
|
if has_more is not None and has_more is False:
|
|
return False
|
|
|
|
return super().has_next_page()
|
|
|
|
@override
|
|
def next_page_info(self) -> Optional[PageInfo]:
|
|
next = self.next
|
|
if not next:
|
|
return None
|
|
|
|
return PageInfo(params={"page": next})
|