نسخهٔ وب مستقل — پلتفرم نیروی کار هوش مصنوعی

مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت.
تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
This commit is contained in:
mjalmoudy94
2026-10-13 00:39:28 +03:30
commit 717e53a7ce
185 changed files with 39910 additions and 0 deletions
@@ -0,0 +1,139 @@
"""Per-request correlation and actor context.
Every request gets an id before it reaches a route. That id is returned in the
``X-Request-ID`` response header, included in every error body, and written onto
every error record — so a user reporting "it broke" can hand over one string
that points at exactly one row in the error console.
The same object carries the resolved tenant and user. Authentication happens in
dependencies, long after the middleware has run, so the middleware seeds the
context with what it knows (method, path, ip, user agent) and the auth
dependencies fill in the rest via :func:`note_actor`. Error handlers then read
one object instead of re-deriving anything.
Deliberately stored on ``request.state`` rather than in a ``contextvar``: it is
the same object the handler already has in hand, it survives every task
boundary Starlette may introduce, and it cannot leak between requests.
"""
from __future__ import annotations
import uuid
from dataclasses import dataclass
from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.responses import Response
#: Header used to both accept a caller-supplied id and echo it back. Accepting
#: an upstream id matters when the API sits behind a reverse proxy or a
#: frontend BFF that already correlates its own logs.
REQUEST_ID_HEADER = "X-Request-ID"
#: Long enough to be unique, short enough to paste into a bug report.
REQUEST_ID_LENGTH = 16
_STATE_ATTR = "error_context"
@dataclass(slots=True)
class RequestContext:
"""What we know about the request an error happened on."""
request_id: str
method: str
path: str
ip: str = ""
user_agent: str = ""
#: Filled in by the auth dependencies once the caller is authenticated.
organization_id: str | None = None
user_id: str | None = None
#: Set when a failure happens inside a specific agent run.
run_id: str | None = None
#: Query string, kept separate from ``path`` so fingerprints group across
#: different filters on the same endpoint.
query: str = ""
def new_request_id() -> str:
return uuid.uuid4().hex[:REQUEST_ID_LENGTH]
def client_ip(request: Request) -> str:
"""Real client address, honouring one proxy hop.
Same rule as ``deps.client_meta``: a trusted reverse proxy sets
``X-Forwarded-For``, and the left-most entry is the original caller.
"""
forwarded = request.headers.get("x-forwarded-for", "")
if forwarded:
return forwarded.split(",")[0].strip()
return request.client.host if request.client else ""
def context_of(request: Request) -> RequestContext | None:
"""The context for this request, or ``None`` outside the middleware."""
return getattr(request.state, _STATE_ATTR, None)
def note_actor(
request: Request,
*,
user_id: str | None = None,
organization_id: str | None = None,
run_id: str | None = None,
) -> None:
"""Record who/which tenant this request belongs to.
Called from the auth dependencies. Silent when there is no context (a
direct unit-test call to a dependency, for instance) — correlation is
telemetry, and telemetry must never be the reason a request fails.
"""
context = context_of(request)
if context is None:
return
if user_id:
context.user_id = user_id
if organization_id:
context.organization_id = organization_id
if run_id:
context.run_id = run_id
class RequestContextMiddleware(BaseHTTPMiddleware):
"""Attach a :class:`RequestContext` to every request and echo its id."""
async def dispatch(
self, request: Request, call_next: RequestResponseEndpoint
) -> Response:
incoming = request.headers.get(REQUEST_ID_HEADER, "").strip()
# Cap the accepted value: an unbounded header would let a caller write
# arbitrarily long ids into the error table.
request_id = incoming[:64] if incoming else new_request_id()
setattr( # noqa: B010 - the attribute name is a module constant
request.state,
_STATE_ATTR,
RequestContext(
request_id=request_id,
method=request.method,
path=request.url.path,
query=request.url.query or "",
ip=client_ip(request),
user_agent=request.headers.get("user-agent", "")[:255],
),
)
response = await call_next(request)
response.headers[REQUEST_ID_HEADER] = request_id
return response
__all__ = [
"REQUEST_ID_HEADER",
"RequestContext",
"RequestContextMiddleware",
"client_ip",
"context_of",
"new_request_id",
"note_actor",
]