def create_native_stream_router(
host: Any,
*,
sessions: Any,
prefix: str = "/api/v1",
app: Any = None,
) -> APIRouter:
"""Mount ``/stream`` separately from the still-live compatibility router."""
if not callable(getattr(host, "agent_for", None)):
raise TypeError("native stream host must provide agent_for(scope)")
if not all(callable(getattr(sessions, name, None)) for name in ("ensure_session", "touch")):
raise TypeError("native stream sessions must provide ensure_session and touch")
_check_production_auth_gate(app)
router = APIRouter(prefix=prefix, tags=["agent"])
@router.post("/stream")
async def stream(
request: AgentRequest,
raw_request: Request,
scope: FrameworkTenantScope = Depends(get_tenant_scope), # noqa: B008
) -> Any:
from sse_starlette.sse import EventSourceResponse
await check_budget_for_tenant(raw_request, str(scope.tenant_id))
invocation = _CODEC.decode_invocation(
request,
scope=scope,
is_admin=request_is_admin(raw_request),
principal_id=request_user_id(raw_request),
)
try:
agent = await host.agent_for(scope)
admission = admit_native_stream(agent, str(scope.tenant_id), invocation.query)
session_id = sessions.ensure_session(str(scope.tenant_id), invocation.session_id)
sessions.touch(session_id)
source = _chunks(
agent, invocation, session_id,
metered=admission.metered,
prepared_plan=admission.prepared_plan,
)
except Exception as exc:
from symfonic.platform.host import HostClosed
if isinstance(exc, HostClosed):
exc = SymfonicAgentError(
"the host is shutting down and is composing no further agents",
code="native_stream_unavailable",
)
raise raise_mapped(exc, STREAM_POLICY) from exc
adapter = SseFrameAdapter(STREAM_POLICY)
return EventSourceResponse(
adapter.frames(
source,
lambda chunk: {"event": chunk.event_type, "data": chunk.model_dump_json()},
)
)
return router