diff --git a/litellm/integrations/langfuse/langfuse_sdk.py b/litellm/integrations/langfuse/langfuse_sdk.py index 1bbc18d5dd4..51b08cb72df 100644 --- a/litellm/integrations/langfuse/langfuse_sdk.py +++ b/litellm/integrations/langfuse/langfuse_sdk.py @@ -317,9 +317,6 @@ class _RequestedIdGenerator(RandomIdGenerator): the tracer's id generator, so the request rides on a context variable instead. """ - def is_trace_id_random(self) -> bool: - return _requested_trace_id.get() is None - def generate_trace_id(self) -> int: requested: Final = _requested_trace_id.get() return super().generate_trace_id() if requested is None else requested diff --git a/litellm/integrations/opentelemetry.py b/litellm/integrations/opentelemetry.py index 0f1212721d9..0d0e309034a 100644 --- a/litellm/integrations/opentelemetry.py +++ b/litellm/integrations/opentelemetry.py @@ -3161,14 +3161,13 @@ class OpenTelemetry(OTELGenAISemconvMixin, CustomLogger): otel_exporter, ) normalized_endpoint = self._normalize_otel_endpoint(otel_endpoint, "traces") - from litellm.integrations.otel.plumbing.otlp_transport import build_otlp_http_transport - - tls: Final = resolve_otlp_http_tls("TRACES", _split_otel_headers) + tls: Final = resolve_otlp_http_tls("TRACES") return BatchSpanProcessor( OTLPSpanExporterHTTP( endpoint=normalized_endpoint, headers=_split_otel_headers, - _transport=build_otlp_http_transport("TRACES", tls), + certificate_file=tls.certificate_file, + session=tls.session, ), ) elif otel_exporter == "otlp_grpc" or otel_exporter == "grpc": @@ -3250,13 +3249,12 @@ class OpenTelemetry(OTELGenAISemconvMixin, CustomLogger): self.OTEL_EXPORTER, normalized_endpoint, ) - from litellm.integrations.otel.plumbing.otlp_transport import build_otlp_http_transport - - tls: Final = resolve_otlp_http_tls("LOGS", _split_otel_headers) + tls: Final = resolve_otlp_http_tls("LOGS") return OTLPLogExporter( endpoint=normalized_endpoint, headers=_split_otel_headers, - _transport=build_otlp_http_transport("LOGS", tls), + certificate_file=tls.certificate_file, + session=tls.session, ) elif self.OTEL_EXPORTER == "otlp_grpc" or self.OTEL_EXPORTER == "grpc": try: @@ -3320,13 +3318,12 @@ class OpenTelemetry(OTELGenAISemconvMixin, CustomLogger): OTLPMetricExporter, ) - from litellm.integrations.otel.plumbing.otlp_transport import build_otlp_http_transport - - tls: Final = resolve_otlp_http_tls("METRICS", _split_otel_headers) + tls: Final = resolve_otlp_http_tls("METRICS") exporter = OTLPMetricExporter( endpoint=normalized_endpoint, headers=_split_otel_headers, - _transport=build_otlp_http_transport("METRICS", tls), + certificate_file=tls.certificate_file, + session=tls.session, ) return PeriodicExportingMetricReader(exporter, export_interval_millis=5000) diff --git a/litellm/integrations/otel/mount.py b/litellm/integrations/otel/mount.py index c033bc1935b..cb52ad24c8e 100644 --- a/litellm/integrations/otel/mount.py +++ b/litellm/integrations/otel/mount.py @@ -126,9 +126,6 @@ def instrument_fastapi_app(app: "FastAPI", tracer_provider: "TracerProvider | No try: if not is_otel_v2_enabled(): return - if app.middleware_stack is not None: - verbose_logger.debug("Skipping OTel V2 FastAPI instrumentation after the middleware stack was built") - return # Lazy: only the V2-enabled path needs the optional # ``opentelemetry-instrumentation-fastapi`` package, which is not part of the diff --git a/litellm/integrations/otel/plumbing/otlp_json.py b/litellm/integrations/otel/plumbing/otlp_json.py index f3f4bb897b7..bc6d7d435b0 100644 --- a/litellm/integrations/otel/plumbing/otlp_json.py +++ b/litellm/integrations/otel/plumbing/otlp_json.py @@ -1,26 +1,77 @@ -"""OTLP/JSON export with the same requests and TLS configuration as OTLP/protobuf.""" +"""OTLP/HTTP span exporter that sends the OTLP/JSON encoding instead of protobuf. -from collections.abc import Mapping -from typing import Final +The SDK only ships a protobuf OTLP/HTTP exporter; this reuses its transport and +retry loop and swaps the payload for OTLP/JSON (enums as integers, ids as hex). +""" + +import base64 +import json +from collections.abc import Mapping, Sequence +from types import MappingProxyType +from typing import Final, TypeAlias import requests -from opentelemetry.exporter.otlp.json.http.trace_exporter import OTLPSpanExporter +from google.protobuf.json_format import MessageToDict +from opentelemetry.exporter.otlp.proto.common.trace_encoder import encode_spans +from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter +from opentelemetry.sdk.trace import ReadableSpan -from litellm.integrations.otel.plumbing.otlp_tls import OtlpHttpTls -from litellm.integrations.otel.plumbing.otlp_transport import build_otlp_http_transport +JSON_CONTENT_TYPE: Final = "application/json" +_HEX_ID_KEYS: Final = frozenset({"traceId", "spanId", "parentSpanId"}) + +_JsonValue: TypeAlias = "Mapping[str, _JsonValue] | Sequence[_JsonValue] | str | int | float | bool | None" +_JsonObject: TypeAlias = Mapping[str, "_JsonValue"] + + +def _objects(node: _JsonObject, key: str) -> tuple[_JsonObject, ...]: + items: Final = node.get(key) + if isinstance(items, str) or not isinstance(items, Sequence): + return () + return tuple(item for item in items if isinstance(item, Mapping)) + + +def _hex_ids(node: _JsonObject) -> _JsonObject: + return MappingProxyType( + { + key: base64.b64decode(item).hex() if key in _HEX_ID_KEYS and isinstance(item, str) else item + for key, item in node.items() + } + ) + + +def _hex_span(span: _JsonObject) -> _JsonObject: + links: Final = _objects(span, "links") + if not links: + return _hex_ids(span) + return MappingProxyType({**_hex_ids(span), "links": tuple(_hex_ids(link) for link in links)}) + + +def _hex_scope_spans(scope: _JsonObject) -> _JsonObject: + return MappingProxyType({**scope, "spans": tuple(_hex_span(span) for span in _objects(scope, "spans"))}) + + +def _hex_resource_spans(resource: _JsonObject) -> _JsonObject: + scope_spans: Final = tuple(_hex_scope_spans(scope) for scope in _objects(resource, "scopeSpans")) + return MappingProxyType({**resource, "scopeSpans": scope_spans}) + + +def encode_spans_json(spans: Sequence[ReadableSpan]) -> bytes: + payload: Final[_JsonObject] = MessageToDict(encode_spans(spans), use_integers_for_enums=True) + resource_spans: Final = tuple(_hex_resource_spans(resource) for resource in _objects(payload, "resourceSpans")) + hexed: Final[_JsonObject] = MappingProxyType({**payload, "resourceSpans": resource_spans}) + return json.dumps(hexed, default=dict, separators=(",", ":")).encode() class OTLPJsonSpanExporter(OTLPSpanExporter): def __init__( self, endpoint: str | None, - headers: Mapping[str, str], + headers: dict[str, str], # mutable-ok: SDK __init__ takes Dict certificate_file: str | None = None, - session: requests.Session | None = None, + session: "requests.Session | None" = None, ) -> None: - transport: Final = build_otlp_http_transport( - "TRACES", OtlpHttpTls(certificate_file=certificate_file, session=session), use_credential_provider=False - ) - super().__init__( - endpoint=endpoint, headers={**headers, "content-type": "application/json"}, _transport=transport - ) + super().__init__(endpoint=endpoint, headers=headers, certificate_file=certificate_file, session=session) + self._session.headers["Content-Type"] = JSON_CONTENT_TYPE + + def _serialize_spans(self, spans: Sequence[ReadableSpan]) -> bytes: + return encode_spans_json(spans) diff --git a/litellm/integrations/otel/plumbing/otlp_tls.py b/litellm/integrations/otel/plumbing/otlp_tls.py index 959e45e9bd7..9de97e0c77a 100644 --- a/litellm/integrations/otel/plumbing/otlp_tls.py +++ b/litellm/integrations/otel/plumbing/otlp_tls.py @@ -1,19 +1,11 @@ import os -from collections.abc import Mapping -from copy import copy from dataclasses import dataclass from typing import Final, Literal import requests -from pydantic import BaseModel, ConfigDict from requests.adapters import HTTPAdapter -class _RequestHeaders(BaseModel): - model_config = ConfigDict(frozen=True, from_attributes=True) - headers: Mapping[str, str] | None = None - - @dataclass(frozen=True, slots=True) class OtlpHttpTls: certificate_file: str | None @@ -33,65 +25,17 @@ class _NoVerifyAdapter(HTTPAdapter): ) -class _IsolatedHeadersSession(requests.Session): - def __init__(self, signal: str, headers: Mapping[str, str], content_type: str) -> None: - from opentelemetry.util.re import parse_env_headers - - super().__init__() - environment: Final = parse_env_headers( - os.getenv(f"OTEL_EXPORTER_OTLP_{signal}_HEADERS") or os.getenv("OTEL_EXPORTER_OTLP_HEADERS", ""), - liberal=True, - ) - explicit: Final = frozenset(key.lower() for key in headers) - self._excluded_headers: Final = frozenset(key.lower() for key in environment) - explicit - self._content_type: Final = content_type - self._compression: Final = ( - ( - os.getenv(f"OTEL_EXPORTER_OTLP_{signal}_COMPRESSION") - or os.getenv("OTEL_EXPORTER_OTLP_COMPRESSION", "none") - ) - .strip() - .lower() - ) - - def prepare_request(self, request: requests.Request) -> requests.PreparedRequest: - isolated: Final = copy(request) - source: Final[Mapping[str, str]] = _RequestHeaders.model_validate(request).headers or {} - kept: Final = {key: value for key, value in source.items() if key.lower() not in self._excluded_headers} - content_type: Final = {"Content-Type": self._content_type} if "content-type" in self._excluded_headers else {} - compression: Final = ( - {"Content-Encoding": self._compression} - if "content-encoding" in self._excluded_headers and self._compression in ("gzip", "deflate") - else {} - ) - isolated.headers = {**kept, **content_type, **compression} - session: Final = copy(self) - session.headers = { - key: value for key, value in self.headers.items() if key.lower() not in self._excluded_headers - } - return requests.Session.prepare_request(session, isolated) - - -def resolve_otlp_http_tls( - signal: Literal["TRACES", "METRICS", "LOGS"], - headers: Mapping[str, str] | None = None, - *, - content_type: str = "application/x-protobuf", - isolate_headers: bool = False, -) -> OtlpHttpTls: - session: Final = ( - _IsolatedHeadersSession(signal, headers or {}, content_type) if headers or isolate_headers else None - ) +def resolve_otlp_http_tls(signal: Literal["TRACES", "METRICS", "LOGS"]) -> OtlpHttpTls: if os.getenv(f"OTEL_EXPORTER_OTLP_{signal}_CERTIFICATE") or os.getenv("OTEL_EXPORTER_OTLP_CERTIFICATE"): - return OtlpHttpTls(certificate_file=None, session=session) + return OtlpHttpTls(certificate_file=None, session=None) from litellm.llms.custom_httpx.http_handler import get_ssl_verify verify: Final = get_ssl_verify() if verify is False: - unverified: Final = session or requests.Session() - unverified.mount("https://", _NoVerifyAdapter()) - return OtlpHttpTls(certificate_file=None, session=unverified) + session: Final = requests.Session() + session.mount("https://", _NoVerifyAdapter()) + return OtlpHttpTls(certificate_file=None, session=session) if isinstance(verify, str): - return OtlpHttpTls(certificate_file=verify, session=session) - return OtlpHttpTls(certificate_file=None, session=session) + return OtlpHttpTls(certificate_file=verify, session=None) + return OtlpHttpTls(certificate_file=None, session=None) diff --git a/litellm/integrations/otel/plumbing/otlp_transport.py b/litellm/integrations/otel/plumbing/otlp_transport.py deleted file mode 100644 index 31ff3a85c17..00000000000 --- a/litellm/integrations/otel/plumbing/otlp_transport.py +++ /dev/null @@ -1,95 +0,0 @@ -"""Keep explicit collector TLS settings authoritative at request time.""" - -import os -from collections.abc import Mapping -from typing import Final, Literal - -import requests -from opentelemetry.exporter.http.transport._requests import RequestsHTTPResult, RequestsHTTPTransport -from opentelemetry.exporter.otlp.proto.http._common import ( - _load_session_from_envvar, # pyright: ignore[reportPrivateUsage] # pinned SDK loader preserves operator auth -) - -from litellm.integrations.otel.plumbing.otlp_tls import OtlpHttpTls - - -class _OtlpHTTPTransport(RequestsHTTPTransport): - def __init__( - self, - verify: bool | str, - cert: str | tuple[str, str] | None, - session: requests.Session | None, - ) -> None: - self._request_session: Final = session if session is not None else requests.Session() - self._request_verify: Final = verify - super().__init__(verify=verify, cert=cert, session=self._request_session) - - def request( - self, - method: str, - url: str, - *, - headers: Mapping[str, str] | None = None, - timeout: float | None = None, - data: bytes | None = None, - ) -> RequestsHTTPResult: - try: - response: Final = self._request_session.request( - method=method, - url=url, - headers=headers, - data=data, - timeout=timeout, - verify=self._request_verify, - allow_redirects=False, - ) - except Exception as error: # noqa: BLE001 # SDK transport returns all session and auth failures as values - return RequestsHTTPResult(error=error) - return RequestsHTTPResult(status_code=response.status_code, reason=response.reason, response=response) - - -def build_otlp_http_transport( - signal: Literal["TRACES", "LOGS", "METRICS"], - tls: OtlpHttpTls, - *, - use_credential_provider: bool = True, - client_key_file: str | None = None, - client_certificate_file: str | None = None, -) -> RequestsHTTPTransport: - verify: Final = ( - tls.certificate_file - or os.getenv(f"OTEL_EXPORTER_OTLP_{signal}_CERTIFICATE") - or os.getenv("OTEL_EXPORTER_OTLP_CERTIFICATE") - or True - ) - client_key: Final = ( - client_key_file - or os.getenv(f"OTEL_EXPORTER_OTLP_{signal}_CLIENT_KEY") - or os.getenv("OTEL_EXPORTER_OTLP_CLIENT_KEY") - ) - client_certificate: Final = ( - client_certificate_file - or os.getenv(f"OTEL_EXPORTER_OTLP_{signal}_CLIENT_CERTIFICATE") - or os.getenv("OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE") - ) - credential_variable: Final[ - Literal[ - "OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER", - "OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER", - "OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER", - ] - ] = ( - "OTEL_PYTHON_EXPORTER_OTLP_HTTP_TRACES_CREDENTIAL_PROVIDER" - if signal == "TRACES" - else "OTEL_PYTHON_EXPORTER_OTLP_HTTP_LOGS_CREDENTIAL_PROVIDER" - if signal == "LOGS" - else "OTEL_PYTHON_EXPORTER_OTLP_HTTP_METRICS_CREDENTIAL_PROVIDER" - ) - session: Final = tls.session or ( - _load_session_from_envvar(credential_variable) if use_credential_provider else None - ) - return _OtlpHTTPTransport( - verify=verify, - cert=(client_certificate, client_key) if client_certificate and client_key else client_certificate, - session=session, - ) diff --git a/litellm/integrations/otel/plumbing/providers.py b/litellm/integrations/otel/plumbing/providers.py index f5c1637ee63..898eab5715b 100644 --- a/litellm/integrations/otel/plumbing/providers.py +++ b/litellm/integrations/otel/plumbing/providers.py @@ -16,9 +16,9 @@ from opentelemetry.metrics import MeterProvider, NoOpMeterProvider from opentelemetry.sdk._logs import LoggerProvider as SDKLoggerProvider from opentelemetry.sdk._logs.export import ( BatchLogRecordProcessor, - ConsoleLogRecordExporter, - InMemoryLogRecordExporter, - LogRecordExporter, + ConsoleLogExporter, + InMemoryLogExporter, + LogExporter, SimpleLogRecordProcessor, ) from opentelemetry.sdk.metrics import MeterProvider as SDKMeterProvider @@ -194,7 +194,7 @@ def exporter_transport(kind: str) -> Literal["http", "grpc", "headerless"]: return "headerless" -def _exporter_from_spec(spec: ExporterSpec, *, isolate_headers: bool = False) -> SpanExporter: +def _exporter_from_spec(spec: ExporterSpec) -> SpanExporter: kind: Final = (spec.kind or "console").lower() factory: Final = _EXPORTER_FACTORIES.get(kind) if factory is not None: @@ -204,9 +204,7 @@ def _exporter_from_spec(spec: ExporterSpec, *, isolate_headers: bool = False) -> if kind in _OTLP_HTTP_JSON_KINDS: from litellm.integrations.otel.plumbing.otlp_json import OTLPJsonSpanExporter - tls: Final = resolve_otlp_http_tls( - "TRACES", parse_headers(spec.headers), content_type="application/json", isolate_headers=isolate_headers - ) + tls: Final = resolve_otlp_http_tls("TRACES") return OTLPJsonSpanExporter( endpoint=spec.traces_endpoint or _otlp_traces_endpoint(spec.endpoint), headers=parse_headers(spec.headers), @@ -218,13 +216,12 @@ def _exporter_from_spec(spec: ExporterSpec, *, isolate_headers: bool = False) -> OTLPSpanExporter as HTTPExporter, ) - from litellm.integrations.otel.plumbing.otlp_transport import build_otlp_http_transport - - http_tls: Final = resolve_otlp_http_tls("TRACES", parse_headers(spec.headers), isolate_headers=isolate_headers) + http_tls: Final = resolve_otlp_http_tls("TRACES") return HTTPExporter( endpoint=spec.traces_endpoint or _otlp_traces_endpoint(spec.endpoint), headers=parse_headers(spec.headers), - _transport=build_otlp_http_transport("TRACES", http_tls), + certificate_file=http_tls.certificate_file, + session=http_tls.session, ) if kind in _OTLP_GRPC_KINDS: from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( @@ -1016,7 +1013,7 @@ def _destination_processor(destination: "OtelDestination") -> SpanProcessor | No headers=destination.header_string(), owner=None, ) - return _processor_for(_exporter_from_spec(spec, isolate_headers=True), use_simple=False) + return _processor_for(_exporter_from_spec(spec), use_simple=False) except Exception as exc: # noqa: BLE001 # a malformed destination must not break the request or the other destinations verbose_logger.debug("OTel V2 fan-out: no processor for %s: %s", destination.endpoint, exc) return None @@ -1189,13 +1186,12 @@ def build_metric_reader(config: OpenTelemetryV2Config) -> "MetricReader": OTLPMetricExporter as HTTPMetricExporter, ) - from litellm.integrations.otel.plumbing.otlp_transport import build_otlp_http_transport - - tls: Final = resolve_otlp_http_tls("METRICS", parse_headers(config.headers)) + tls: Final = resolve_otlp_http_tls("METRICS") exporter: object = HTTPMetricExporter( endpoint=_otlp_metrics_endpoint(config.endpoint), headers=parse_headers(config.headers), - _transport=build_otlp_http_transport("METRICS", tls), + certificate_file=tls.certificate_file, + session=tls.session, ) elif kind in ("otlp_grpc", "grpc"): try: @@ -1237,7 +1233,7 @@ def _otlp_logs_endpoint(endpoint: str | None) -> str | None: return endpoint + "/v1/logs" -def build_log_exporter(config: OpenTelemetryV2Config) -> LogRecordExporter: +def build_log_exporter(config: OpenTelemetryV2Config) -> LogExporter: """Build a log exporter mirroring the exporter selection of the other signals. ``console`` (and any unrecognized kind) exports to the console; ``otlp_http`` @@ -1247,19 +1243,18 @@ def build_log_exporter(config: OpenTelemetryV2Config) -> LogRecordExporter: """ kind: Final = (config.exporter or "console").lower() if kind in ("in_memory", "inmemory", "memory"): - return InMemoryLogRecordExporter() + return InMemoryLogExporter() if kind in ("otlp_http", "http", "http/protobuf", "http/json"): from opentelemetry.exporter.otlp.proto.http._log_exporter import ( OTLPLogExporter as HTTPLogExporter, ) - from litellm.integrations.otel.plumbing.otlp_transport import build_otlp_http_transport - - tls: Final = resolve_otlp_http_tls("LOGS", parse_headers(config.headers)) + tls: Final = resolve_otlp_http_tls("LOGS") return HTTPLogExporter( endpoint=_otlp_logs_endpoint(config.endpoint), headers=parse_headers(config.headers), - _transport=build_otlp_http_transport("LOGS", tls), + certificate_file=tls.certificate_file, + session=tls.session, ) if kind in ("otlp_grpc", "grpc"): try: @@ -1273,24 +1268,24 @@ def build_log_exporter(config: OpenTelemetryV2Config) -> LogRecordExporter: ) from exc return GRPCLogExporter(endpoint=config.endpoint, headers=parse_headers(config.headers)) - return ConsoleLogRecordExporter() + return ConsoleLogExporter() def build_logger_provider( config: OpenTelemetryV2Config, - log_exporter: LogRecordExporter | None = None, + log_exporter: LogExporter | None = None, ) -> SDKLoggerProvider: """Build the :class:`LoggerProvider` GenAI events export through. ``log_exporter`` is an explicit override (tests inject an - ``InMemoryLogRecordExporter``); otherwise the exporter is selected from the config's + ``InMemoryLogExporter``); otherwise the exporter is selected from the config's exporter kind via :func:`build_log_exporter`. Console and in-memory exporters get a Simple processor (synchronous export, which tests rely on), everything else a Batch processor — the same split as span processing. """ exporter: Final = log_exporter if log_exporter is not None else build_log_exporter(config) provider: Final = SDKLoggerProvider(resource=build_resource(config)) - use_simple: Final = isinstance(exporter, (ConsoleLogRecordExporter, InMemoryLogRecordExporter)) + use_simple: Final = isinstance(exporter, (ConsoleLogExporter, InMemoryLogExporter)) provider.add_log_record_processor( SimpleLogRecordProcessor(exporter) if use_simple else BatchLogRecordProcessor(exporter) ) diff --git a/litellm/integrations/otel/presets/agentops.py b/litellm/integrations/otel/presets/agentops.py index 3408adbfb5f..3befb73b0cc 100644 --- a/litellm/integrations/otel/presets/agentops.py +++ b/litellm/integrations/otel/presets/agentops.py @@ -9,14 +9,14 @@ this preset registers a custom exporter (``kind="agentops"``) that mints the JWT worker thread, off any event loop — and caches it for the process lifetime. """ +from collections.abc import Sequence from typing import Final import httpx -import requests -from opentelemetry.sdk.trace.export import SpanExporter +from opentelemetry.sdk.trace import ReadableSpan +from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict -from requests.auth import AuthBase from litellm._logging import verbose_logger from litellm.integrations.otel.model.config import ( @@ -24,7 +24,6 @@ from litellm.integrations.otel.model.config import ( ExporterSpec, OpenTelemetryV2Config, ) -from litellm.integrations.otel.plumbing.otlp_tls import OtlpHttpTls, resolve_otlp_http_tls from litellm.integrations.otel.plumbing.providers import register_exporter_factory _AGENTOPS_ENDPOINT: Final = "https://otlp.agentops.ai/v1/traces" @@ -75,44 +74,47 @@ def agentops_preset( ) -class _AgentOpsAuth(AuthBase): - def __init__(self, api_key: str | None) -> None: - self._api_key = api_key - self._resolved = False - self._token: str | None = None - - def __call__(self, request: requests.PreparedRequest) -> requests.PreparedRequest: - if not self._resolved: - self._resolved = True - if self._api_key: - try: - token: Final = _fetch_agentops_jwt(self._api_key).get("token") - if isinstance(token, str) and token: - self._token = token - except Exception as error: - verbose_logger.debug("AgentOps JWT fetch failed: %s", error) - authenticated: Final = request.copy() - if self._token: - authenticated.headers["Authorization"] = f"Bearer {self._token}" - return authenticated - - def _build_agentops_exporter(spec: ExporterSpec) -> SpanExporter: - """Authenticate lazily through requests' public session authentication API.""" - from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter + """Factory for the ``agentops`` exporter kind: a lazy-auth OTLP/HTTP exporter.""" + from opentelemetry.exporter.otlp.proto.http.trace_exporter import ( + OTLPSpanExporter, + ) - from litellm.integrations.otel.plumbing.otlp_transport import build_otlp_http_transport + class _LazyAuthAgentOpsExporter(OTLPSpanExporter): + """OTLP/HTTP exporter that mints the AgentOps JWT on its first export. + + ``export`` runs in the ``BatchSpanProcessor`` worker thread, so the + blocking token fetch never touches an event loop. The result is cached + after the first attempt (success or failure) so it runs at most once. + """ + + def __init__(self, *, endpoint: str | None, api_key: str | None) -> None: + super().__init__(endpoint=endpoint) + self._agentops_api_key = api_key + self._auth_resolved = False + + def _ensure_authenticated(self) -> None: + if self._auth_resolved: + return + self._auth_resolved = True + if not self._agentops_api_key: + return + try: + token: Final = _fetch_agentops_jwt(self._agentops_api_key).get("token") + if token: + # ``_session`` is the requests.Session the base exporter + # POSTs through; updating its Authorization header is how the + # minted JWT reaches every subsequent export. + self._session.headers["Authorization"] = f"Bearer {token}" + except Exception as e: + verbose_logger.debug("AgentOps JWT fetch failed: %s", e) + + def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: + self._ensure_authenticated() + return super().export(spans) options: Final = spec.options or {} - tls: Final = resolve_otlp_http_tls("TRACES", isolate_headers=True) - session: Final = tls.session or requests.Session() - session.auth = _AgentOpsAuth(options.get("api_key")) - return OTLPSpanExporter( - endpoint=spec.endpoint, - _transport=build_otlp_http_transport( - "TRACES", OtlpHttpTls(certificate_file=tls.certificate_file, session=session) - ), - ) + return _LazyAuthAgentOpsExporter(endpoint=spec.endpoint, api_key=options.get("api_key")) def _fetch_agentops_jwt(api_key: str) -> dict[str, object]: diff --git a/litellm/proxy/enterprise_billing/billing_metrics.py b/litellm/proxy/enterprise_billing/billing_metrics.py index 90616e8acb1..166ece28b83 100644 --- a/litellm/proxy/enterprise_billing/billing_metrics.py +++ b/litellm/proxy/enterprise_billing/billing_metrics.py @@ -25,8 +25,6 @@ from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader from opentelemetry.sdk.resources import Resource from litellm._logging import verbose_proxy_logger -from litellm.integrations.otel.plumbing.otlp_tls import OtlpHttpTls -from litellm.integrations.otel.plumbing.otlp_transport import build_otlp_http_transport from litellm.proxy.middleware.billable_request_metrics_middleware import ( BillableCategory, ) @@ -106,12 +104,10 @@ def build_mtls_meter_provider(config: BillingMetricsConfig) -> MeterProvider: """ exporter: Final = OTLPMetricExporter( endpoint=_metrics_endpoint(config.endpoint), - _transport=build_otlp_http_transport( - "METRICS", - OtlpHttpTls(certificate_file=config.ca_cert_path, session=None), - client_certificate_file=config.client_cert_path, - client_key_file=config.client_key_path, - ), + # None -> exporter falls back to the system trust store. + certificate_file=config.ca_cert_path, + client_certificate_file=config.client_cert_path, + client_key_file=config.client_key_path, ) reader: Final = PeriodicExportingMetricReader(exporter, export_interval_millis=config.export_interval_ms) return MeterProvider(metric_readers=[reader], resource=Resource.create(_resource_attributes(config))) diff --git a/pyproject.toml b/pyproject.toml index 1789d5503c0..3aefd911937 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -177,17 +177,16 @@ proxy-runtime = [ "grpcio==1.78.0", "prometheus-client>=0.20.0,<1.0", "langfuse>=4.7,<5.0", - "opentelemetry-api==1.45.1", - "opentelemetry-sdk==1.45.1", - "opentelemetry-exporter-otlp==1.45.1", - "opentelemetry-exporter-otlp-json-http==0.66b1", - "opentelemetry-instrumentation-fastapi==0.66b1", + "opentelemetry-api==1.33.1", + "opentelemetry-sdk==1.33.1", + "opentelemetry-exporter-otlp==1.33.1", + "opentelemetry-instrumentation-fastapi==0.54b1", "ddtrace>=4.8.2,<5.0", "sentry-sdk>=2.21.0,<3.0", "mangum>=0.17.0,<1.0", "azure-ai-contentsafety>=1.0.0,<2.0", "azure-storage-file-datalake>=12.20.0,<13.0", - "pypdf>=6.21.1,<7.0", + "pypdf>=6.16.1,<7.0", "llm-sandbox>=0.3.39,<1.0", "detect-secrets>=1.5.0,<2.0", ] @@ -224,11 +223,10 @@ dev = [ "types-PyYAML==6.0.12.20250915", "botocore-stubs==1.43.14", "types-boto3[bedrock,bedrock-agent,bedrock-runtime,kms,s3,sagemaker-runtime,sts]==1.43.30", - "opentelemetry-api==1.45.1", - "opentelemetry-sdk==1.45.1", - "opentelemetry-exporter-otlp==1.45.1", - "opentelemetry-exporter-otlp-json-http==0.66b1", - "opentelemetry-instrumentation-fastapi==0.66b1", + "opentelemetry-api==1.33.1", + "opentelemetry-sdk==1.33.1", + "opentelemetry-exporter-otlp==1.33.1", + "opentelemetry-instrumentation-fastapi==0.54b1", "langfuse>=4.7,<5.0", "fastapi-offline==1.7.6", "fakeredis==2.34.1", @@ -257,11 +255,10 @@ proxy-dev = [ "hypercorn==0.17.3", "prometheus-client==0.20.0", "sentry-sdk==2.21.0", - "opentelemetry-api==1.45.1", - "opentelemetry-sdk==1.45.1", - "opentelemetry-exporter-otlp==1.45.1", - "opentelemetry-exporter-otlp-json-http==0.66b1", - "opentelemetry-instrumentation-fastapi==0.66b1", + "opentelemetry-api==1.33.1", + "opentelemetry-sdk==1.33.1", + "opentelemetry-exporter-otlp==1.33.1", + "opentelemetry-instrumentation-fastapi==0.54b1", "azure-identity==1.25.2", "a2a-sdk==1.1.0", "backports-zstd==1.8.0; python_version < '3.14'", @@ -283,7 +280,7 @@ ci = [ "langchain==1.3.9", "lunary==1.4.36; python_version == '3.10'", "lunary==1.4.37; python_version >= '3.11'", - "logfire==5.2.0", + "logfire==4.6.0", "traceloop-sdk==0.34.0", "detect-secrets==1.5.0", "PyGithub==2.8.1", diff --git a/tests/unit/integrations/langfuse/test_langfuse_sdk.py b/tests/unit/integrations/langfuse/test_langfuse_sdk.py index 8f5a8fc603b..cef2b13f0fd 100644 --- a/tests/unit/integrations/langfuse/test_langfuse_sdk.py +++ b/tests/unit/integrations/langfuse/test_langfuse_sdk.py @@ -165,30 +165,6 @@ def test_requested_trace_id_is_the_exported_trace_id_and_the_generation_is_its_r assert span.parent is None - -@pytest.mark.parametrize("trace_id", ["0" * 31 + "1", None]) -def test_trace_randomness_flag_matches_the_id_source(channel, trace_id): - from opentelemetry.context import Context - from litellm.integrations.langfuse.langfuse_sdk import _start_span - - tracing, exporter = channel - span = _start_span( - tracing.tracer, - name="id-source", - context=Context(), - start_time=CALL_START, - trace_id=trace_id, - observation_id=None, - attributes={}, - ) - span.end(end_time=to_unix_nanos(CALL_END)) - tracing.flush() - exported = _only_span(exporter, "id-source") - assert exported.context.trace_flags.random_trace_id is (trace_id is None) - assert exported.context.trace_flags.sampled - if trace_id is not None: - assert exported.context.trace_id == int(trace_id, 16) - def test_parent_observation_id_nests_the_generation_under_the_callers_observation(channel): tracing, exporter = channel _generation(tracing, name="child-gen", parent=PARENT_C).end(CALL_END) diff --git a/tests/unit/integrations/otel/test_otel_v2_components.py b/tests/unit/integrations/otel/test_otel_v2_components.py index 37392e3c77f..3f4389765d3 100644 --- a/tests/unit/integrations/otel/test_otel_v2_components.py +++ b/tests/unit/integrations/otel/test_otel_v2_components.py @@ -23,9 +23,8 @@ from opentelemetry.proto.collector.trace.v1.trace_service_pb2 import ( # noqa: from opentelemetry import baggage # noqa: E402 from opentelemetry.context import attach, detach # noqa: E402 from opentelemetry._logs.severity import SeverityNumber # noqa: E402 -from opentelemetry._logs import LogRecord -from opentelemetry.sdk._logs import ReadableLogRecord # noqa: E402 -from opentelemetry.sdk._logs.export import LogRecordExportResult # noqa: E402 +from opentelemetry.sdk._logs import LogData, LogRecord # noqa: E402 +from opentelemetry.sdk._logs.export import LogExportResult # noqa: E402 from opentelemetry.sdk.metrics import MeterProvider # noqa: E402 from opentelemetry.sdk.metrics.export import InMemoryMetricReader # noqa: E402 from opentelemetry.sdk.trace import TracerProvider # noqa: E402 @@ -677,9 +676,7 @@ def test_parse_headers_percent_decodes_values(): documents ``Authorization=Basic%20``. Forwarding the literal ``%20`` makes the backend reject the export as a malformed credential. """ - from base64 import b64encode - - token: Final = b64encode(b"TEST_ONLY_USER:TEST_ONLY_PASSWORD").decode() + token = "MTMzNzc4MzpnbGNfZXlKdklqb2lNVEl6TkNJPQ==" assert providers.parse_headers(f"Authorization=Basic%20{token}") == {"authorization": f"Basic {token}"} assert providers.parse_headers("x-scope-orgid=team%20a") == {"x-scope-orgid": "team a"} @@ -736,7 +733,7 @@ def _export_one_trace_to_local_collector(exporter_kind: str) -> tuple[list[dict] class Collector(BaseHTTPRequestHandler): def do_POST(self): body = self.rfile.read(int(self.headers["Content-Length"])) - received.append({"path": self.path, "headers": requests.structures.CaseInsensitiveDict(self.headers), "body": body}) + received.append({"path": self.path, "headers": dict(self.headers), "body": body}) self.send_response(200) self.end_headers() @@ -789,60 +786,13 @@ def test_http_json_exporter_posts_otlp_json_to_traces_endpoint(): assert parent["kind"] == 2 assert parent["traceId"] == format(trace_id, "032x") assert parent["spanId"] == format(parent_id, "016x") - assert parent.get("parentSpanId", "") == "" + assert "parentSpanId" not in parent child = _only_span(child_request) assert child["traceId"] == format(trace_id, "032x") assert child["spanId"] == format(child_id, "016x") assert child["parentSpanId"] == format(parent_id, "016x") - -@pytest.mark.parametrize("compression", ["none", "gzip", "deflate"]) -def test_http_json_exporter_preserves_session_tls_auth_and_compression(monkeypatch, requests_mock, compression): - import gzip - import zlib - - from opentelemetry.sdk.trace.export import SpanExportResult - from litellm.integrations.otel.plumbing.otlp_json import OTLPJsonSpanExporter - - monkeypatch.setenv("REQUESTS_CA_BUNDLE", "/corporate/global-ca.pem") - monkeypatch.setenv("OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE", "/collector/environment-ca.pem") - monkeypatch.setenv("OTEL_EXPORTER_OTLP_TRACES_COMPRESSION", compression) - monkeypatch.setenv("OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE", "/collector/client.crt") - monkeypatch.setenv("OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY", "/collector/client.key") - monkeypatch.setenv("HTTPS_PROXY", "http://proxy.example:8080") - collector = requests_mock.post("https://collector.example/v1/traces", status_code=200) - session = requests.Session() - session.headers["x-session-marker"] = "retained" - session.auth = requests.auth.HTTPBasicAuth("session-user", "synthetic-password") - exporter = OTLPJsonSpanExporter( - endpoint="https://collector.example/v1/traces", - headers={"x-exporter-marker": "configured"}, - certificate_file="/collector/ca.crt", - session=session, - ) - provider = TracerProvider() - span = provider.get_tracer(__name__).start_span("session-export") - span.end() - try: - assert exporter.export([span]) is SpanExportResult.SUCCESS - request = collector.last_request - assert request.headers["Content-Type"] == "application/json" - assert request.headers["x-session-marker"] == "retained" - assert request.headers["x-exporter-marker"] == "configured" - assert request.headers["Authorization"] == "Basic c2Vzc2lvbi11c2VyOnN5bnRoZXRpYy1wYXNzd29yZA==" - assert request.verify == "/collector/ca.crt" - assert request.cert == ("/collector/client.crt", "/collector/client.key") - assert request.proxies["https"] == "http://proxy.example:8080" - decompress = {"none": lambda body: body, "gzip": gzip.decompress, "deflate": zlib.decompress}[compression] - payload = json.loads(decompress(request.body)) - exported = payload["resourceSpans"][0]["scopeSpans"][0]["spans"][0] - assert exported["name"] == "session-export" - assert exported["traceId"] == f"{span.get_span_context().trace_id:032x}" - finally: - exporter.shutdown() - provider.shutdown() - def test_http_protobuf_exporter_still_posts_protobuf(): (_child_request, parent_request), (trace_id, _parent_id, _child_id) = _export_one_trace_to_local_collector( "http/protobuf" @@ -974,20 +924,20 @@ def test_otlp_logs_endpoint_normalization(): def test_build_log_exporter_variants(): - from opentelemetry.sdk._logs.export import ConsoleLogRecordExporter, InMemoryLogRecordExporter + from opentelemetry.sdk._logs.export import ConsoleLogExporter, InMemoryLogExporter assert isinstance( providers.build_log_exporter(OpenTelemetryV2Config(exporter="console")), - ConsoleLogRecordExporter, + ConsoleLogExporter, ) assert isinstance( providers.build_log_exporter(OpenTelemetryV2Config(exporter="in_memory")), - InMemoryLogRecordExporter, + InMemoryLogExporter, ) # An unrecognized kind falls back to console rather than dropping events. assert isinstance( providers.build_log_exporter(OpenTelemetryV2Config(exporter="unknown")), - ConsoleLogRecordExporter, + ConsoleLogExporter, ) http_exporter = providers.build_log_exporter(OpenTelemetryV2Config(exporter="otlp_http", endpoint="http://h:4318")) assert "OTLPLogExporter" in type(http_exporter).__name__ @@ -1680,13 +1630,9 @@ def test_v2_otlp_http_log_export_trusts_ssl_cert_file(monkeypatch: pytest.Monkey severity_number=SeverityNumber.INFO, body="v2-tls-test", ) - log_data = ReadableLogRecord( - log_record=record, - resource=providers.build_resource(cfg), - instrumentation_scope=InstrumentationScope("v2-tls-test"), - ) + log_data = LogData(log_record=record, instrumentation_scope=InstrumentationScope("v2-tls-test")) result = exporter.export([log_data]) - assert result is LogRecordExportResult.SUCCESS, f"log export failed: {result}" + assert result is LogExportResult.SUCCESS, f"log export failed: {result}" assert tls_sink.received.get(timeout=5) == "/v1/logs" finally: exporter.shutdown() diff --git a/tests/unit/integrations/otel/test_otel_v2_destinations.py b/tests/unit/integrations/otel/test_otel_v2_destinations.py index a8d8da9b426..c7a057cca5c 100644 --- a/tests/unit/integrations/otel/test_otel_v2_destinations.py +++ b/tests/unit/integrations/otel/test_otel_v2_destinations.py @@ -2,8 +2,6 @@ import asyncio import contextvars -import gzip -import os import threading import time from base64 import b64encode @@ -5069,267 +5067,3 @@ class TestArizeProjectRouting: monkeypatch.setenv("ARIZE_PROJECT_NAME", "gateway-prod") assert self._forwarded_projects() == {"gateway-prod"} - - -def _export_header_probe(api: str, signal: str, endpoint: str, headers: str | None) -> None: - from opentelemetry.sdk.metrics import MeterProvider - - from litellm.integrations.opentelemetry import OpenTelemetry, OpenTelemetryConfig - - config: Final = OpenTelemetryV2Config( - exporter="http/json" if api == "v2-json" else "otlp_http", endpoint=endpoint, headers=headers - ) - tracing: Final = TracerProvider() - legacy: Final = ( - OpenTelemetry( - config=OpenTelemetryConfig(exporter="otlp_http", endpoint=endpoint, headers=headers), - tracer_provider=tracing, - ) - if api == "v1" - else None - ) - try: - if signal == "metrics": - reader: Final = legacy._get_metric_reader() if legacy else otel_providers.build_metric_reader(config) - metrics: Final = MeterProvider(metric_readers=(reader,)) - try: - metrics.get_meter("header-isolation").create_counter("header_probe").add(1) - assert metrics.force_flush() - finally: - metrics.shutdown() - elif signal == "logs": - logs: Final = legacy._get_log_exporter() if legacy else otel_providers.build_log_exporter(config) - try: - assert logs.export(()).name == "SUCCESS" - finally: - logs.shutdown() - elif legacy: - processor: Final = legacy._get_span_processor() - try: - assert processor.span_exporter.export(()).name == "SUCCESS" - finally: - processor.shutdown() - else: - spans: Final = otel_providers.build_span_exporter(config) - try: - assert spans.export(()).name == "SUCCESS" - finally: - spans.shutdown() - finally: - tracing.shutdown() - - -@pytest.mark.parametrize( - ("api", "signal"), - ( - ("v1", "traces"), - ("v1", "metrics"), - ("v1", "logs"), - ("v2", "traces"), - ("v2", "metrics"), - ("v2", "logs"), - ("v2-json", "traces"), - ), -) -@pytest.mark.parametrize("signal_headers", (False, True)) -def test_http_destinations_do_not_inherit_other_accounts_headers( - monkeypatch, requests_mock, api: str, signal: str, signal_headers: bool -) -> None: - variable: Final = f"OTEL_EXPORTER_OTLP_{signal.upper()}_HEADERS" if signal_headers else "OTEL_EXPORTER_OTLP_HEADERS" - operator_headers: Final = "Authorization=Bearer%20operator-key,x-operator-key=operator-only" - monkeypatch.setenv(variable, operator_headers) - for account, configured in (("tenant-a", "a-key"), ("tenant-b", "b-key"), ("operator", None)): - endpoint: Final = f"https://{account}.example.test" - captured: Final = requests_mock.post(f"{endpoint}/v1/{signal}", content=b"") - - _export_header_probe(api, signal, endpoint, f"x-tenant-key={configured}" if configured else None) - - assert captured.request_history, f"{account} never exported {signal}" - for request in captured.request_history: - credentials: Final = { - name.lower(): value - for name, value in request.headers.items() - if name.lower() in {"authorization", "x-operator-key", "x-tenant-key"} - } - assert credentials == ( - {"x-tenant-key": configured} - if configured - else {"authorization": "Bearer operator-key", "x-operator-key": "operator-only"} - ) - assert os.environ[variable] == operator_headers - - -@pytest.mark.parametrize("protocol", ("otlp_http", "http/json")) -def test_headerless_tenant_destination_does_not_receive_operator_credentials(monkeypatch, requests_mock, protocol): - monkeypatch.setenv("OTEL_EXPORTER_OTLP_HEADERS", "Authorization=Bearer%20operator-key") - captured: Final = requests_mock.post("https://tenant.example.test/v1/traces", content=b"") - processor: Final = otel_providers._destination_processor( - OtelDestination(endpoint="https://tenant.example.test", headers={}, protocol=protocol) - ) - assert processor is not None - provider: Final = TracerProvider() - provider.add_span_processor(processor) - try: - emit(provider, "headerless-tenant") - assert provider.force_flush() - assert captured.request_history - assert all("Authorization" not in request.headers for request in captured.request_history) - finally: - provider.shutdown() - - -@pytest.mark.parametrize("api", ("v2", "v2-json")) -def test_isolated_headers_keep_the_exporters_content_type_and_compression(monkeypatch, requests_mock, api): - monkeypatch.setenv("OTEL_EXPORTER_OTLP_HEADERS", "Content-Type=operator-only,Content-Encoding=operator-only") - monkeypatch.setenv("OTEL_EXPORTER_OTLP_TRACES_COMPRESSION", "gzip") - captured: Final = requests_mock.post("https://tenant.example.test/v1/traces", content=b"") - - _export_header_probe(api, "traces", "https://tenant.example.test", "x-tenant-key=tenant-key") - - assert len(captured.request_history) == 1 - request: Final = captured.last_request - assert request.headers["Content-Type"] == ("application/json" if api == "v2-json" else "application/x-protobuf") - assert request.headers["Content-Encoding"] == "gzip" - monkeypatch.delenv("OTEL_EXPORTER_OTLP_TRACES_COMPRESSION") - - _export_header_probe(api, "traces", "https://tenant.example.test", "x-tenant-key=tenant-key") - - assert gzip.decompress(request.body) == (captured.last_request.body or b"") - - -@pytest.mark.parametrize("api", ("v2", "v2-json")) -@pytest.mark.parametrize("header", ("Authorization", "AUTHORIZATION", "authorization")) -def test_tenant_authorization_replaces_operator_authorization(monkeypatch, requests_mock, api, header): - monkeypatch.setenv("OTEL_EXPORTER_OTLP_HEADERS", "Authorization=Bearer%20operator-key") - captured: Final = requests_mock.post("https://tenant.example.test/v1/traces", content=b"") - - _export_header_probe(api, "traces", "https://tenant.example.test", f"{header}=Bearer tenant-key") - - assert len(captured.request_history) == 1 - assert captured.last_request.headers["Authorization"] == "Bearer tenant-key" - assert "Bearer operator-key" not in captured.last_request.headers.values() - - -@pytest.mark.parametrize("protocol", ("otlp_http", "http/json")) -def test_session_defaults_cannot_restore_excluded_operator_headers(monkeypatch, requests_mock, protocol): - from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter - - from litellm.integrations.otel.plumbing.otlp_json import OTLPJsonSpanExporter - from litellm.integrations.otel.plumbing.otlp_tls import resolve_otlp_http_tls - - monkeypatch.setenv("OTEL_EXPORTER_OTLP_HEADERS", "Authorization=Bearer%20operator-key,x-operator-key=operator-only") - captured: Final = requests_mock.post("https://tenant.example.test/v1/traces", content=b"") - headers: Final = {"x-tenant-key": "tenant-key"} - tls: Final = resolve_otlp_http_tls("TRACES", headers) - assert tls.session is not None - tls.session.headers.update({"AUTHORIZATION": "Bearer operator-key", "X-Operator-Key": "operator-only"}) - exporter_type: Final = OTLPJsonSpanExporter if protocol == "http/json" else OTLPSpanExporter - exporter: Final = exporter_type( - endpoint="https://tenant.example.test/v1/traces", headers=headers, session=tls.session - ) - try: - assert exporter.export(()).name == "SUCCESS" - assert len(captured.request_history) == 1 - assert captured.last_request.headers["x-tenant-key"] == "tenant-key" - assert "Authorization" not in captured.last_request.headers - assert "x-operator-key" not in captured.last_request.headers - finally: - exporter.shutdown() - - -@pytest.mark.parametrize( - ("api", "signal"), - (("v1", "traces"), ("v1", "logs"), ("v1", "metrics"), - ("v2", "traces"), ("v2", "logs"), ("v2", "metrics"), ("v2-json", "traces")), -) -@pytest.mark.parametrize("requests_ca", ("REQUESTS_CA_BUNDLE", "CURL_CA_BUNDLE")) -@pytest.mark.parametrize("collector_ca", ("signal", "global", "SSL_CERT_FILE", "ssl_verify")) -@pytest.mark.parametrize("tenant", (False, True)) -def test_explicit_collector_ca_precedes_requests_environment( - monkeypatch, requests_mock, tmp_path, api: str, signal: str, requests_ca: str, collector_ca: str, tenant: bool -) -> None: - certificate_path: Final = tmp_path / "collector-ca.pem" - certificate_path.write_text("TEST_ONLY_CA") - certificate: Final = str(certificate_path) - for variable in ("REQUESTS_CA_BUNDLE", "CURL_CA_BUNDLE", "SSL_CERT_FILE", "SSL_VERIFY", - "OTEL_EXPORTER_OTLP_CERTIFICATE", f"OTEL_EXPORTER_OTLP_{signal.upper()}_CERTIFICATE"): - monkeypatch.delenv(variable, raising=False) - monkeypatch.setattr(litellm, "ssl_verify", True) - monkeypatch.setenv(requests_ca, "/corporate/global-ca.pem") - if collector_ca == "ssl_verify": - monkeypatch.setattr(litellm, "ssl_verify", certificate) - else: - variable: Final = ( - f"OTEL_EXPORTER_OTLP_{signal.upper()}_CERTIFICATE" if collector_ca == "signal" - else "OTEL_EXPORTER_OTLP_CERTIFICATE" if collector_ca == "global" else collector_ca - ) - monkeypatch.setenv(variable, certificate) - if collector_ca == "signal": - monkeypatch.setenv("OTEL_EXPORTER_OTLP_CERTIFICATE", "/collector/generic-ca.pem") - monkeypatch.setenv(f"OTEL_EXPORTER_OTLP_{signal.upper()}_CLIENT_CERTIFICATE", "/collector/client.pem") - monkeypatch.setenv(f"OTEL_EXPORTER_OTLP_{signal.upper()}_CLIENT_KEY", "/collector/client.key") - monkeypatch.setenv("HTTPS_PROXY", "http://proxy.example.invalid:8080") - monkeypatch.setenv("OTEL_EXPORTER_OTLP_HEADERS", "Authorization=Bearer%20TEST_ONLY_OPERATOR") - captured: Final = requests_mock.post(f"https://collector.example.invalid/v1/{signal}", content=b"") - - _export_header_probe(api, signal, "https://collector.example.invalid", "x-tenant-key=TEST_ONLY" if tenant else None) - - assert captured.request_history - for request in captured.request_history: - assert request.verify == certificate - assert request.cert == ("/collector/client.pem", "/collector/client.key") - assert request.proxies["https"] == "http://proxy.example.invalid:8080" - assert request.headers.get("Authorization") == (None if tenant else "Bearer TEST_ONLY_OPERATOR") - assert request.headers.get("x-tenant-key") == ("TEST_ONLY" if tenant else None) - - -@pytest.mark.parametrize("api", ("v1", "v2")) -@pytest.mark.parametrize("signal", ("traces", "logs", "metrics")) -@pytest.mark.parametrize("global_provider", (False, True)) -@pytest.mark.parametrize("tenant", (False, True)) -def test_collector_tls_preserves_operator_credential_provider_without_tenant_leakage( - monkeypatch, requests_mock, api: str, signal: str, global_provider: bool, tenant: bool -) -> None: - from types import SimpleNamespace - - import requests - from opentelemetry.exporter.otlp.proto.http import _common - - session: Final = requests.Session() - session.auth = requests.auth.HTTPBasicAuth("TEST_ONLY_USER", "TEST_ONLY_PASSWORD") - session.headers["x-session-marker"] = "TEST_ONLY_SESSION" - provider_variable: Final = ( - "OTEL_PYTHON_EXPORTER_OTLP_HTTP_CREDENTIAL_PROVIDER" if global_provider - else f"OTEL_PYTHON_EXPORTER_OTLP_HTTP_{signal.upper()}_CREDENTIAL_PROVIDER" - ) - monkeypatch.setenv(provider_variable, "TEST_ONLY_PROVIDER") - monkeypatch.setenv(f"OTEL_EXPORTER_OTLP_{signal.upper()}_CERTIFICATE", "/collector/collector-ca.pem") - monkeypatch.setenv("REQUESTS_CA_BUNDLE", "/corporate/global-ca.pem") - monkeypatch.setattr(_common, "entry_points", lambda **kwargs: (SimpleNamespace(load=lambda: lambda: session),)) - captured: Final = requests_mock.post(f"https://collector.example.invalid/v1/{signal}", content=b"") - - _export_header_probe(api, signal, "https://collector.example.invalid", "x-tenant-key=TEST_ONLY" if tenant else None) - - assert captured.request_history - for request in captured.request_history: - assert request.verify == "/collector/collector-ca.pem" - assert request.headers.get("x-session-marker") == (None if tenant else "TEST_ONLY_SESSION") - assert request.headers.get("Authorization") == ( - None if tenant else "Basic " + b64encode(b"TEST_ONLY_USER:TEST_ONLY_PASSWORD").decode() - ) - - -@pytest.mark.parametrize("api", ("v1", "v2", "v2-json")) -@pytest.mark.parametrize("requests_ca", ("REQUESTS_CA_BUNDLE", "CURL_CA_BUNDLE")) -def test_requests_environment_ca_remains_fallback(monkeypatch, requests_mock, api: str, requests_ca: str) -> None: - for variable in ("REQUESTS_CA_BUNDLE", "CURL_CA_BUNDLE", "SSL_CERT_FILE", "SSL_VERIFY", - "OTEL_EXPORTER_OTLP_CERTIFICATE", "OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE"): - monkeypatch.delenv(variable, raising=False) - monkeypatch.setattr(litellm, "ssl_verify", True) - monkeypatch.setenv(requests_ca, "/corporate/global-ca.pem") - captured: Final = requests_mock.post("https://collector.example.invalid/v1/traces", content=b"") - - _export_header_probe(api, "traces", "https://collector.example.invalid", None) - - assert captured.request_history - assert captured.last_request.verify == "/corporate/global-ca.pem" diff --git a/tests/unit/integrations/otel/test_otel_v2_mount.py b/tests/unit/integrations/otel/test_otel_v2_mount.py index 22d7e4cd317..3a27c3ec4e4 100644 --- a/tests/unit/integrations/otel/test_otel_v2_mount.py +++ b/tests/unit/integrations/otel/test_otel_v2_mount.py @@ -382,7 +382,7 @@ def test_every_traced_response_names_its_own_server_span(monkeypatch): assert responses["/stream"].text == "data: first\n\ndata: [DONE]\n\n" for path, response in responses.items(): context = server_spans[path].context - assert response.headers.get_list("traceresponse") == [f"00-{context.trace_id:032x}-{context.span_id:016x}-{context.trace_flags:02x}"] + assert response.headers.get_list("traceresponse") == [f"00-{context.trace_id:032x}-{context.span_id:016x}-01"] @pytest.mark.parametrize("path", ["/relayed", "/relayed/health"]) @@ -472,20 +472,13 @@ def test_gate_off_responses_carry_no_traceresponse(monkeypatch): assert "traceresponse" not in TestClient(app).get("/ping").headers -def test_started_app_keeps_serving_without_registering_a_response_propagator(monkeypatch): +def test_failed_instrumentation_registers_no_response_propagator(monkeypatch): monkeypatch.setenv("LITELLM_OTEL_V2", "1") app = fastapi.FastAPI() - @app.get("/running") - def running(): - return {"ok": True} - - with TestClient(app) as client: + with TestClient(app): instrument_fastapi_app(app) - response = client.get("/running") - assert response.json() == {"ok": True} - assert "traceresponse" not in response.headers assert get_global_response_propagator() is None @@ -552,4 +545,4 @@ def test_the_unhandled_error_helper_names_the_span_it_is_given_in_any_context(mo headers = contextvars.Context().run(trace_response_headers, span) - assert headers["traceresponse"] == f"00-{context.trace_id:032x}-{context.span_id:016x}-{context.trace_flags:02x}" + assert headers["traceresponse"] == f"00-{context.trace_id:032x}-{context.span_id:016x}-01" diff --git a/tests/unit/integrations/otel/test_otel_v2_presets.py b/tests/unit/integrations/otel/test_otel_v2_presets.py index 60207a2b5bc..64366626784 100644 --- a/tests/unit/integrations/otel/test_otel_v2_presets.py +++ b/tests/unit/integrations/otel/test_otel_v2_presets.py @@ -2,8 +2,6 @@ event loop: the preset does no network I/O, and a custom exporter mints the JWT lazily on its first export (in the BatchSpanProcessor worker thread).""" -import json - import httpx import pytest @@ -77,53 +75,50 @@ def test_dynamic_cred_presets_tag_exporter_with_matching_owner(monkeypatch): ) -def test_agentops_exporter_mints_jwt_lazily(monkeypatch, requests_mock, respx_mock): - from opentelemetry.sdk.trace import TracerProvider - from opentelemetry.sdk.trace.export import SpanExportResult - - monkeypatch.setenv("OTEL_EXPORTER_OTLP_HEADERS", "Authorization=Bearer%20operator,x-operator-key=operator-secret") - auth = respx_mock.post(agentops_mod._AGENTOPS_AUTH_ENDPOINT).respond(200, json={"token": "jwt-xyz"}) - collector = requests_mock.post(_AGENTOPS_ENDPOINT, status_code=200) - exporter = _build_agentops_exporter( - ExporterSpec(kind=_AGENTOPS_EXPORTER_KIND, endpoint=_AGENTOPS_ENDPOINT, options={"api_key": "ak"}) +def test_agentops_exporter_mints_jwt_lazily(monkeypatch): + pytest.importorskip("opentelemetry.exporter.otlp.proto.http.trace_exporter") + monkeypatch.setattr( + agentops_mod, "_fetch_agentops_jwt", lambda _k: {"token": "jwt-xyz"} ) - provider = TracerProvider() - span = provider.get_tracer(__name__).start_span("authenticated") - span.end() - try: - assert auth.call_count == 0 - assert exporter.export([span]) is SpanExportResult.SUCCESS - assert exporter.export([]) is SpanExportResult.SUCCESS - assert auth.call_count == 1 - assert json.loads(auth.calls[0].request.content) == {"api_key": "ak"} - assert [request.headers["Authorization"] for request in collector.request_history] == [ - "Bearer jwt-xyz", - "Bearer jwt-xyz", - ] - assert all("x-operator-key" not in request.headers for request in collector.request_history) - finally: - exporter.shutdown() - provider.shutdown() - - -def test_agentops_exporter_tolerates_fetch_failure(monkeypatch, requests_mock, respx_mock): - from opentelemetry.sdk.trace.export import SpanExportResult - - monkeypatch.setenv("OTEL_EXPORTER_OTLP_TRACES_HEADERS", "Authorization=Bearer%20operator,x-operator-key=operator-secret") - auth = respx_mock.post(agentops_mod._AGENTOPS_AUTH_ENDPOINT).respond(503, text="auth down") - collector = requests_mock.post(_AGENTOPS_ENDPOINT, status_code=200) - exporter = _build_agentops_exporter( - ExporterSpec(kind=_AGENTOPS_EXPORTER_KIND, endpoint=_AGENTOPS_ENDPOINT, options={"api_key": "ak"}) + spec = ExporterSpec( + kind=_AGENTOPS_EXPORTER_KIND, + endpoint=_AGENTOPS_ENDPOINT, + options={"api_key": "ak"}, ) - try: - assert exporter.export([]) is SpanExportResult.SUCCESS - assert exporter.export([]) is SpanExportResult.SUCCESS - assert auth.call_count == 1 - assert collector.call_count == 2 - assert all("Authorization" not in request.headers for request in collector.request_history) - assert all("x-operator-key" not in request.headers for request in collector.request_history) - finally: - exporter.shutdown() + exporter = _build_agentops_exporter(spec) + + # No auth header until the first export triggers the (off-loop) fetch. + assert "Authorization" not in exporter._session.headers + exporter._ensure_authenticated() + assert exporter._session.headers["Authorization"] == "Bearer jwt-xyz" + + # Cached: a second resolution does not re-fetch. + calls = [] + monkeypatch.setattr( + agentops_mod, + "_fetch_agentops_jwt", + lambda k: calls.append(k) or {"token": "again"}, + ) + exporter._ensure_authenticated() + assert calls == [] + + +def test_agentops_exporter_tolerates_fetch_failure(monkeypatch): + pytest.importorskip("opentelemetry.exporter.otlp.proto.http.trace_exporter") + + def _raise(_k): + raise RuntimeError("auth down") + + monkeypatch.setattr(agentops_mod, "_fetch_agentops_jwt", _raise) + exporter = _build_agentops_exporter( + ExporterSpec( + kind=_AGENTOPS_EXPORTER_KIND, + endpoint=_AGENTOPS_ENDPOINT, + options={"api_key": "ak"}, + ) + ) + exporter._ensure_authenticated() # must not raise + assert "Authorization" not in exporter._session.headers def test_fetch_jwt_uses_owned_client_not_shared_pool(monkeypatch): diff --git a/tests/unit/integrations/test_langfuse_otel.py b/tests/unit/integrations/test_langfuse_otel.py index cc31e2788da..9f536ab0dac 100644 --- a/tests/unit/integrations/test_langfuse_otel.py +++ b/tests/unit/integrations/test_langfuse_otel.py @@ -591,7 +591,7 @@ class TestLangfuseOtelKeyDynamicConfig: assert logger.construct_dynamic_otel_config(self._dynamic_params(langfuse_secret_key=None)) is None assert logger.construct_dynamic_otel_config(self._dynamic_params(langfuse_public_key=None)) is None - def test_key_dynamic_params_create_otlp_exporter_without_global_env(self, requests_mock): + def test_key_dynamic_params_create_otlp_exporter_without_global_env(self): """Without global LANGFUSE_* env vars, a request carrying key-scoped Langfuse credentials must get a tracer exporting via OTLP HTTP to that key's host.""" from opentelemetry.exporter.otlp.proto.http.trace_exporter import ( @@ -617,17 +617,15 @@ class TestLangfuseOtelKeyDynamicConfig: exporter = span_processors[0].span_exporter assert isinstance(exporter, OTLPSpanExporter) + assert exporter._endpoint == "https://langfuse.example.com/api/public/otel/v1/traces" import base64 expected_auth = base64.b64encode(b"key_public:key_secret").decode() - collector = requests_mock.post("https://langfuse.example.com/api/public/otel/v1/traces", status_code=200) - with tracer.start_as_current_span("dynamic-credentials"): - pass - assert provider.force_flush() - assert collector.call_count == 1 - assert collector.last_request.headers["Authorization"] == f"Basic {expected_auth}" - assert collector.last_request.headers["x-langfuse-ingestion-version"] == "4" + assert exporter._headers == { + "Authorization": f"Basic {expected_auth}", + "x-langfuse-ingestion-version": "4", + } def test_key_dynamic_params_reuse_cached_provider(self): with self._clean_env(): @@ -646,7 +644,7 @@ class TestLangfuseOtelKeyDynamicConfig: assert tracer is logger.tracer assert logger._tracer_provider_cache == {} - def test_key_credentials_never_passed_to_debug_logger(self, requests_mock): + def test_key_credentials_never_passed_to_debug_logger(self): """The span-processor debug logs must receive a redacted header value, so the key-scoped Langfuse secret never enters a log record regardless of downstream handler configuration, while the exporter still gets the real header.""" @@ -680,12 +678,10 @@ class TestLangfuseOtelKeyDynamicConfig: provider = next(iter(logger._tracer_provider_cache.values())).provider exporter = provider._active_span_processor._span_processors[0].span_exporter assert isinstance(exporter, OTLPSpanExporter) - from opentelemetry.sdk.trace.export import SpanExportResult - - collector = requests_mock.post("https://langfuse.example.com/api/public/otel/v1/traces", status_code=200) - assert exporter.export([]) is SpanExportResult.SUCCESS - assert collector.last_request.headers["Authorization"] == f"Basic {secret}" - assert collector.last_request.headers["x-langfuse-ingestion-version"] == "4" + assert exporter._headers == { + "Authorization": f"Basic {secret}", + "x-langfuse-ingestion-version": "4", + } class TestLangfuseOtelResponsesAPI: diff --git a/tests/unit/integrations/test_opentelemetry.py b/tests/unit/integrations/test_opentelemetry.py index 8b14fbddaf8..d14703e48a1 100644 --- a/tests/unit/integrations/test_opentelemetry.py +++ b/tests/unit/integrations/test_opentelemetry.py @@ -12,23 +12,21 @@ import weakref from datetime import datetime, timedelta, timezone from pathlib import Path from types import MappingProxyType -from typing import ClassVar, Final +from typing import Final from unittest.mock import MagicMock, patch import pytest # Adds the grandparent directory to sys.path to allow importing project modules from opentelemetry import trace -from opentelemetry._logs import LogRecord from opentelemetry._logs.severity import SeverityNumber -from opentelemetry.sdk._logs import ReadableLogRecord +from opentelemetry.sdk._logs import LogData, LogRecord from opentelemetry.sdk._logs import LoggerProvider as OTLoggerProvider -from opentelemetry.sdk._logs.export import InMemoryLogExporter, LogRecordExportResult, SimpleLogRecordProcessor +from opentelemetry.sdk._logs.export import InMemoryLogExporter, LogExportResult, SimpleLogRecordProcessor from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import InMemoryMetricReader, MetricsData -from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import ReadableSpan, SpanLimits, TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor, SpanExportResult +from opentelemetry.sdk.trace.export import SimpleSpanProcessor, SpanExportResult from opentelemetry.sdk.util.instrumentation import InstrumentationScope from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter from parameterized import parameterized @@ -2323,13 +2321,9 @@ def test_otlp_http_log_export_trusts_ssl_cert_file(monkeypatch: pytest.MonkeyPat severity_number=SeverityNumber.INFO, body="tls-export-test", ) - log_data: Final = ReadableLogRecord( - log_record=record, - resource=Resource.create({}), - instrumentation_scope=InstrumentationScope("tls-export-test"), - ) + log_data: Final = LogData(log_record=record, instrumentation_scope=InstrumentationScope("tls-export-test")) result: Final = exporter.export([log_data]) - assert result is LogRecordExportResult.SUCCESS, f"log export failed: {result}" + assert result is LogExportResult.SUCCESS, f"log export failed: {result}" assert tls_sink.received.get(timeout=5) == "/v1/logs" finally: exporter.shutdown() @@ -6645,7 +6639,7 @@ class TestOpenTelemetryProviderlessCallAttributes(unittest.TestCase): time.sleep(self.POLL_INTERVAL) return None - def _emitted_log_records(self, semconv_opt_in: str) -> tuple[ReadableLogRecord, ...]: + def _emitted_log_records(self, semconv_opt_in: str) -> tuple[LogData, ...]: log_exporter = InMemoryLogExporter() logger_provider = OTLoggerProvider() logger_provider.add_log_record_processor(SimpleLogRecordProcessor(log_exporter)) @@ -6719,19 +6713,7 @@ class TestDynamicTracerProviderCache(unittest.TestCase): must be bounded and must shut down whatever it drops (LIT-5437: threads accumulated until pods were OOMKilled).""" - BSP_THREAD_NAME: ClassVar[str] - - @classmethod - def setUpClass(cls) -> None: - before: Final = frozenset(threading.enumerate()) - processor: Final = BatchSpanProcessor(InMemorySpanExporter()) - try: - workers: Final = tuple(thread for thread in threading.enumerate() if thread not in before) - assert len(workers) == 1 - assert workers[0].is_alive() - cls.BSP_THREAD_NAME = workers[0].name - finally: - processor.shutdown() + BSP_THREAD_NAME = "OtelBatchSpanProcessor" def _logger(self, cap=3, exporter="console"): logger = OpenTelemetry( @@ -6769,6 +6751,8 @@ class TestDynamicTracerProviderCache(unittest.TestCase): logger._get_tracer_with_dynamic_headers({"authorization": f"Basic tenant-{i}"}) self.assertEqual(len(logger._tracer_provider_cache), 3) + # Guards the thread-name constant: a rename upstream would make this read 0 and the + # bound assertion below would pass while measuring nothing. self.assertGreaterEqual(len(self._live_exporter_threads()), 1) self.assertLessEqual(self._wait_for_exporter_threads(before + 3) - before, 3) @@ -6939,8 +6923,8 @@ class TestOpenTelemetryDatabaseSemconvAttributes(unittest.TestCase): database's own metrics. """ - DSN = "postgresql://TEST_ONLY_USER:TEST_ONLY_PASSWORD@primary-db.example.invalid:6432/TEST_ONLY_DATABASE?schema=reporting" - REPLICA_DSN = "postgresql://TEST_ONLY_REPLICA_USER:TEST_ONLY_REPLICA_PASSWORD@replica-db.example.invalid/TEST_ONLY_DATABASE" + DSN = "postgresql://llmproxy:dbpassword9090@litellm-prod.abc123.us-east-1.rds.amazonaws.com:6432/litellm?schema=reporting" + REPLICA_DSN = "postgresql://reader:r3ad0nly@litellm-prod-ro.abc123.us-east-1.rds.amazonaws.com/litellm" def _service_span(self, service, call_type, dsn, error=None, replica_dsn=None): exporter = InMemorySpanExporter() @@ -6981,10 +6965,10 @@ class TestOpenTelemetryDatabaseSemconvAttributes(unittest.TestCase): self.assertEqual(span.attributes["db.operation.name"], "get_data") self.assertEqual( span.attributes["server.address"], - "primary-db.example.invalid", + "litellm-prod.abc123.us-east-1.rds.amazonaws.com", ) self.assertEqual(span.attributes["server.port"], 6432) - self.assertEqual(span.attributes["db.namespace"], "TEST_ONLY_DATABASE|reporting") + self.assertEqual(span.attributes["db.namespace"], "litellm|reporting") def test_datastore_span_is_a_client_span_carrying_the_legacy_db_system(self): """Datadog types a span as a database call from CLIENT kind plus @@ -7010,16 +6994,16 @@ class TestOpenTelemetryDatabaseSemconvAttributes(unittest.TestCase): self.assertEqual(span.kind, trace.SpanKind.CLIENT) self.assertEqual( span.attributes["server.address"], - "primary-db.example.invalid", + "litellm-prod.abc123.us-east-1.rds.amazonaws.com", ) self.assertEqual(span.attributes["error"], "connection refused") def test_no_credential_from_the_dsn_lands_on_the_span(self): span = self._service_span(ServiceTypes.DB, "get_data", self.DSN) exported = " ".join(str(value) for value in span.attributes.values()) - self.assertIn("primary-db.example.invalid", exported) - self.assertNotIn("TEST_ONLY_PASSWORD", exported) - self.assertNotIn("TEST_ONLY_USER", exported) + self.assertIn("litellm-prod.abc123.us-east-1.rds.amazonaws.com", exported) + self.assertNotIn("dbpassword9090", exported) + self.assertNotIn("llmproxy", exported) def test_redis_span_does_not_borrow_the_postgres_endpoint(self): span = self._service_span(ServiceTypes.REDIS, "async_set_cache", self.DSN) diff --git a/tests/unit/litellm_core_utils/test_litellm_logging.py b/tests/unit/litellm_core_utils/test_litellm_logging.py index 5b67250ad00..bbfae2229ce 100644 --- a/tests/unit/litellm_core_utils/test_litellm_logging.py +++ b/tests/unit/litellm_core_utils/test_litellm_logging.py @@ -2043,7 +2043,7 @@ async def test_logfire_logger_accepts_env_vars_for_base_url(monkeypatch): ], ) def test_langtrace_callback_exports_to_api_trace_with_x_api_key( - monkeypatch: pytest.MonkeyPatch, api_host: str | None, expected_endpoint: str, requests_mock + monkeypatch: pytest.MonkeyPatch, api_host: str | None, expected_endpoint: str ) -> None: """The exporter must post to Langtrace's complete /api/trace path with the key in x-api-key, without leaking it into the process-wide OTEL_EXPORTER_OTLP_TRACES_HEADERS.""" @@ -2069,12 +2069,8 @@ def test_langtrace_callback_exports_to_api_trace_with_x_api_key( assert type(logger) is OpenTelemetry and logger.callback_name == "langtrace" exporter: Final = logger._get_span_processor().span_exporter assert isinstance(exporter, OTLPSpanExporter) - from opentelemetry.sdk.trace.export import SpanExportResult - - collector: Final = requests_mock.post(expected_endpoint, status_code=200) - assert exporter.export([]) is SpanExportResult.SUCCESS - assert collector.call_count == 1 - assert collector.last_request.headers["x-api-key"] == api_key + assert exporter._endpoint == expected_endpoint + assert exporter._headers == {"x-api-key": api_key} assert "OTEL_EXPORTER_OTLP_TRACES_HEADERS" not in os.environ finally: logging_module._in_memory_loggers.clear() @@ -8023,14 +8019,14 @@ def test_payload_without_guardrail_cost_is_unchanged(logging_obj): assert payload["cost_breakdown"] is None -_AWS_SECRET = "TEST_ONLY_AWS_SECRET_NOT_A_CREDENTIAL" -_GEMINI_KEY = "TEST_ONLY_GEMINI_KEY_NOT_A_CREDENTIAL" +_AWS_SECRET = "wJalrXUtnFEMIK7MDENGbPxRfiCYEXAMPLEKEY" +_GEMINI_KEY = "AIzaSyC0000000000000000000000000000000" def test_empty_api_base_does_not_dump_call_state(logging_obj): """Direct (non-HTTP) providers pass api_base='', which used to echo model_call_details.""" logging_obj.model_call_details["litellm_params"] = { - "api_key": "sk-TEST_ONLY_API_KEY_NOT_A_CREDENTIAL", + "api_key": "sk-proj-hunter2hunter2hunter2hunter2", "aws_secret_access_key": _AWS_SECRET, } @@ -8043,7 +8039,7 @@ def test_empty_api_base_does_not_dump_call_state(logging_obj): assert "litellm_call_id" not in curl_command assert _AWS_SECRET not in curl_command - assert "sk-TEST_ONLY_API_KEY_NOT_A_CREDENTIAL" not in curl_command + assert "hunter2" not in curl_command def test_pre_call_redacts_and_masks_raw_request(logging_obj): diff --git a/tests/unit/proxy/enterprise_billing/test_billing_metrics.py b/tests/unit/proxy/enterprise_billing/test_billing_metrics.py index 06f4e09697c..0446cfeeab0 100644 --- a/tests/unit/proxy/enterprise_billing/test_billing_metrics.py +++ b/tests/unit/proxy/enterprise_billing/test_billing_metrics.py @@ -12,7 +12,7 @@ import os import socket import stat from pathlib import Path -from typing import Dict, Final, List, Optional +from typing import Dict, List, Optional import pytest from opentelemetry.sdk.metrics import MeterProvider @@ -350,30 +350,19 @@ def _fake_exporter_class(captured: Dict[str, object]) -> type: return _FakeExporter -@pytest.mark.parametrize("requests_ca", ("REQUESTS_CA_BUNDLE", "CURL_CA_BUNDLE")) -def test_meter_provider_preserves_explicit_tls_settings(tmp_path, monkeypatch, requests_mock, requests_ca: str) -> None: - monkeypatch.delenv("REQUESTS_CA_BUNDLE", raising=False) - monkeypatch.delenv("CURL_CA_BUNDLE", raising=False) - monkeypatch.setenv(requests_ca, "/corporate/global-ca.pem") - monkeypatch.setenv("OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE", "/operator/collector-ca.pem") - monkeypatch.setenv("OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE", "/operator/client.pem") - monkeypatch.setenv("OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY", "/operator/client.key") - monkeypatch.setenv("HTTPS_PROXY", "http://proxy.example.invalid:8080") - captured: Final = requests_mock.post("https://collector.example:4317/v1/metrics", content=b"") - config: Final = _config(tmp_path) - provider: Final = bm.build_mtls_meter_provider(config) - try: - bm.BillingMetricsRecorder(provider).record( - category=BillableCategory.LLM, route="/chat/completions", status_code=200, model_id=None - ) - assert provider.force_flush() - assert captured.request_history - for request in captured.request_history: - assert request.verify == config.ca_cert_path - assert request.cert == (config.client_cert_path, config.client_key_path) - assert request.proxies["https"] == "http://proxy.example.invalid:8080" - finally: - provider.shutdown() +def test_meter_provider_wires_client_cert_into_http_exporter(tmp_path, monkeypatch): + """Client cert+key authenticate us at the collector's mTLS front end; CA override rides certificate_file.""" + captured: Dict[str, object] = {} + + monkeypatch.setattr(bm, "OTLPMetricExporter", _fake_exporter_class(captured)) + config = _config(tmp_path) + provider = bm.build_mtls_meter_provider(config) + provider.shutdown() + + assert captured["endpoint"] == "https://collector.example:4317/v1/metrics" + assert captured["client_certificate_file"] == config.client_cert_path + assert captured["client_key_file"] == config.client_key_path + assert captured["certificate_file"] == config.ca_cert_path def test_load_config_without_ca_is_valid(monkeypatch, tmp_path): diff --git a/ui/litellm-dashboard/package-lock.json b/ui/litellm-dashboard/package-lock.json index c6f45fd19f5..d6b98d9be80 100644 --- a/ui/litellm-dashboard/package-lock.json +++ b/ui/litellm-dashboard/package-lock.json @@ -29,7 +29,7 @@ "jwt-decode": "4.0.0", "lucide-react": "0.513.0", "moment": "2.31.0", - "next": "16.4.1", + "next": "16.3.8", "next-themes": "^0.4.6", "nuqs": "^2.9.4", "openai": "6.49.0", @@ -71,8 +71,8 @@ "@types/react-copy-to-clipboard": "5.0.7", "@types/react-dom": "19.2.4", "@types/react-syntax-highlighter": "15.5.13", - "@vitest/coverage-v8": "4.1.12", - "@vitest/ui": "4.1.12", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", "eslint": "9.39.2", "eslint-config-next": "16.3.3", "eslint-config-prettier": "10.1.8", @@ -89,7 +89,7 @@ "typescript": "5.9.3", "typescript-eslint": "8.60.1", "vite": "7.3.7", - "vitest": "4.1.12" + "vitest": "4.1.11" }, "engines": { "node": ">=24.14.1", @@ -2155,9 +2155,9 @@ } }, "node_modules/@next/env": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.4.1.tgz", - "integrity": "sha512-5sUUrrSvs/2/2LnLoesybMGx12EtPB46fdZ6RhAw0XDGSPxU2LFyQBB7yuEiOFFJPKmBY6+q2jTr1/AMbnp0/g==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.8.tgz", + "integrity": "sha512-Al9zqHVV7TJv0eFuOU4U7Lvv74PTih4Ch63sk2xCIpSTkE3udFnaOcnzP2lQVymiL7yS9Cj2iClUXlR3EQ5sEw==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -2172,9 +2172,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.4.1.tgz", - "integrity": "sha512-U+hQ5Gj3fpzf4fveTIF3dpUFYhaSW8vHhdAnbiIomzXzg7boreYFgBqprepXFtW4sw+aTttiNmg0gew/iFNhbg==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.8.tgz", + "integrity": "sha512-2JPRMh2nmQG5CiL7cXGL9AGwnPWJQ//cTtAUCT+w511QHk79SYz3LGv/pc5X643B/WEO0rvu3Yww0hqwt3kgeA==", "cpu": [ "arm64" ], @@ -2188,9 +2188,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.4.1.tgz", - "integrity": "sha512-/hqC3V1XLUjhMASf/WridKdO9FXoEgN3HBWmtKbj00MPgnltutfraWJ3bYTpCRIgEIpbjJyOVLpYgAhh/BkdcQ==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.8.tgz", + "integrity": "sha512-GZtCCOBKJ4leVIT/Th0llWKhD1ca92lzbQiS5R5ON9QkoiFnilFsebDae1JU2a3HWoKMEmEZWGs1AGLavVM72Q==", "cpu": [ "x64" ], @@ -2204,12 +2204,15 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.4.1.tgz", - "integrity": "sha512-GHzncHbcaS9g95kcvTNtKQW3aCLoR+xc3Jm7qrkpk1R+D2BjMHIPyvQGRPKCdMX95BsH/421jBUK06q9tEwzjw==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.8.tgz", + "integrity": "sha512-O659ygeQYqneJ1fBKMpFxIFqYkYswu8IAS1OCKK/4f3ZgJJm1dRz4fVJZRi/kLLWjnBKnebOePA4WNv+sV1pVA==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2220,12 +2223,15 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.4.1.tgz", - "integrity": "sha512-1SuJyDxW/PR9dHJat/ZRgcYLhuSZ+1psP4DlKb2tKIuXbjygqpLrJWRSq+onZQBv8LcmU67EIrmb6c3XTaJLjw==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.8.tgz", + "integrity": "sha512-dSjKSyWpzxoO1d3DIZZcP4XJcNaKeLmxQMFOiYl5vuBRMmweIqnAhty8tAmRsvTss779cK1FtYnDMj40e4TQlg==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2236,12 +2242,15 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.4.1.tgz", - "integrity": "sha512-Istng1hMGp9TGO4lUqeKLd70MPN/OOkucVTP2Nmj+gLkwBgmPqUw8CJYBCC7nbl/B/23bBCcjCI5Hsh6hazC1A==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.8.tgz", + "integrity": "sha512-lbqOuz3RPRcv+o9msNsJw5x4+Y1ZwPTs6vmL6DCf7i0fZfvng/F59wyeDwqHIvV0mK//RBy/jJkZ+nCKsSMXjQ==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2252,12 +2261,15 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.4.1.tgz", - "integrity": "sha512-aSC9wMNiZRRFVpPkPWBzRwLQxIGm/CiQcXyFF1XNcgodb+uMxdk7aSSGGpDXCbnvZdf4itjHxpOd5h7i7zaiYw==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.8.tgz", + "integrity": "sha512-+316WswI8ScVgZeUd+1KGaXkHhaYQzCjvH/05TZSpJ8zBizb1a4G7DtO7F12jcBIqMOtsz9ji1t48fmKtzqsGA==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2268,9 +2280,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.4.1.tgz", - "integrity": "sha512-t/myvwFDlypMUjRvbBf8qLPyuaB0iXYYdBtwMt6DM6f+60wtGr4bSYholdPqNeV5VUXZhrpMwfza829kmhS3iA==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.8.tgz", + "integrity": "sha512-ji0gd4kMYUxO+1fJBIbiBVRCjzG/lloiyCccnlebvb1ZJ5qXCPZqYg4Jl1DrrixnWNMKylzgpmMWx0yNDYXlzw==", "cpu": [ "arm64" ], @@ -2284,9 +2296,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.4.1.tgz", - "integrity": "sha512-6ICqPo9awCkrtYNPhwkx1uU7rlFWrZRrIboDHS2LU+jv088UBYQtmJ25h6pCp27/+FZjF6BQ4Te4ErugyMeuBg==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.8.tgz", + "integrity": "sha512-WcTlaKt/TWkh5kUjdJcUmB1XgZ+1c6fz4Y9fDHL73YNSdGaUWjceeWrrlwF0nv19iABYWC4iAq1oX1w4Bn0vfg==", "cpu": [ "x64" ], @@ -4480,14 +4492,14 @@ ] }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.12.tgz", - "integrity": "sha512-6S7/iVknKglLAxMFxQGNppBI0iqJS2NgM6FBxavitj7KaDq/vywOWGf6dGu7F2L1Qy0G6S3uTp7aGshHrzK8JA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.11.tgz", + "integrity": "sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.12", + "@vitest/utils": "4.1.11", "ast-v8-to-istanbul": "^1.0.0", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -4501,8 +4513,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.12", - "vitest": "4.1.12" + "@vitest/browser": "4.1.11", + "vitest": "4.1.11" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -4511,16 +4523,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.12.tgz", - "integrity": "sha512-vFlTt+YEu9WBpNjdGayNmlMVzrWPi1+sv853/7t2FU7hpO5gAFBKHzQmkCq5Xjpr+evegF2994LQT3hF3mRlqQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.12", - "@vitest/utils": "4.1.12", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -4529,13 +4541,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.12.tgz", - "integrity": "sha512-fKShNpKZzupWZuY0z7egydvL0sqW4INEZBEExMBMAa6izlENEVHwoXgdaTCaBZ5fwlr4xf2o1wXC83n9RpO+NA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.12", + "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -4556,9 +4568,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.12.tgz", - "integrity": "sha512-XW9O8+Mxx4VpCtkYdMJLZQQwsVryP9wk7cPKDxtXFfwSak6yRh5+2fep/Y0XF/ElBvJHq8bcYdi6aeuai0opZA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "license": "MIT", "dependencies": { @@ -4569,13 +4581,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.12.tgz", - "integrity": "sha512-NKlVKCr5QnCe8jijdMnmkpHHU+WTqiLFR6G0pHji9uRjXK9xJl1k1nTw6F3pOOj2PDcsDt2GKQMI7wIsaT491w==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.12", + "@vitest/utils": "4.1.11", "pathe": "^2.0.3" }, "funding": { @@ -4583,14 +4595,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.12.tgz", - "integrity": "sha512-ko1y1Qgb/slzQ9mr+b89Cuqud8WG1TKtsPD8r7ZUBdvVOGt85Af2HwCqHujrhwJq95hs/9RM7fcAPg57o1vlzw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.12", - "@vitest/utils": "4.1.12", + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -4599,9 +4611,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.12.tgz", - "integrity": "sha512-RGq9x4JNGNTZHEJxvN0oSiJjeSCExm0n2gZKKJZHY9cX5AVA6M7VJILBT3ZJ/tJ/0J9uWT7fETV9/btoi//vSg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, "license": "MIT", "funding": { @@ -4609,13 +4621,13 @@ } }, "node_modules/@vitest/ui": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.12.tgz", - "integrity": "sha512-eYUTHtsp52Fu6YGUw9T9173bba7YUr9j/Xj9CdhUaOb8AppLKcknHUaiZQeqJ8MiJ8C2VyKiZuUGSKdvPIKQKA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.11.tgz", + "integrity": "sha512-r/rwyKoev21mWdRGSEkZOqkQ2BYy68mwjihg9M90nNRbf4NGrgzZ4cj6JNCEwlOGJkbKeMgsjlykvwKUbRr7gw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.12", + "@vitest/utils": "4.1.11", "fflate": "^0.8.2", "flatted": "^3.4.2", "pathe": "^2.0.3", @@ -4627,17 +4639,17 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "4.1.12" + "vitest": "4.1.11" } }, "node_modules/@vitest/utils": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.12.tgz", - "integrity": "sha512-Q3awXrwOfEV+qc3WmO5ONyE0yO7Q1gQ9/oyHxGLZ/pl7N3NO/ZztqQwKLuwMkV+CA1JYSIeO3gkHnRDIo4AVaA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.12", + "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -9737,12 +9749,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "16.4.1", - "resolved": "https://registry.npmjs.org/next/-/next-16.4.1.tgz", - "integrity": "sha512-uc657V62E34fOI6W5NtG9IzwsPcms0Z60grz2MJGtglgD+pTFnNt4Yw2PzaU622tfX4vhrDgLZzicE2YlhI20Q==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/next/-/next-16.3.8.tgz", + "integrity": "sha512-U7QEZaTini6wKrb8A8hqLLqYQyCetegKjCpJOyxk642vWoMoU1x5PyZCJFvgYgiptA8xc5j/9xYlZFO7w9Sjmw==", "license": "MIT", "dependencies": { - "@next/env": "16.4.1", + "@next/env": "16.3.8", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -9756,15 +9768,15 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.4.1", - "@next/swc-darwin-x64": "16.4.1", - "@next/swc-linux-arm64-gnu": "16.4.1", - "@next/swc-linux-arm64-musl": "16.4.1", - "@next/swc-linux-x64-gnu": "16.4.1", - "@next/swc-linux-x64-musl": "16.4.1", - "@next/swc-win32-arm64-msvc": "16.4.1", - "@next/swc-win32-x64-msvc": "16.4.1", - "sharp": "^0.35.5" + "@next/swc-darwin-arm64": "16.3.8", + "@next/swc-darwin-x64": "16.3.8", + "@next/swc-linux-arm64-gnu": "16.3.8", + "@next/swc-linux-arm64-musl": "16.3.8", + "@next/swc-linux-x64-gnu": "16.3.8", + "@next/swc-linux-x64-musl": "16.3.8", + "@next/swc-win32-arm64-msvc": "16.3.8", + "@next/swc-win32-x64-msvc": "16.3.8", + "sharp": "^0.35.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -12566,19 +12578,19 @@ } }, "node_modules/vitest": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.12.tgz", - "integrity": "sha512-5eWdz2yb3ajxWF3C8C4TjkXl7FGm3n2E/VlazALrQHzK7nYMSGFnCZto0bbPNl77AwkOZVLk0YnBAejUu0NiWA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.12", - "@vitest/mocker": "4.1.12", - "@vitest/pretty-format": "4.1.12", - "@vitest/runner": "4.1.12", - "@vitest/snapshot": "4.1.12", - "@vitest/spy": "4.1.12", - "@vitest/utils": "4.1.12", + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -12606,12 +12618,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.12", - "@vitest/browser-preview": "4.1.12", - "@vitest/browser-webdriverio": "^4.1.11", - "@vitest/coverage-istanbul": "4.1.12", - "@vitest/coverage-v8": "4.1.12", - "@vitest/ui": "4.1.12", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" diff --git a/ui/litellm-dashboard/package.json b/ui/litellm-dashboard/package.json index 432e327ec5a..8d1b7752b25 100644 --- a/ui/litellm-dashboard/package.json +++ b/ui/litellm-dashboard/package.json @@ -46,7 +46,7 @@ "jwt-decode": "4.0.0", "lucide-react": "0.513.0", "moment": "2.31.0", - "next": "16.4.1", + "next": "16.3.8", "next-themes": "^0.4.6", "nuqs": "^2.9.4", "openai": "6.49.0", @@ -88,8 +88,8 @@ "@types/react-copy-to-clipboard": "5.0.7", "@types/react-dom": "19.2.4", "@types/react-syntax-highlighter": "15.5.13", - "@vitest/coverage-v8": "4.1.12", - "@vitest/ui": "4.1.12", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", "eslint": "9.39.2", "eslint-config-next": "16.3.3", "eslint-config-prettier": "10.1.8", @@ -106,7 +106,7 @@ "typescript": "5.9.3", "typescript-eslint": "8.60.1", "vite": "7.3.7", - "vitest": "4.1.12" + "vitest": "4.1.11" }, "overrides": { "prismjs": "1.30.0", @@ -119,10 +119,7 @@ "axios": "1.13.6", "postcss": "8.5.23", "esbuild": "0.28.1", - "sharp": "^0.35.5", - "@litellm/lens-ui": { - "next": "$next" - } + "sharp": "^0.35.5" }, "engines": { "node": ">=24.14.1", diff --git a/uv.lock b/uv.lock index 17247935ebc..8d2a38c1806 100644 --- a/uv.lock +++ b/uv.lock @@ -2482,14 +2482,14 @@ wheels = [ [[package]] name = "gitpython" -version = "3.2.1" +version = "3.1.62" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/ff/fee293f1ec3e1b64db6eb270e00964f29c086d769201474cedb8d1608930/gitpython-3.2.1.tar.gz", hash = "sha256:0f1229ec5c057bdabdeeda8bda4f17bda679ab33dbccc00f24e6a480553c8613", size = 241688, upload-time = "2026-10-14T12:20:20.589Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/db/3ca813cbacb23ab6fe46ff38a9b5ef8e73e970c8051f2ce903aacafe0446/gitpython-3.1.62.tar.gz", hash = "sha256:1791de66309bc0c7cfca40bf8d2e3de7ca091cbf94e6051be1ad0722c61062af", size = 231728, upload-time = "2026-09-07T02:57:21.155Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/50/2a923c4dc02522ff9567f07c67ed1deca6d5fa9474f4708168b8088dfa37/gitpython-3.2.1-py3-none-any.whl", hash = "sha256:d66b0832aa8755c1a84892937282df0befb16c97e5a64aeebd68be9c5e323f53", size = 231548, upload-time = "2026-10-14T12:20:18.844Z" }, + { url = "https://files.pythonhosted.org/packages/d6/0b/29d7965215f8ef830a7ca1f42997fe13e5693d85e9edb18f938d063ef5f2/gitpython-3.1.62-py3-none-any.whl", hash = "sha256:7002251225e10e29d2e1f49e6532613fe5d5d9f0b6f1f02997a52b38fe56899e", size = 222753, upload-time = "2026-09-07T02:57:19.762Z" }, ] [[package]] @@ -2987,11 +2987,11 @@ wheels = [ [[package]] name = "graphql-core" -version = "3.2.13" +version = "3.2.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/5e/aa0d4e701b50db0bab71b125dd19ddb0f98c638d008d6f7e3d8ce9cbc92e/graphql_core-3.2.13.tar.gz", hash = "sha256:bb81dd266d4ab7b591bd976f1b23639d97776cb9ac1a896b4a93c271e11ed618", size = 577471, upload-time = "2026-09-27T12:22:57.136Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/c5/36aa96205c3ecbb3d34c7c24189e4553c7ca2ebc7e1dd07432339b980272/graphql_core-3.2.8.tar.gz", hash = "sha256:015457da5d996c924ddf57a43f4e959b0b94fb695b85ed4c29446e508ed65cf3", size = 513181, upload-time = "2026-03-05T19:55:37.332Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/b4/b52a324b0b297ddc49b8ea31a51d39fae63132f694a267b9381d94e2e0af/graphql_core-3.2.13-py3-none-any.whl", hash = "sha256:b0eb04f2c31556b2310a77c8fb53c74e8b56570f8fea594d89c6ef7827dbb497", size = 268191, upload-time = "2026-09-27T12:22:55.661Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/cb887d9afc5dabd78feefe6ccbaf83ff423c206a7a1b7aeeac05120b2125/graphql_core-3.2.8-py3-none-any.whl", hash = "sha256:cbee07bee1b3ed5e531723685369039f32ff815ef60166686e0162f540f1520c", size = 207349, upload-time = "2026-03-05T19:55:35.911Z" }, ] [[package]] @@ -4727,7 +4727,6 @@ proxy-runtime = [ { name = "mangum" }, { name = "opentelemetry-api" }, { name = "opentelemetry-exporter-otlp" }, - { name = "opentelemetry-exporter-otlp-json-http" }, { name = "opentelemetry-instrumentation-fastapi" }, { name = "opentelemetry-sdk" }, { name = "prometheus-client" }, @@ -4811,7 +4810,6 @@ dev = [ { name = "openapi-core" }, { name = "opentelemetry-api" }, { name = "opentelemetry-exporter-otlp" }, - { name = "opentelemetry-exporter-otlp-json-http" }, { name = "opentelemetry-instrumentation-fastapi" }, { name = "opentelemetry-sdk" }, { name = "parameterized" }, @@ -4859,7 +4857,6 @@ proxy-dev = [ { name = "litellm-admin-mcp", marker = "python_full_version >= '3.12'" }, { name = "opentelemetry-api" }, { name = "opentelemetry-exporter-otlp" }, - { name = "opentelemetry-exporter-otlp-json-http" }, { name = "opentelemetry-instrumentation-fastapi" }, { name = "opentelemetry-sdk" }, { name = "prisma" }, @@ -4933,11 +4930,10 @@ requires-dist = [ { name = "numpydoc", marker = "extra == 'utils'", specifier = ">=1.8.0,<2.0" }, { name = "nvidia-riva-client", marker = "extra == 'stt-nvidia-riva'", specifier = ">=2.15.0" }, { name = "openai", specifier = ">=2.20.0,<4.0.0" }, - { name = "opentelemetry-api", marker = "extra == 'proxy-runtime'", specifier = "==1.45.1" }, - { name = "opentelemetry-exporter-otlp", marker = "extra == 'proxy-runtime'", specifier = "==1.45.1" }, - { name = "opentelemetry-exporter-otlp-json-http", marker = "extra == 'proxy-runtime'", specifier = "==0.66b1" }, - { name = "opentelemetry-instrumentation-fastapi", marker = "extra == 'proxy-runtime'", specifier = "==0.66b1" }, - { name = "opentelemetry-sdk", marker = "extra == 'proxy-runtime'", specifier = "==1.45.1" }, + { name = "opentelemetry-api", marker = "extra == 'proxy-runtime'", specifier = "==1.33.1" }, + { name = "opentelemetry-exporter-otlp", marker = "extra == 'proxy-runtime'", specifier = "==1.33.1" }, + { name = "opentelemetry-instrumentation-fastapi", marker = "extra == 'proxy-runtime'", specifier = "==0.54b1" }, + { name = "opentelemetry-sdk", marker = "extra == 'proxy-runtime'", specifier = "==1.33.1" }, { name = "orjson", marker = "extra == 'proxy'", specifier = ">=3.11.6,<4.0" }, { name = "packaging", specifier = ">=24.0" }, { name = "polars", marker = "extra == 'proxy'", specifier = ">=1.38.1,<2.0" }, @@ -4952,7 +4948,7 @@ requires-dist = [ { name = "pydantic-settings", specifier = ">=2.14.1,<3.0" }, { name = "pyjwt", marker = "extra == 'proxy'", specifier = ">=2.13.0,<3.0" }, { name = "pynacl", marker = "extra == 'proxy'", specifier = ">=1.6.2,<2.0" }, - { name = "pypdf", marker = "extra == 'proxy-runtime'", specifier = ">=6.21.1,<7.0" }, + { name = "pypdf", marker = "extra == 'proxy-runtime'", specifier = ">=6.16.1,<7.0" }, { name = "pyroscope-io", marker = "sys_platform != 'win32' and extra == 'proxy'", specifier = ">=0.8.16,<1.0" }, { name = "python-dotenv", specifier = ">=1.0.0,<2.0" }, { name = "python-multipart", marker = "extra == 'proxy'", specifier = ">=0.0.27,<1.0" }, @@ -5008,7 +5004,7 @@ ci = [ { name = "langchain-openai", specifier = "==1.1.14" }, { name = "langgraph", specifier = ">=1.2.4,<1.3.0" }, { name = "langgraph-prebuilt", specifier = ">=1.1.0,<1.3.0" }, - { name = "logfire", specifier = "==5.2.0" }, + { name = "logfire", specifier = "==4.6.0" }, { name = "lunary", marker = "python_full_version == '3.10.*'", specifier = "==1.4.36" }, { name = "lunary", marker = "python_full_version >= '3.11'", specifier = "==1.4.37" }, { name = "pillow", marker = "python_full_version == '3.10.*'", specifier = "==12.3.0" }, @@ -5035,11 +5031,10 @@ dev = [ { name = "mypy", specifier = "==1.20.1" }, { name = "numpy", specifier = ">=1.26.0,<3.0" }, { name = "openapi-core", specifier = "==0.22.0" }, - { name = "opentelemetry-api", specifier = "==1.45.1" }, - { name = "opentelemetry-exporter-otlp", specifier = "==1.45.1" }, - { name = "opentelemetry-exporter-otlp-json-http", specifier = "==0.66b1" }, - { name = "opentelemetry-instrumentation-fastapi", specifier = "==0.66b1" }, - { name = "opentelemetry-sdk", specifier = "==1.45.1" }, + { name = "opentelemetry-api", specifier = "==1.33.1" }, + { name = "opentelemetry-exporter-otlp", specifier = "==1.33.1" }, + { name = "opentelemetry-instrumentation-fastapi", specifier = "==0.54b1" }, + { name = "opentelemetry-sdk", specifier = "==1.33.1" }, { name = "parameterized", specifier = "==0.9.0" }, { name = "psycopg", specifier = "==3.3.3" }, { name = "psycopg-binary", specifier = "==3.3.3" }, @@ -5083,11 +5078,10 @@ proxy-dev = [ { name = "backports-zstd", marker = "python_full_version < '3.14'", specifier = "==1.8.0" }, { name = "hypercorn", specifier = "==0.17.3" }, { name = "litellm-admin-mcp", marker = "python_full_version >= '3.12'", url = "https://github.com/BerriAI/liteadmin-mcp/archive/d35ec9c19c117d4c50cc1eccf6ce1296aac25a1a.tar.gz" }, - { name = "opentelemetry-api", specifier = "==1.45.1" }, - { name = "opentelemetry-exporter-otlp", specifier = "==1.45.1" }, - { name = "opentelemetry-exporter-otlp-json-http", specifier = "==0.66b1" }, - { name = "opentelemetry-instrumentation-fastapi", specifier = "==0.66b1" }, - { name = "opentelemetry-sdk", specifier = "==1.45.1" }, + { name = "opentelemetry-api", specifier = "==1.33.1" }, + { name = "opentelemetry-exporter-otlp", specifier = "==1.33.1" }, + { name = "opentelemetry-instrumentation-fastapi", specifier = "==0.54b1" }, + { name = "opentelemetry-sdk", specifier = "==1.33.1" }, { name = "prisma", specifier = "==0.11.0" }, { name = "prometheus-client", specifier = "==0.20.0" }, { name = "sentry-sdk", specifier = "==2.21.0" }, @@ -5179,24 +5173,21 @@ wheels = [ [[package]] name = "logfire" -version = "5.2.0" +version = "4.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "executing" }, - { name = "opentelemetry-api" }, { name = "opentelemetry-exporter-otlp-proto-http" }, { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-sdk" }, - { name = "opentelemetry-semantic-conventions" }, { name = "protobuf" }, - { name = "requests" }, { name = "rich" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/5a/e6cccc98d30358b8beee47c61c2a1ca66ac7a76a07de9ba333aa5219b9a6/logfire-5.2.0.tar.gz", hash = "sha256:5fb9ddda303759766e86f5704a1ae96f3a0072906d52e21fbaad2e3c3b03f823", size = 1404990, upload-time = "2026-10-13T17:36:30.746Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/fa/14cbd976e8ff77290c0c87f93c45029785537ffb84e2f5215416786cda11/logfire-4.6.0.tar.gz", hash = "sha256:974bc8f4efd3aa9df2d0c7b1d53b35dc4612ff5cee2be6c40dd65e5c26eab694", size = 533174, upload-time = "2025-09-10T14:56:42.168Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/cb/bfcd484c16eeac8cdeb2defe1a2ee39bb1b4b6e69324b85515e7818eb678/logfire-5.2.0-py3-none-any.whl", hash = "sha256:c73dd02cfe048f2254e803962aa992d7f53217d121f3488e797926c30442342c", size = 495128, upload-time = "2026-10-13T17:36:27.305Z" }, + { url = "https://files.pythonhosted.org/packages/04/9c/6384dc69601bfba19c1f5909a472b00cc4b5863f411ecf85be308beacf8f/logfire-4.6.0-py3-none-any.whl", hash = "sha256:d322844db7a2f1935976f2852a07e3753489ecc4e83fa288173b05a453bb1cbf", size = 219556, upload-time = "2025-09-10T14:56:37.278Z" }, ] [[package]] @@ -6315,144 +6306,81 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.45.1" +version = "1.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "deprecated" }, + { name = "importlib-metadata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/02/6e0ae9cc61bd3169d401077b507b3ebc344745171e1051ab430be012dcd9/opentelemetry_api-1.45.1.tar.gz", hash = "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", size = 72804, upload-time = "2026-10-06T17:32:58.133Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8d/1f5a45fbcb9a7d87809d460f09dc3399e3fbd31d7f3e14888345e9d29951/opentelemetry_api-1.33.1.tar.gz", hash = "sha256:1c6055fc0a2d3f23a50c7e17e16ef75ad489345fd3df1f8b8af7c0bbf8a109e8", size = 65002, upload-time = "2025-05-16T18:52:41.146Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/41/f7dcf80b81ee8e71c1a2b59f14208bc723edbd89ed027a73b175abf6348e/opentelemetry_api-1.45.1-py3-none-any.whl", hash = "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb", size = 60256, upload-time = "2026-10-06T17:32:33.506Z" }, -] - -[[package]] -name = "opentelemetry-exporter-http-transport" -version = "0.66b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-api" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/62/0c/e3ebdb4b507f66afcc905e6885a4946969bd75b45988492643356fbbdc63/opentelemetry_exporter_http_transport-0.66b1.tar.gz", hash = "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952", size = 11693, upload-time = "2026-10-06T17:32:59.65Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/69/6af86ff66492b481c6a4c05dcfd68beb47ed8ba046440a26a2aac76b95c7/opentelemetry_exporter_http_transport-0.66b1-py3-none-any.whl", hash = "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", size = 12155, upload-time = "2026-10-06T17:32:35.454Z" }, -] - -[package.optional-dependencies] -requests = [ - { name = "requests" }, -] -urllib3 = [ - { name = "urllib3" }, + { url = "https://files.pythonhosted.org/packages/05/44/4c45a34def3506122ae61ad684139f0bbc4e00c39555d4f7e20e0e001c8a/opentelemetry_api-1.33.1-py3-none-any.whl", hash = "sha256:4db83ebcf7ea93e64637ec6ee6fabee45c5cbe4abd9cf3da95c43828ddb50b83", size = 65771, upload-time = "2025-05-16T18:52:17.419Z" }, ] [[package]] name = "opentelemetry-exporter-otlp" -version = "1.45.1" +version = "1.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-exporter-otlp-proto-grpc" }, { name = "opentelemetry-exporter-otlp-proto-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e3/6f/5a561048ea372894f22f58f70e8478e4cd97d9a4e2d6f014559584a29bc5/opentelemetry_exporter_otlp-1.45.1.tar.gz", hash = "sha256:d0ac35592e77663a9fabf2740b4818c57196b1b764e0c8449d0d7bb2c7b2bc67", size = 6094, upload-time = "2026-10-06T17:33:00.966Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/3f/c8ad4f1c3aaadcea2b0f1b4d7970e7b7898c145699769a789f3435143f69/opentelemetry_exporter_otlp-1.33.1.tar.gz", hash = "sha256:4d050311ea9486e3994575aa237e32932aad58330a31fba24fdba5c0d531cf04", size = 6189, upload-time = "2025-05-16T18:52:43.176Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/e1/68c28d7da4482ce882a640b2c61405a78284c0a3a17a4db125647189b0b7/opentelemetry_exporter_otlp-1.45.1-py3-none-any.whl", hash = "sha256:ef3910d32b36ccbaf62390189759bd43a8109885e2d50b738ed9c9b255bf5cb5", size = 6730, upload-time = "2026-10-06T17:32:37.236Z" }, -] - -[[package]] -name = "opentelemetry-exporter-otlp-common" -version = "0.66b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-sdk" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cb/19/41de712173f43057e4532d42ece7d0c6d4210d353e5752433cb14987643f/opentelemetry_exporter_otlp_common-0.66b1.tar.gz", hash = "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9", size = 14325, upload-time = "2026-10-06T17:33:01.725Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/39/8c23d67665c762aa51840fa06f86e902e8f6f1693bc8d7e3d98cd6e2f753/opentelemetry_exporter_otlp_common-0.66b1-py3-none-any.whl", hash = "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", size = 12385, upload-time = "2026-10-06T17:32:38.177Z" }, -] - -[[package]] -name = "opentelemetry-exporter-otlp-json-common" -version = "0.66b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-proto-json" }, - { name = "opentelemetry-sdk" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/27/fd51023a4916ad00ef0c970577e148fba36e3682716d15a1495f74f830fb/opentelemetry_exporter_otlp_json_common-0.66b1.tar.gz", hash = "sha256:d8bd656ee03be5d2f54b533e03e297792c5017501417d132c3c67eb2ac95b3de", size = 24519, upload-time = "2026-10-06T17:33:02.52Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/34/6e7a4794ff9995042c8372af5c7e4e5127aea10dfeec64402fbec93a6d43/opentelemetry_exporter_otlp_json_common-0.66b1-py3-none-any.whl", hash = "sha256:1ae23eb3dfcd756607367c1ff5df2b4120fe481a523e7868b26b61720c558875", size = 14826, upload-time = "2026-10-06T17:32:39.052Z" }, -] - -[[package]] -name = "opentelemetry-exporter-otlp-json-http" -version = "0.66b1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "opentelemetry-exporter-http-transport", extra = ["urllib3"] }, - { name = "opentelemetry-exporter-otlp-common" }, - { name = "opentelemetry-exporter-otlp-json-common" }, - { name = "opentelemetry-proto-json" }, - { name = "opentelemetry-sdk" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/44/30/47c00f9329242b71724df87536613cb21596b41f294cdc37d8b216c5afaa/opentelemetry_exporter_otlp_json_http-0.66b1.tar.gz", hash = "sha256:940ca96fff3509ca06b3ce8733d4818ee022620aa9329cb7025c63e43c2d224d", size = 17583, upload-time = "2026-10-06T17:33:03.868Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/2d/469689a34b17ed83d4ddee480db8322246dcffeb95ef5fc5050bb8a16c8c/opentelemetry_exporter_otlp_json_http-0.66b1-py3-none-any.whl", hash = "sha256:4bd7607120fb7cc77aae88d5c7f629c40515d225b3ca5db293d20d091a248156", size = 14523, upload-time = "2026-10-06T17:32:40.837Z" }, + { url = "https://files.pythonhosted.org/packages/4d/32/b9add70dd4e845654fc9fcd1401a705477743880be6c3e62acb1ad0d8662/opentelemetry_exporter_otlp-1.33.1-py3-none-any.whl", hash = "sha256:9bcf1def35b880b55a49e31ebd63910edac14b294fd2ab884953c4deaff5b300", size = 7045, upload-time = "2025-05-16T18:52:21.022Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.45.1" +version = "1.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c1/8e/65e85e5137991a3c493b11682151d198638a5bc1dd4b4c5f67e013c57d7c/opentelemetry_exporter_otlp_proto_common-1.45.1.tar.gz", hash = "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", size = 18873, upload-time = "2026-10-06T17:33:04.471Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/18/a1ec9dcb6713a48b4bdd10f1c1e4d5d2489d3912b80d2bcc059a9a842836/opentelemetry_exporter_otlp_proto_common-1.33.1.tar.gz", hash = "sha256:c57b3fa2d0595a21c4ed586f74f948d259d9949b58258f11edb398f246bec131", size = 20828, upload-time = "2025-05-16T18:52:43.795Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/aa/92f225d353904e7f70b8b3e3c1b02db0cf56f744c2e83c581dc372e78873/opentelemetry_exporter_otlp_proto_common-1.45.1-py3-none-any.whl", hash = "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c", size = 15393, upload-time = "2026-10-06T17:32:41.911Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/9bcb17e2c29c1194a28e521b9d3f2ced09028934c3c52a8205884c94b2df/opentelemetry_exporter_otlp_proto_common-1.33.1-py3-none-any.whl", hash = "sha256:b81c1de1ad349785e601d02715b2d29d6818aed2c809c20219f3d1f20b038c36", size = 18839, upload-time = "2025-05-16T18:52:22.447Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.45.1" +version = "1.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "deprecated" }, { name = "googleapis-common-protos" }, { name = "grpcio" }, { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, - { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/00/a82af0be959dc58495740b169c6669a86e0811f6cd353a01eda34d255db3/opentelemetry_exporter_otlp_proto_grpc-1.45.1.tar.gz", hash = "sha256:3b3dcfbfdcb4e35149fcf309972282054b45228f5c10547d0095d6578510a9a0", size = 25997, upload-time = "2026-10-06T17:33:05.114Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/5f/75ef5a2a917bd0e6e7b83d3fb04c99236ee958f6352ba3019ea9109ae1a6/opentelemetry_exporter_otlp_proto_grpc-1.33.1.tar.gz", hash = "sha256:345696af8dc19785fac268c8063f3dc3d5e274c774b308c634f39d9c21955728", size = 22556, upload-time = "2025-05-16T18:52:44.76Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/46/2d1da202f1e17c81aae7efcf702898d524b46709e4d3e2bf1f7f8ca8fbc6/opentelemetry_exporter_otlp_proto_grpc-1.45.1-py3-none-any.whl", hash = "sha256:e42ecb789d2fc5d8145e3dadc3e2991c9f18cd166d7c7514e234702540274b76", size = 19492, upload-time = "2026-10-06T17:32:42.838Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ec/6047e230bb6d092c304511315b13893b1c9d9260044dd1228c9d48b6ae0e/opentelemetry_exporter_otlp_proto_grpc-1.33.1-py3-none-any.whl", hash = "sha256:7e8da32c7552b756e75b4f9e9c768a61eb47dee60b6550b37af541858d669ce1", size = 18591, upload-time = "2025-05-16T18:52:23.772Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.45.1" +version = "1.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "deprecated" }, { name = "googleapis-common-protos" }, { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-http-transport", extra = ["requests"] }, - { name = "opentelemetry-exporter-otlp-common" }, { name = "opentelemetry-exporter-otlp-proto-common" }, { name = "opentelemetry-proto" }, { name = "opentelemetry-sdk" }, { name = "requests" }, - { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/17/26487707ea4caa97b17e6e4b5fa72133a53512ffa2f5cf7a49ef284b29cb/opentelemetry_exporter_otlp_proto_http-1.45.1.tar.gz", hash = "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7", size = 28839, upload-time = "2026-10-06T17:33:05.713Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/48/e4314ac0ed2ad043c07693d08c9c4bf5633857f5b72f2fefc64fd2b114f6/opentelemetry_exporter_otlp_proto_http-1.33.1.tar.gz", hash = "sha256:46622d964a441acb46f463ebdc26929d9dec9efb2e54ef06acdc7305e8593c38", size = 15353, upload-time = "2025-05-16T18:52:45.522Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/1f/517eaa0187ba106a9da97160ce2add3a371812681dc440930b267f714e42/opentelemetry_exporter_otlp_proto_http-1.45.1-py3-none-any.whl", hash = "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", size = 22180, upload-time = "2026-10-06T17:32:43.946Z" }, + { url = "https://files.pythonhosted.org/packages/63/ba/5a4ad007588016fe37f8d36bf08f325fe684494cc1e88ca8fa064a4c8f57/opentelemetry_exporter_otlp_proto_http-1.33.1-py3-none-any.whl", hash = "sha256:ebd6c523b89a2ecba0549adb92537cc2bf647b4ee61afbbd5a4c6535aa3da7cf", size = 17733, upload-time = "2025-05-16T18:52:25.137Z" }, ] [[package]] name = "opentelemetry-instrumentation" -version = "0.66b1" +version = "0.54b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -6460,9 +6388,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/03/89e47ff8d52a4f83b343e6eb9ef1698ff45357216e5b6b2b21e0da5c5c7d/opentelemetry_instrumentation-0.66b1.tar.gz", hash = "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce", size = 43308, upload-time = "2026-10-06T17:36:10.703Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/fd/5756aea3fdc5651b572d8aef7d94d22a0a36e49c8b12fcb78cb905ba8896/opentelemetry_instrumentation-0.54b1.tar.gz", hash = "sha256:7658bf2ff914b02f246ec14779b66671508125c0e4227361e56b5ebf6cef0aec", size = 28436, upload-time = "2025-05-16T19:03:22.223Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/b2/d1413681ff43e13ac9860df27e1226d3199ab0b97b352ceea41abcc660a5/opentelemetry_instrumentation-0.66b1-py3-none-any.whl", hash = "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", size = 36904, upload-time = "2026-10-06T17:35:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/f4/89/0790abc5d9c4fc74bd3e03cb87afe2c820b1d1a112a723c1163ef32453ee/opentelemetry_instrumentation-0.54b1-py3-none-any.whl", hash = "sha256:a4ae45f4a90c78d7006c51524f57cd5aa1231aef031eae905ee34d5423f5b198", size = 31019, upload-time = "2025-05-16T19:02:15.611Z" }, ] [[package]] @@ -6497,7 +6425,7 @@ wheels = [ [[package]] name = "opentelemetry-instrumentation-asgi" -version = "0.66b1" +version = "0.54b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asgiref" }, @@ -6506,9 +6434,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-util-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/d9/ff522f5c3e340e9007554923b1a4d2ac451676f8757bafb3d0057f68b5c3/opentelemetry_instrumentation_asgi-0.66b1.tar.gz", hash = "sha256:78cdc5e45e897e16a8dac9d282e8d5bdf9af2d58e1313fa0bdd4a134c6f9dafc", size = 25992, upload-time = "2026-10-06T17:36:14.593Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/f7/a3377f9771947f4d3d59c96841d3909274f446c030dbe8e4af871695ddee/opentelemetry_instrumentation_asgi-0.54b1.tar.gz", hash = "sha256:ab4df9776b5f6d56a78413c2e8bbe44c90694c67c844a1297865dc1bd926ed3c", size = 24230, upload-time = "2025-05-16T19:03:30.234Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/ea/10ba99110bf3c9fb736af39c96ca8f3668b988cabb6b59309e058c44461c/opentelemetry_instrumentation_asgi-0.66b1-py3-none-any.whl", hash = "sha256:78b3f9bdf0fa38c65935a2ab46d59e0f9de873a51e0c95b0329f106e2ccb5274", size = 15812, upload-time = "2026-10-06T17:35:17.638Z" }, + { url = "https://files.pythonhosted.org/packages/20/24/7a6f0ae79cae49927f528ecee2db55a5bddd87b550e310ce03451eae7491/opentelemetry_instrumentation_asgi-0.54b1-py3-none-any.whl", hash = "sha256:84674e822b89af563b283a5283c2ebb9ed585d1b80a1c27fb3ac20b562e9f9fc", size = 16338, upload-time = "2025-05-16T19:02:22.808Z" }, ] [[package]] @@ -6559,7 +6487,7 @@ wheels = [ [[package]] name = "opentelemetry-instrumentation-fastapi" -version = "0.66b1" +version = "0.54b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -6568,9 +6496,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-util-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/2a/cd4125b7acbea2ed17f1d31b58c184cb0a79fcb5541ceb4de90ffc6d8c01/opentelemetry_instrumentation_fastapi-0.66b1.tar.gz", hash = "sha256:584cf9d2c4417ff8b2d6ff2bc606bfe13c8b3456018bf94f50f2cf658492505b", size = 26634, upload-time = "2026-10-06T17:36:25.157Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/3b/9a262cdc1a4defef0e52afebdde3e8add658cc6f922e39e9dcee0da98349/opentelemetry_instrumentation_fastapi-0.54b1.tar.gz", hash = "sha256:1fcad19cef0db7092339b571a59e6f3045c9b58b7fd4670183f7addc459d78df", size = 19325, upload-time = "2025-05-16T19:03:45.359Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/70/676928d537978acc7bff2ac8657bd0836ba608ffc8f65455238f1fa2bd0f/opentelemetry_instrumentation_fastapi-0.66b1-py3-none-any.whl", hash = "sha256:97f8ac8fd7537517f9e6988bd0aca04bfa5aad564bcd46c245530739e2be72d1", size = 14216, upload-time = "2026-10-06T17:35:32.827Z" }, + { url = "https://files.pythonhosted.org/packages/df/9c/6b2b0f9d6c5dea7528ae0bf4e461dd765b0ae35f13919cd452970bb0d0b3/opentelemetry_instrumentation_fastapi-0.54b1-py3-none-any.whl", hash = "sha256:fb247781cfa75fd09d3d8713c65e4a02bd1e869b00e2c322cc516d4b5429860c", size = 12125, upload-time = "2025-05-16T19:02:41.172Z" }, ] [[package]] @@ -6666,16 +6594,15 @@ wheels = [ [[package]] name = "opentelemetry-instrumentation-logging" -version = "0.66b1" +version = "0.54b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-instrumentation" }, - { name = "opentelemetry-semantic-conventions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/ca/0fcf525f238f4ab9df3a6d0423fb057085ea39a3e79ac912fef3d5fa16b8/opentelemetry_instrumentation_logging-0.66b1.tar.gz", hash = "sha256:2250240ecb1fce36939f64eafd988832338684564fb4c7b401c9b9af0ee11d46", size = 20353, upload-time = "2026-10-06T17:36:29.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/5b/88ed39f22e8c6eb4f6192ab9a62adaa115579fcbcadb3f0241ee645eea56/opentelemetry_instrumentation_logging-0.54b1.tar.gz", hash = "sha256:893a3cbfda893b64ff71b81991894e2fd6a9267ba85bb6c251f51c0419fbe8fa", size = 9976, upload-time = "2025-05-16T19:03:49.976Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/7b/76bea1e80787848a9ac9882a598cbf1e761f16cebdb399d0e675221e34a0/opentelemetry_instrumentation_logging-0.66b1-py3-none-any.whl", hash = "sha256:f13761110c52eecae9d7debedb0f88b46acc248c20437e97c2a5b3a2b4f96a02", size = 15928, upload-time = "2026-10-06T17:35:39.915Z" }, + { url = "https://files.pythonhosted.org/packages/96/0c/b441fb30d860f25040eaed61e89d68f4d9ee31873159ed18cbc1b92eba56/opentelemetry_instrumentation_logging-0.54b1-py3-none-any.whl", hash = "sha256:01a4cec54348f13941707d857b850b0febf9d49f45d0fcf0673866e079d7357b", size = 12579, upload-time = "2025-05-16T19:02:49.039Z" }, ] [[package]] @@ -6801,7 +6728,7 @@ wheels = [ [[package]] name = "opentelemetry-instrumentation-requests" -version = "0.66b1" +version = "0.54b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -6809,9 +6736,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-util-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/62/dcca0b7a2008675056040c61b70d37b5a24439613c7822068908ad3255ae/opentelemetry_instrumentation_requests-0.66b1.tar.gz", hash = "sha256:28578f72e68e3a5be3226c618ac9570ed360ef1dd22d5d6e0721c6905a4ccc67", size = 17985, upload-time = "2026-10-06T17:36:37.478Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/45/116da84930d3dc2f5cdd876283ca96e9b96547bccee7eaa0bd01ce6bf046/opentelemetry_instrumentation_requests-0.54b1.tar.gz", hash = "sha256:3eca5d697c5564af04c6a1dd23b6a3ffbaf11e64887c6051655cee03998f4654", size = 15148, upload-time = "2025-05-16T19:04:00.488Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/01/98e9bb7fef12784cd19811e454cf25bce4d774fd3e46b3f5fd2bce237075/opentelemetry_instrumentation_requests-0.66b1-py3-none-any.whl", hash = "sha256:7ba17d984a2bd876b88bf0aafcca27b5e3ff4c1821e0fe07641e380a3cf3a3a2", size = 13320, upload-time = "2026-10-06T17:35:52.477Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b1/6e33d2c3d3cc9e3ae20a9a77625ec81a509a0e5d7fa87e09e7f879468990/opentelemetry_instrumentation_requests-0.54b1-py3-none-any.whl", hash = "sha256:a0c4cd5d946224f336d6bd73cdabdecc6f80d5c39208f84eb96eb15f16cd41a0", size = 12968, upload-time = "2025-05-16T19:03:03.131Z" }, ] [[package]] @@ -6831,7 +6758,7 @@ wheels = [ [[package]] name = "opentelemetry-instrumentation-sqlalchemy" -version = "0.66b1" +version = "0.54b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -6840,23 +6767,23 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d4/3e/d69fb08dacc4c248daedf7357732ddca7a0aaff7072a55e311d3da3ea51c/opentelemetry_instrumentation_sqlalchemy-0.66b1.tar.gz", hash = "sha256:a10043953fcba71911bf29a024f8cc337260c1ef0b4fc844b96cae0de0947baa", size = 18181, upload-time = "2026-10-06T17:36:38.111Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/33/78a25ae4233d42058bb0b363ba4fea7d7210e53c24e5e31f16d5cf6cf957/opentelemetry_instrumentation_sqlalchemy-0.54b1.tar.gz", hash = "sha256:97839acf1c9b96ded857fca57a09b86a56cf8d9eb6d706b7ceaee9352a460e03", size = 14620, upload-time = "2025-05-16T19:04:01.215Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/05/f8cff0c68a7f9ab8fab01904be5a49c5214435109c2a6b6d173a974c10d9/opentelemetry_instrumentation_sqlalchemy-0.66b1-py3-none-any.whl", hash = "sha256:aa30b10d880d7e91cf94b23a92ac85cec09ffddd8f0d40256d7c510e3dd33971", size = 14425, upload-time = "2026-10-06T17:35:53.436Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/1c954885815614ef5c1e8c7bbf57a5275e64cd6fb5946b65e17162a34037/opentelemetry_instrumentation_sqlalchemy-0.54b1-py3-none-any.whl", hash = "sha256:d2ca5edb4c7ecef120d51aad6793b7da1cc80207ccfd31c437ee18f098e7c4c4", size = 14169, upload-time = "2025-05-16T19:03:04.119Z" }, ] [[package]] name = "opentelemetry-instrumentation-threading" -version = "0.66b1" +version = "0.54b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-instrumentation" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/4f/5fa05266cf24cae93fc32d557d0d2d2f605f81cca1df90614231bbad3cd1/opentelemetry_instrumentation_threading-0.66b1.tar.gz", hash = "sha256:2c217359439d09ca4a4be524db368c05a3cc7f218b85e8086f22664db0a03538", size = 9031, upload-time = "2026-10-06T17:36:41.147Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/bd/561245292e7cc78ac7a0a75537873aea87440cb9493d41371421b3308c2b/opentelemetry_instrumentation_threading-0.54b1.tar.gz", hash = "sha256:3a081085b59675baf7bd93126a681903e6304a5f283df5eaecdd44bcb66df578", size = 8774, upload-time = "2025-05-16T19:04:04.482Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/da/1cd52b8be7a521958ccdb9a1c4e5ae743aa2be295ba21ec0b620dc87842a/opentelemetry_instrumentation_threading-0.66b1-py3-none-any.whl", hash = "sha256:94039c60603060d26524e31aed1181a5d153e43f58462d110dbeb4830687d7d7", size = 8493, upload-time = "2026-10-06T17:35:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/d87ec07d69546adaad525ba5d40d27324a45cba29097d9854a53d9af5047/opentelemetry_instrumentation_threading-0.54b1-py3-none-any.whl", hash = "sha256:bc229e6cd3f2b29fafe0a8dd3141f452e16fcb4906bca4fbf52609f99fb1eb42", size = 9314, upload-time = "2025-05-16T19:03:09.527Z" }, ] [[package]] @@ -6891,7 +6818,7 @@ wheels = [ [[package]] name = "opentelemetry-instrumentation-urllib3" -version = "0.66b1" +version = "0.54b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, @@ -6900,9 +6827,9 @@ dependencies = [ { name = "opentelemetry-util-http" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/ab/2dd6b3a69df31ae12714bf24fcc2772956d7cb8f9082432c9e63a37e6930/opentelemetry_instrumentation_urllib3-0.66b1.tar.gz", hash = "sha256:31e84a2ccfa86ca1d4f1209ea66c1f709c335348d5c487477d3843172efcc408", size = 18826, upload-time = "2026-10-06T17:36:43.547Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/6f/76a46806cd21002cac1bfd087f5e4674b195ab31ab44c773ca534b6bb546/opentelemetry_instrumentation_urllib3-0.54b1.tar.gz", hash = "sha256:0d30ba3b230e4100cfadaad29174bf7bceac70e812e4f5204e681e4b55a74cd9", size = 15697, upload-time = "2025-05-16T19:04:07.709Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/7f/16f82bf359c28fb71ad4dc5ad386cce3483abbf1ce0ade087fe78c18daf3/opentelemetry_instrumentation_urllib3-0.66b1-py3-none-any.whl", hash = "sha256:e48b448fed4a127e54c584c37a443c91d80d34b9f9de324af9a00cead8b338b8", size = 13462, upload-time = "2026-10-06T17:36:02.242Z" }, + { url = "https://files.pythonhosted.org/packages/ff/7a/d75bec41edb6deaf1d2859bab66a84c8ba03e822e7eafdb245da205e53f6/opentelemetry_instrumentation_urllib3-0.54b1-py3-none-any.whl", hash = "sha256:e87958c297ddd36d30e1c9069f34a9690e845e4ccc2662dd80e99ed976d4c03e", size = 13123, upload-time = "2025-05-16T19:03:14.053Z" }, ] [[package]] @@ -6952,50 +6879,41 @@ wheels = [ [[package]] name = "opentelemetry-proto" -version = "1.45.1" +version = "1.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4b/7f/15f014fb195da6c2dbb6c71399b8e76824878718e94de6454038488eed28/opentelemetry_proto-1.45.1.tar.gz", hash = "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", size = 46488, upload-time = "2026-10-06T17:33:11.49Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/dc/791f3d60a1ad8235930de23eea735ae1084be1c6f96fdadf38710662a7e5/opentelemetry_proto-1.33.1.tar.gz", hash = "sha256:9627b0a5c90753bf3920c398908307063e4458b287bb890e5c1d6fa11ad50b68", size = 34363, upload-time = "2025-05-16T18:52:52.141Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/9a/42ec8180a769516ae757e893b69736826efceac7332553915b4528a91c6d/opentelemetry_proto-1.45.1-py3-none-any.whl", hash = "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e", size = 72488, upload-time = "2026-10-06T17:32:53.057Z" }, -] - -[[package]] -name = "opentelemetry-proto-json" -version = "0.66b1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/90/25/a845cc4644123c6b29b2c21822341a55e7f1808703d4f557d24cbd841290/opentelemetry_proto_json-0.66b1.tar.gz", hash = "sha256:92bb70ff0f05815821e922ea628bfdf96f034b981747b0ba8e09db61e1a9cddd", size = 18708, upload-time = "2026-10-06T17:33:12.169Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/7d/bb6acc006c6c4042ef9f734f4901630a440992033f48b66a58fc8fd5e3f4/opentelemetry_proto_json-0.66b1-py3-none-any.whl", hash = "sha256:0d07f1922451cf3a8705bb46fa807f478a1164fbe9287bf7bd50e1ff43f75da3", size = 32061, upload-time = "2026-10-06T17:32:54.097Z" }, + { url = "https://files.pythonhosted.org/packages/c4/29/48609f4c875c2b6c80930073c82dd1cafd36b6782244c01394007b528960/opentelemetry_proto-1.33.1-py3-none-any.whl", hash = "sha256:243d285d9f29663fc7ea91a7171fcc1ccbbfff43b48df0774fd64a37d98eda70", size = 55854, upload-time = "2025-05-16T18:52:36.269Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.45.1" +version = "1.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/79/7392e21a1c8f0c61d90b223e31c7e48cb9d452e91a6b820ad24cca5f23c4/opentelemetry_sdk-1.45.1.tar.gz", hash = "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", size = 218324, upload-time = "2026-10-06T17:33:13.26Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/12/909b98a7d9b110cce4b28d49b2e311797cffdce180371f35eba13a72dd00/opentelemetry_sdk-1.33.1.tar.gz", hash = "sha256:85b9fcf7c3d23506fbc9692fd210b8b025a1920535feec50bd54ce203d57a531", size = 161885, upload-time = "2025-05-16T18:52:52.832Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/3c/87c42b4bd6dd297536f04cd9383d212ac557ecd49f2cbdcd46da1c9ef5c8/opentelemetry_sdk-1.45.1-py3-none-any.whl", hash = "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4", size = 140063, upload-time = "2026-10-06T17:32:55.04Z" }, + { url = "https://files.pythonhosted.org/packages/df/8e/ae2d0742041e0bd7fe0d2dcc5e7cce51dcf7d3961a26072d5b43cc8fa2a7/opentelemetry_sdk-1.33.1-py3-none-any.whl", hash = "sha256:19ea73d9a01be29cacaa5d6c8ce0adc0b7f7b4d58cc52f923e4413609f670112", size = 118950, upload-time = "2025-05-16T18:52:37.297Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.66b1" +version = "0.54b1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "deprecated" }, { name = "opentelemetry-api" }, - { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/e4/dbbfb2a010c4db2224a5114638acede6fe563d33cc20fb1752cebcbe6298/opentelemetry_semantic_conventions-0.66b1.tar.gz", hash = "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", size = 150250, upload-time = "2026-10-06T17:33:14.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/2c/d7990fc1ffc82889d466e7cd680788ace44a26789809924813b164344393/opentelemetry_semantic_conventions-0.54b1.tar.gz", hash = "sha256:d1cecedae15d19bdaafca1e56b29a66aa286f50b5d08f036a145c7f3e9ef9cee", size = 118642, upload-time = "2025-05-16T18:52:53.962Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/14/67f8aa798857f8cf686f515bf93d9bb877ce952ddc8efae0fa25b45ce0d6/opentelemetry_semantic_conventions-0.66b1-py3-none-any.whl", hash = "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b", size = 206279, upload-time = "2026-10-06T17:32:56.103Z" }, + { url = "https://files.pythonhosted.org/packages/0a/80/08b1698c52ff76d96ba440bf15edc2f4bc0a279868778928e947c1004bdd/opentelemetry_semantic_conventions-0.54b1-py3-none-any.whl", hash = "sha256:29dab644a7e435b58d3a3918b58c333c92686236b30f7891d5e51f02933ca60d", size = 194938, upload-time = "2025-05-16T18:52:38.796Z" }, ] [[package]] @@ -7009,11 +6927,11 @@ wheels = [ [[package]] name = "opentelemetry-util-http" -version = "0.66b1" +version = "0.54b1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/b5/df4b61da899f6ebdffdbdf0c8b0f3189ee57151694ccd5b7d50ee2906241/opentelemetry_util_http-0.66b1.tar.gz", hash = "sha256:047dea1a628031f857a5a32261dc0e955bc162d39993ed1cffb8f2cff5ba8a62", size = 11586, upload-time = "2026-10-06T17:36:46.572Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/9f/1d8a1d1f34b9f62f2b940b388bf07b8167a8067e70870055bd05db354e5c/opentelemetry_util_http-0.54b1.tar.gz", hash = "sha256:f0b66868c19fbaf9c9d4e11f4a7599fa15d5ea50b884967a26ccd9d72c7c9d15", size = 8044, upload-time = "2025-05-16T19:04:10.79Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/9b/c77ecaea79ba0de1a11e7f06a7f5eea7043ec23f1860dcf5f03536698e4c/opentelemetry_util_http-0.66b1-py3-none-any.whl", hash = "sha256:8f443d7abcaf29c4a07b373bbd31b5b39132c0ed3c27d015a59dc0323d5b1c58", size = 8387, upload-time = "2026-10-06T17:36:06.984Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ef/c5aa08abca6894792beed4c0405e85205b35b8e73d653571c9ff13a8e34e/opentelemetry_util_http-0.54b1-py3-none-any.whl", hash = "sha256:b1c91883f980344a1c3c486cffd47ae5c9c1dd7323f9cbe9fdb7cadb401c87c9", size = 7301, upload-time = "2025-05-16T19:03:18.18Z" }, ] [[package]] @@ -8239,14 +8157,14 @@ wheels = [ [[package]] name = "pypdf" -version = "6.21.1" +version = "6.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/e1/32a9c4dbd94618145f8bab5c623bdb0a528f23fc8719afd77ba0278fd377/pypdf-6.21.1.tar.gz", hash = "sha256:2d62c383acd6e0dff0a96322ba8b1135229343b084d73c6f6cfd947fd291afcb", size = 7088597, upload-time = "2026-10-17T10:29:52.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/ac/63d71aaedb59acbcdef491e6ca6469165e3771c9c74358204818fd9bc5a6/pypdf-6.19.0.tar.gz", hash = "sha256:bbc43aca292369ccc6cbc8a921991ecf2538a3587ab5a116eff06c321d647155", size = 7033266, upload-time = "2026-09-16T09:32:05.946Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/e6/24c944e473c34e818cf1b98085796fcd780465ecfe17663f2bf8fee1958b/pypdf-6.21.1-py3-none-any.whl", hash = "sha256:553e2d42f5bb3bb2a785a7d3de8ef90bb7df74b5413eff5a7b4388007ea9254b", size = 405134, upload-time = "2026-10-17T10:29:50.687Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2c/c43c03eaf630435f023f1dc61ec4a4a78951ad5530a62c71cc89bde307b7/pypdf-6.19.0-py3-none-any.whl", hash = "sha256:7e5d6e730e7dae87d560a2cee218b852f6498c8be61966f3cd02ead971e48d14", size = 395480, upload-time = "2026-09-16T09:32:04.087Z" }, ] [[package]] diff --git a/vscode-extension/package-lock.json b/vscode-extension/package-lock.json index 81e9425ef4e..536d7a88f25 100644 --- a/vscode-extension/package-lock.json +++ b/vscode-extension/package-lock.json @@ -17,7 +17,7 @@ "@vscode/vsce": "^4.0.0", "esbuild": "^0.28.2", "typescript": "^5.9.3", - "vitest": "^4.1.12" + "vitest": "^4.1.11" }, "engines": { "vscode": "^1.115.0" @@ -1323,16 +1323,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.12.tgz", - "integrity": "sha512-vFlTt+YEu9WBpNjdGayNmlMVzrWPi1+sv853/7t2FU7hpO5gAFBKHzQmkCq5Xjpr+evegF2994LQT3hF3mRlqQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.12", - "@vitest/utils": "4.1.12", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -1341,13 +1341,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.12.tgz", - "integrity": "sha512-fKShNpKZzupWZuY0z7egydvL0sqW4INEZBEExMBMAa6izlENEVHwoXgdaTCaBZ5fwlr4xf2o1wXC83n9RpO+NA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.12", + "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -1368,9 +1368,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.12.tgz", - "integrity": "sha512-XW9O8+Mxx4VpCtkYdMJLZQQwsVryP9wk7cPKDxtXFfwSak6yRh5+2fep/Y0XF/ElBvJHq8bcYdi6aeuai0opZA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "license": "MIT", "dependencies": { @@ -1381,13 +1381,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.12.tgz", - "integrity": "sha512-NKlVKCr5QnCe8jijdMnmkpHHU+WTqiLFR6G0pHji9uRjXK9xJl1k1nTw6F3pOOj2PDcsDt2GKQMI7wIsaT491w==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.12", + "@vitest/utils": "4.1.11", "pathe": "^2.0.3" }, "funding": { @@ -1395,14 +1395,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.12.tgz", - "integrity": "sha512-ko1y1Qgb/slzQ9mr+b89Cuqud8WG1TKtsPD8r7ZUBdvVOGt85Af2HwCqHujrhwJq95hs/9RM7fcAPg57o1vlzw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.12", - "@vitest/utils": "4.1.12", + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -1411,9 +1411,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.12.tgz", - "integrity": "sha512-RGq9x4JNGNTZHEJxvN0oSiJjeSCExm0n2gZKKJZHY9cX5AVA6M7VJILBT3ZJ/tJ/0J9uWT7fETV9/btoi//vSg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, "license": "MIT", "funding": { @@ -1421,13 +1421,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.12.tgz", - "integrity": "sha512-Q3awXrwOfEV+qc3WmO5ONyE0yO7Q1gQ9/oyHxGLZ/pl7N3NO/ZztqQwKLuwMkV+CA1JYSIeO3gkHnRDIo4AVaA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.12", + "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -3390,19 +3390,19 @@ } }, "node_modules/vitest": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.12.tgz", - "integrity": "sha512-5eWdz2yb3ajxWF3C8C4TjkXl7FGm3n2E/VlazALrQHzK7nYMSGFnCZto0bbPNl77AwkOZVLk0YnBAejUu0NiWA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.12", - "@vitest/mocker": "4.1.12", - "@vitest/pretty-format": "4.1.12", - "@vitest/runner": "4.1.12", - "@vitest/snapshot": "4.1.12", - "@vitest/spy": "4.1.12", - "@vitest/utils": "4.1.12", + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -3430,12 +3430,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.12", - "@vitest/browser-preview": "4.1.12", - "@vitest/browser-webdriverio": "^4.1.11", - "@vitest/coverage-istanbul": "4.1.12", - "@vitest/coverage-v8": "4.1.12", - "@vitest/ui": "4.1.12", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" diff --git a/vscode-extension/package.json b/vscode-extension/package.json index a3cf2dc7a5f..431dad316a1 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -82,6 +82,6 @@ "@vscode/vsce": "^4.0.0", "esbuild": "^0.28.2", "typescript": "^5.9.3", - "vitest": "^4.1.12" + "vitest": "^4.1.11" } }