File size: 3,249 Bytes
2415446
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
"""Ingress-owned HTTP request correlation."""

import uuid

from fastapi import Request, Response
from loguru import logger
from starlette.datastructures import Headers, MutableHeaders
from starlette.types import ASGIApp, Message, Receive, Scope, Send

from free_claude_code.core.trace import extract_claude_session_id_from_headers

REQUEST_ID_HEADER = "request-id"
OPENAI_REQUEST_ID_HEADER = "x-request-id"
_REQUEST_ID_STATE_ATTRIBUTE = "fcc_request_id"
_OPENAI_REQUEST_ID_PATHS = frozenset({"/v1/responses", "/v1/models"})


class RequestCorrelationMiddleware:
    """Own one request id and logging context for the full ASGI response."""

    def __init__(self, app: ASGIApp) -> None:
        self._app = app

    async def __call__(
        self,
        scope: Scope,
        receive: Receive,
        send: Send,
    ) -> None:
        if scope["type"] != "http":
            await self._app(scope, receive, send)
            return

        request_id = new_request_id()
        state = scope.setdefault("state", {})
        state[_REQUEST_ID_STATE_ATTRIBUTE] = request_id
        method = scope.get("method", "")
        path = scope.get("path", "")
        request_headers = Headers(scope=scope)
        claude_sid = extract_claude_session_id_from_headers(request_headers)

        async def send_with_correlation(message: Message) -> None:
            if message["type"] == "http.response.start":
                message = dict(message)
                raw_headers = list(message.get("headers", ()))
                _set_request_id_headers(
                    MutableHeaders(raw=raw_headers),
                    request_id=request_id,
                    path=path,
                )
                message["headers"] = raw_headers
            await send(message)

        with logger.contextualize(
            http_method=method,
            http_path=path,
            claude_session_id=claude_sid,
            request_id=request_id,
        ):
            await self._app(scope, receive, send_with_correlation)


def new_request_id() -> str:
    """Return a new opaque FCC request identifier."""
    return f"req_{uuid.uuid4().hex}"


def set_request_id(request: Request, request_id: str) -> None:
    """Attach the ingress correlation identifier to request state."""
    setattr(request.state, _REQUEST_ID_STATE_ATTRIBUTE, request_id)


def get_request_id(request: Request) -> str:
    """Return the ingress correlation identifier, creating a fallback if needed."""
    request_id = getattr(request.state, _REQUEST_ID_STATE_ATTRIBUTE, None)
    if isinstance(request_id, str) and request_id:
        return request_id
    request_id = new_request_id()
    set_request_id(request, request_id)
    return request_id


def attach_request_id_headers(
    response: Response, *, request_id: str, path: str
) -> None:
    """Attach correlation when an outer server-error boundary bypasses middleware."""
    _set_request_id_headers(response.headers, request_id=request_id, path=path)


def _set_request_id_headers(
    headers: MutableHeaders,
    *,
    request_id: str,
    path: str,
) -> None:
    headers[REQUEST_ID_HEADER] = request_id
    if path in _OPENAI_REQUEST_ID_PATHS:
        headers[OPENAI_REQUEST_ID_HEADER] = request_id