"""本地 SSE 响应包装(透传 chunk 并在 result 事件时回调)。""" from __future__ import annotations import json import time from collections.abc import Callable, Iterator from typing import Any from flask import Response, stream_with_context def _notify_sse_block( block: bytes, on_response: Callable[[Any, float, int], None], started: float, status_code: int, ) -> None: for line in block.decode("utf-8", errors="replace").split("\n"): if not line.startswith("data: "): continue try: payload = json.loads(line[6:].strip()) except json.JSONDecodeError: continue if payload.get("type") == "result": on_response(payload.get("data"), time.perf_counter() - started, status_code) elif payload.get("type") == "error": on_response(payload, time.perf_counter() - started, payload.get("status_code", 500)) def _tap_chunk_stream( chunks: Iterator, on_response: Callable[[Any, float, int], None] | None, started: float, status_code: int, ): if on_response is None: yield from chunks return pending = b"" for chunk in chunks: if not chunk: continue yield chunk pending += chunk if isinstance(chunk, bytes) else chunk.encode("utf-8") while b"\n\n" in pending: block, pending = pending.split(b"\n\n", 1) _notify_sse_block(block, on_response, started, status_code) def wrap_sse_response( response: Response, on_response: Callable[[Any, float, int], None], started: float, ) -> Response: """本地 SSE 响应:透传 chunk 并在 result 事件时回调。""" status_code = response.status_code def generating(): yield from _tap_chunk_stream( response.response, on_response, started, status_code ) return Response( stream_with_context(generating()), status=status_code, headers=dict(response.headers), mimetype=response.mimetype, )