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
@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, request=raw_request)
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:
resume_stream = getattr(host, "resume_stream", None)
if callable(resume_stream):
source = resume_stream(pause_token, answer, scope=request_scope)
async with aclosing(source):
async for event in source:
yield _typed(event)
else:
result = await host.resume(pause_token, answer, scope=request_scope)
if hasattr(result, "__aiter__"):
async with aclosing(result):
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")