def mount_continuation_route(router: Any, *, host: Any, resolve: Any) -> None:
"""Mount ``POST /resume/{token}`` over a host continuation service.
``resolve`` authenticates the HTTP request. It is intentionally supplied
by the main router so all transport doors share the same tenant policy.
"""
from fastapi import Header, Request
from fastapi.responses import StreamingResponse
# With postponed annotations FastAPI resolves ``Request`` against this
# module, not this factory's local frame. Keep the optional FastAPI import
# local while still making its special request injection discoverable.
globals()["Request"] = Request
@router.post("/resume/{pause_token}") # type: ignore[untyped-decorator]
async def resume(
pause_token: str,
raw_request: Request,
x_tenant_id: str | None = Header(default=None), # noqa: B008
authorization: str | None = Header(default=None), # noqa: B008
) -> Any:
request_scope = await resolve(x_tenant_id, authorization)
try:
ticket = await host.ticket(pause_token)
except Exception as exc: # ticket lookup has no redemption side effect
status, detail = _status(exc)
return StreamingResponse(_single_error(status, detail), media_type="text/event-stream")
cross_scope_allowed = bool(getattr(ticket, "cross_scope_allowed", False))
if not _same_scope(request_scope, ticket.scope) and not cross_scope_allowed:
return StreamingResponse(
_single_error(403, "continuation belongs to another authenticated scope"),
media_type="text/event-stream",
)
try:
raw_body = await raw_request.json()
except Exception as exc: # malformed JSON is still an SSE protocol error
detail = f"Invalid JSON body: {exc}"
return StreamingResponse(_single_error(400, detail), media_type="text/event-stream")
schema = getattr(ticket, "response_schema", None)
validate = getattr(schema, "model_validate", None)
if not callable(validate):
return StreamingResponse(
_single_error(400, "continuation has no registered response schema"),
media_type="text/event-stream",
)
try:
answer = validate(raw_body)
except Exception as exc: # validation happens before host.resume/redeem
detail = f"Invalid resume payload: {exc}"
return StreamingResponse(_single_error(400, detail), media_type="text/event-stream")
async def events() -> AsyncIterator[str]:
try:
result = await host.resume(pause_token, answer, scope=request_scope)
if hasattr(result, "__aiter__"):
async for event in result:
yield _typed(event)
else:
yield _typed(result)
except Exception as exc: # resume failures occur after the SSE handshake
status, detail = _status(exc)
yield _error(status, detail)
return StreamingResponse(events(), media_type="text/event-stream")