refactor: split flat core into capability packages, layer the conversations service, English defaults for every model-facing text
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"""Server-sent events helpers shared by the markdown and API frontends."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import json
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
__all__ = ["HEARTBEAT_INTERVAL", "SSE_HEADERS", "events_with_heartbeat", "sse_pack"]
|
||||
|
||||
HEARTBEAT_INTERVAL = 15.0
|
||||
"""Seconds of backend silence before a comment frame keeps the socket warm."""
|
||||
|
||||
SSE_HEADERS = {
|
||||
"Cache-Control": "no-cache, no-transform",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
}
|
||||
|
||||
KEEPALIVE = b": keepalive\n\n"
|
||||
|
||||
|
||||
async def events_with_heartbeat(
|
||||
events: AsyncIterator[Any], interval: float = HEARTBEAT_INTERVAL
|
||||
) -> AsyncIterator[Any]:
|
||||
"""Pass ``events`` through, yielding ``None`` after ``interval`` seconds of silence.
|
||||
|
||||
One in-flight ``__anext__`` task is reused across timeouts: a second
|
||||
consumer on the same async generator raises ``RuntimeError``.
|
||||
Cancellation of the outer scope cancels that task instead of leaving
|
||||
it dangling.
|
||||
"""
|
||||
src = events.__aiter__()
|
||||
next_task: asyncio.Task[Any] | None = None
|
||||
try:
|
||||
while True:
|
||||
if next_task is None:
|
||||
next_task = asyncio.ensure_future(src.__anext__())
|
||||
done, _pending = await asyncio.wait({next_task}, timeout=interval)
|
||||
if not done:
|
||||
yield None
|
||||
continue
|
||||
task = next_task
|
||||
next_task = None
|
||||
try:
|
||||
result = task.result()
|
||||
except StopAsyncIteration:
|
||||
return
|
||||
yield result
|
||||
finally:
|
||||
if next_task is not None and not next_task.done():
|
||||
next_task.cancel()
|
||||
with contextlib.suppress(BaseException):
|
||||
await next_task
|
||||
|
||||
|
||||
def sse_pack(event: str, data: dict[str, Any]) -> bytes:
|
||||
body = json.dumps(data, ensure_ascii=False)
|
||||
return f"event: {event}\ndata: {body}\n\n".encode()
|
||||
Reference in New Issue
Block a user