From 8c91c9d76b5a71feb2ece7bc5cdb2d50221c19fd Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Thu, 21 May 2026 00:19:59 +0530 Subject: [PATCH 001/198] refactor: replace ContextMenu with DropdownMenu in SearchSpaceAvatar for improved mobile and desktop interactions --- .../layout/ui/icon-rail/SearchSpaceAvatar.tsx | 118 ++++++++---------- 1 file changed, 52 insertions(+), 66 deletions(-) diff --git a/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx b/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx index 944536c8f..0aed0db61 100644 --- a/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx +++ b/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx @@ -2,14 +2,9 @@ import { Settings, Trash2, Users } from "lucide-react"; import { useTranslations } from "next-intl"; +import type { MouseEvent } from "react"; import { useCallback, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; -import { - ContextMenu, - ContextMenuContent, - ContextMenuItem, - ContextMenuTrigger, -} from "@/components/ui/context-menu"; import { DropdownMenu, DropdownMenuContent, @@ -80,19 +75,28 @@ export function SearchSpaceAvatar({ const initials = getInitials(name); const sizeClasses = size === "sm" ? "h-8 w-8 text-xs" : "h-10 w-10 text-sm"; - // Long-press state for mobile - const [longPressMenuOpen, setLongPressMenuOpen] = useState(false); + const [menuOpen, setMenuOpen] = useState(false); const longPressTimer = useRef | null>(null); const touchMoved = useRef(false); + const openMenu = useCallback(() => { + setMenuOpen(true); + }, []); + + const handleContextMenu = useCallback((event: MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + setMenuOpen(true); + }, []); + const handleTouchStart = useCallback(() => { touchMoved.current = false; longPressTimer.current = setTimeout(() => { if (!touchMoved.current) { - setLongPressMenuOpen(true); + openMenu(); } }, 500); - }, []); + }, [openMenu]); const handleTouchMove = useCallback(() => { touchMoved.current = true; @@ -120,12 +124,26 @@ export function SearchSpaceAvatar({ ); - const avatarButton = ( + const avatarButton = (withMenuHandlers = false) => ( diff --git a/surfsense_web/components/assistant-ui/document-upload-popup.tsx b/surfsense_web/components/assistant-ui/document-upload-popup.tsx index 88e99e67f..477d7ee77 100644 --- a/surfsense_web/components/assistant-ui/document-upload-popup.tsx +++ b/surfsense_web/components/assistant-ui/document-upload-popup.tsx @@ -1,7 +1,7 @@ "use client"; import { useAtomValue } from "jotai"; -import { AlertTriangle, Settings } from "lucide-react"; +import { AlertTriangle } from "lucide-react"; import { useRouter } from "next/navigation"; import { createContext, @@ -148,7 +148,7 @@ const DocumentUploadPopupContent: FC<{
{!isLoading && !hasDocumentSummaryLLM ? (
- + LLM Configuration Required @@ -159,13 +159,12 @@ const DocumentUploadPopupContent: FC<{

diff --git a/surfsense_web/components/ui/button.tsx b/surfsense_web/components/ui/button.tsx index 66d8e231c..f7fb1f045 100644 --- a/surfsense_web/components/ui/button.tsx +++ b/surfsense_web/components/ui/button.tsx @@ -14,7 +14,7 @@ const buttonVariants = cva( "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40", outline: "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground", - secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", + secondary: "bg-accent text-accent-foreground shadow-xs hover:bg-accent/80", ghost: "hover:bg-accent hover:text-accent-foreground focus-visible:ring-0 focus-visible:ring-offset-0", link: "text-primary underline-offset-4 hover:underline", From 60049936e30867bb47892eb89aae4ccb8d091aac Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Thu, 21 May 2026 23:00:56 +0530 Subject: [PATCH 005/198] chore(dev): add local OpenTelemetry backend configuration --- docker/docker-compose.dev.yml | 9 +++++++++ surfsense_backend/.env.example | 11 ++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 53b8ea1a9..58cb7b42f 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -78,6 +78,15 @@ services: timeout: 5s retries: 5 + otel-lgtm: + image: grafana/otel-lgtm:latest + ports: + - "${OTEL_GRPC_PORT:-4317}:4317" + - "${OTEL_HTTP_PORT:-4318}:4318" + - "${OTEL_GRAFANA_PORT:-3001}:3000" + - "${OTEL_TEMPO_PORT:-3200}:3200" + restart: unless-stopped + searxng: image: searxng/searxng:2026.3.13-3c1f68c59 ports: diff --git a/surfsense_backend/.env.example b/surfsense_backend/.env.example index 3d442973c..908f4645d 100644 --- a/surfsense_backend/.env.example +++ b/surfsense_backend/.env.example @@ -303,8 +303,17 @@ LANGSMITH_PROJECT=surfsense # SURFSENSE_ENABLE_BUSY_MUTEX=false # SURFSENSE_ENABLE_LLM_TOOL_SELECTOR=false # adds a per-turn LLM call -# Observability — OTel (also requires OTEL_EXPORTER_OTLP_ENDPOINT) +# Observability - OTel # SURFSENSE_ENABLE_OTEL=false +# OpenTelemetry - endpoint enables export; absent = no-op. +# Production should point at an OTel Collector. For local docker-compose.dev.yml, +# use http://otel-lgtm:4317 instead. +# OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 +# OTEL_EXPORTER_OTLP_PROTOCOL=grpc # or http/protobuf +# OTEL_SERVICE_NAME=surfsense-backend +# OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production +# OTEL_METRIC_EXPORT_INTERVAL=60000 # ms +# OTEL_SDK_DISABLED=false # spec kill-switch # Skills + subagents # SURFSENSE_ENABLE_SKILLS=false From eb2e2b253baa7cb21c0e3558dbe610ea994d4bda Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Thu, 21 May 2026 23:01:54 +0530 Subject: [PATCH 006/198] feat(observability): add OpenTelemetry process bootstrap --- surfsense_backend/app/app.py | 39 +- surfsense_backend/app/celery_app.py | 4 + .../app/observability/__init__.py | 2 + .../app/observability/bootstrap.py | 361 ++++++++++++++++++ surfsense_backend/app/observability/otel.py | 5 + .../app/tasks/celery_tasks/__init__.py | 4 + surfsense_backend/main.py | 19 +- 7 files changed, 413 insertions(+), 21 deletions(-) create mode 100644 surfsense_backend/app/observability/bootstrap.py diff --git a/surfsense_backend/app/app.py b/surfsense_backend/app/app.py index fc6242643..43b0af7d2 100644 --- a/surfsense_backend/app/app.py +++ b/surfsense_backend/app/app.py @@ -1,4 +1,5 @@ import asyncio +import contextlib import gc import logging import time @@ -36,13 +37,15 @@ from app.config import ( ) from app.db import User, create_db_and_tables, get_async_session from app.exceptions import GENERIC_5XX_MESSAGE, ISSUES_URL, SurfSenseError +from app.observability import metrics as ot_metrics +from app.observability.bootstrap import init_otel, shutdown_otel from app.rate_limiter import get_real_client_ip, limiter from app.routes import router as crud_router from app.routes.auth_routes import router as auth_router from app.schemas import UserCreate, UserRead, UserUpdate from app.tasks.surfsense_docs_indexer import seed_surfsense_docs from app.users import SECRET, auth_backend, current_active_user, fastapi_users -from app.utils.perf import get_perf_logger, log_system_snapshot +from app.utils.perf import log_system_snapshot _error_logger = logging.getLogger("surfsense.errors") @@ -127,6 +130,8 @@ def _http_exception_handler(request: Request, exc: HTTPException) -> JSONRespons logged server-side. """ rid = _get_request_id(request) + if exc.status_code in {401, 403} and request.url.path.startswith("/auth"): + ot_metrics.record_auth_failure(reason=_status_to_code(exc.status_code)) should_sanitize = exc.status_code == 500 # Structured dict details (e.g. {"code": "CAPTCHA_REQUIRED", "message": "..."}) @@ -213,6 +218,7 @@ def _validation_error_handler( def _unhandled_exception_handler(request: Request, exc: Exception) -> JSONResponse: """Catch-all: log full traceback, return sanitized 500.""" rid = _get_request_id(request) + ot_metrics.record_auth_failure(reason="unhandled_exception") _error_logger.error( "[%s] Unhandled exception on %s %s", rid, @@ -246,6 +252,7 @@ def _status_to_code(status_code: int, detail: str = "") -> str: def _rate_limit_exceeded_handler(request: Request, exc: RateLimitExceeded): """Custom 429 handler that returns JSON matching our error envelope.""" rid = _get_request_id(request) + ot_metrics.record_rate_limit_rejection(scope="slowapi") retry_after = exc.detail.split("per")[-1].strip() if exc.detail else "60" return _build_error_response( 429, @@ -306,6 +313,7 @@ def _check_rate_limit_memory( f"Rate limit exceeded (in-memory fallback) on {scope} for IP {client_ip} " f"({len(timestamps)}/{max_requests} in {window_seconds}s)" ) + ot_metrics.record_rate_limit_rejection(scope=scope) raise HTTPException( status_code=429, detail="RATE_LIMIT_EXCEEDED", @@ -349,6 +357,7 @@ def _check_rate_limit( f"Rate limit exceeded on {scope} for IP {client_ip} " f"({current_count}/{max_requests} in {window_seconds}s)" ) + ot_metrics.record_rate_limit_rejection(scope=scope) raise HTTPException( status_code=429, detail="RATE_LIMIT_EXCEEDED", @@ -558,6 +567,7 @@ async def lifespan(app: FastAPI): gc.set_threshold(700, 10, 5) _enable_slow_callback_logging(threshold_sec=0.5) + init_otel(app) await create_db_and_tables() await setup_checkpointer_tables() initialize_openrouter_integration() @@ -592,6 +602,7 @@ async def lifespan(app: FastAPI): _stop_openrouter_background_refresh() await close_checkpointer() + shutdown_otel() def registration_allowed(): @@ -676,32 +687,20 @@ class RequestPerfMiddleware(BaseHTTPMiddleware): async def dispatch( self, request: StarletteRequest, call_next: RequestResponseEndpoint ) -> StarletteResponse: - perf = get_perf_logger() t0 = time.perf_counter() response = await call_next(request) elapsed_ms = (time.perf_counter() - t0) * 1000 path = request.url.path - method = request.method - status = response.status_code - - perf.debug( - "[request] %s %s -> %d in %.1fms", - method, - path, - status, - elapsed_ms, - ) if elapsed_ms > _PERF_SLOW_REQUEST_THRESHOLD: - perf.warning( - "[SLOW_REQUEST] %s %s -> %d in %.1fms (threshold=%.0fms)", - method, - path, - status, - elapsed_ms, - _PERF_SLOW_REQUEST_THRESHOLD, - ) + with contextlib.suppress(Exception): + from opentelemetry import trace + + span = trace.get_current_span() + span.set_attribute("slow_request", True) + span.set_attribute("surfsense.request.elapsed_ms", elapsed_ms) + span.set_attribute("http.route", path) log_system_snapshot("slow_request") return response diff --git a/surfsense_backend/app/celery_app.py b/surfsense_backend/app/celery_app.py index 74710d5e1..cfb24731d 100644 --- a/surfsense_backend/app/celery_app.py +++ b/surfsense_backend/app/celery_app.py @@ -18,6 +18,10 @@ def init_worker(**kwargs): This ensures the Auto mode (LiteLLM Router) is available for background tasks like document summarization and image generation. """ + from app.observability.bootstrap import init_otel + + init_otel(app=None, traces=True, metrics=True, logs=True) + from app.config import ( initialize_image_gen_router, initialize_llm_router, diff --git a/surfsense_backend/app/observability/__init__.py b/surfsense_backend/app/observability/__init__.py index dbf082561..a675b1dae 100644 --- a/surfsense_backend/app/observability/__init__.py +++ b/surfsense_backend/app/observability/__init__.py @@ -5,3 +5,5 @@ small wrapper around the optional ``opentelemetry`` instrumentation. The wrapper is a no-op when OTEL is not configured, so importing it from performance-critical paths is safe. """ + +__all__ = ["bootstrap", "metrics", "otel"] diff --git a/surfsense_backend/app/observability/bootstrap.py b/surfsense_backend/app/observability/bootstrap.py new file mode 100644 index 000000000..a1b9817da --- /dev/null +++ b/surfsense_backend/app/observability/bootstrap.py @@ -0,0 +1,361 @@ +"""Programmatic OpenTelemetry bootstrap for SurfSense backend processes.""" + +from __future__ import annotations + +import contextlib +import logging +import os +import socket +from importlib import metadata +from typing import Any + +from app.observability import otel + +logger = logging.getLogger(__name__) + +_BOOL_TRUE = {"1", "true", "yes", "on"} + +_TRACES_INITIALIZED = False +_METRICS_INITIALIZED = False +_LOGS_INITIALIZED = False +_FASTAPI_INSTRUMENTED = False +_SQLALCHEMY_INSTRUMENTED = False +_PSYCOPG_INSTRUMENTED = False +_REDIS_INSTRUMENTED = False +_HTTPX_INSTRUMENTED = False +_CELERY_INSTRUMENTED = False + +_TRACER_PROVIDER: Any | None = None +_METER_PROVIDER: Any | None = None + + +def _env_truthy(name: str) -> bool: + return os.environ.get(name, "").strip().lower() in _BOOL_TRUE + + +def is_otel_disabled() -> bool: + """Return true when either SurfSense or OTel's spec kill switch is set.""" + return _env_truthy("SURFSENSE_DISABLE_OTEL") or _env_truthy("OTEL_SDK_DISABLED") + + +def is_otel_configured() -> bool: + """Return true when this process should export OTel signals.""" + return bool( + os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT") + or os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT") + or os.environ.get("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT") + ) + + +def _package_version() -> str: + with contextlib.suppress(metadata.PackageNotFoundError): + return metadata.version("surf-new-backend") + return "unknown" + + +def _deployment_environment() -> str: + return ( + os.environ.get("SURFSENSE_ENV") + or os.environ.get("APP_ENV") + or os.environ.get("ENVIRONMENT") + or "dev" + ) + + +def _build_resource(): + from opentelemetry.sdk.resources import Resource + + return Resource.create( + { + "service.name": os.environ.get("OTEL_SERVICE_NAME", "surfsense-backend"), + "service.version": _package_version(), + "service.instance.id": socket.gethostname(), + "deployment.environment": _deployment_environment(), + } + ) + + +def _otlp_protocol() -> str: + return os.environ.get("OTEL_EXPORTER_OTLP_PROTOCOL", "grpc").strip().lower() + + +def _trace_exporter(): + if _otlp_protocol() == "http/protobuf": + from opentelemetry.exporter.otlp.proto.http.trace_exporter import ( + OTLPSpanExporter, + ) + + endpoint = os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT") + return OTLPSpanExporter(endpoint=endpoint) if endpoint else OTLPSpanExporter() + + from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter + + endpoint = os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT") + return OTLPSpanExporter(endpoint=endpoint) if endpoint else OTLPSpanExporter() + + +def _metric_exporter(): + if _otlp_protocol() == "http/protobuf": + from opentelemetry.exporter.otlp.proto.http.metric_exporter import ( + OTLPMetricExporter, + ) + + endpoint = os.environ.get("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT") + return ( + OTLPMetricExporter(endpoint=endpoint) if endpoint else OTLPMetricExporter() + ) + + from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import ( + OTLPMetricExporter, + ) + + endpoint = os.environ.get("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT") + return OTLPMetricExporter(endpoint=endpoint) if endpoint else OTLPMetricExporter() + + +def _safe_instrument(name: str, instrument: Any) -> bool: + try: + instrument() + except Exception: + logger.warning("OpenTelemetry %s instrumentation failed", name, exc_info=True) + return False + return True + + +def _instrument_fastapi(app: Any | None) -> None: + global _FASTAPI_INSTRUMENTED + if app is None or _FASTAPI_INSTRUMENTED: + return + + def _run() -> None: + from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor + + FastAPIInstrumentor.instrument_app( + app, + excluded_urls="/health,/ready,/metrics", + ) + + if _safe_instrument("FastAPI", _run): + _FASTAPI_INSTRUMENTED = True + + +def instrument_sqlalchemy_engine(engine: Any) -> None: + """Instrument a SQLAlchemy engine once per process.""" + global _SQLALCHEMY_INSTRUMENTED + if _SQLALCHEMY_INSTRUMENTED: + return + + def _run() -> None: + from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor + + SQLAlchemyInstrumentor().instrument( + engine=getattr(engine, "sync_engine", engine), + enable_commenter=True, + ) + + if _safe_instrument("SQLAlchemy", _run): + _SQLALCHEMY_INSTRUMENTED = True + + +def _instrument_sqlalchemy() -> None: + if _SQLALCHEMY_INSTRUMENTED: + return + with contextlib.suppress(Exception): + from app.db import engine + + instrument_sqlalchemy_engine(engine) + + +def _instrument_psycopg() -> None: + global _PSYCOPG_INSTRUMENTED + if _PSYCOPG_INSTRUMENTED: + return + + def _run() -> None: + from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor + + PsycopgInstrumentor().instrument() + + if _safe_instrument("psycopg", _run): + _PSYCOPG_INSTRUMENTED = True + + +def _instrument_redis() -> None: + global _REDIS_INSTRUMENTED + if _REDIS_INSTRUMENTED: + return + + def _run() -> None: + from opentelemetry.instrumentation.redis import RedisInstrumentor + + RedisInstrumentor().instrument() + + if _safe_instrument("Redis", _run): + _REDIS_INSTRUMENTED = True + + +def _instrument_httpx() -> None: + global _HTTPX_INSTRUMENTED + if _HTTPX_INSTRUMENTED: + return + + def _run() -> None: + from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor + + HTTPXClientInstrumentor().instrument() + + if _safe_instrument("HTTPX", _run): + _HTTPX_INSTRUMENTED = True + + +def instrument_celery() -> None: + """Instrument Celery producer/consumer hooks once per process.""" + global _CELERY_INSTRUMENTED + if _CELERY_INSTRUMENTED: + return + + def _run() -> None: + from opentelemetry.instrumentation.celery import CeleryInstrumentor + + CeleryInstrumentor().instrument() + + if _safe_instrument("Celery", _run): + _CELERY_INSTRUMENTED = True + + +def _instrument_libraries(app: Any | None) -> None: + _instrument_fastapi(app) + _instrument_sqlalchemy() + _instrument_psycopg() + _instrument_redis() + _instrument_httpx() + instrument_celery() + + +def init_traces(app: Any | None = None) -> None: + """Install the tracer provider, span processor, exporter, and instrumentors.""" + global _TRACER_PROVIDER, _TRACES_INITIALIZED + if _TRACES_INITIALIZED: + _instrument_fastapi(app) + return + + from opentelemetry import trace + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import BatchSpanProcessor + from opentelemetry.sdk.trace.sampling import ALWAYS_ON, ParentBased + + provider = TracerProvider( + resource=_build_resource(), + sampler=ParentBased(ALWAYS_ON), + ) + provider.add_span_processor(BatchSpanProcessor(_trace_exporter())) + + try: + trace.set_tracer_provider(provider) + except Exception: + logger.warning( + "OpenTelemetry tracer provider was already set; reusing existing provider", + exc_info=True, + ) + _TRACER_PROVIDER = trace.get_tracer_provider() + else: + _TRACER_PROVIDER = provider + + _TRACES_INITIALIZED = True + otel.reload_for_tests() + _instrument_libraries(app) + + +def init_metrics() -> None: + """Install the meter provider, metric reader, exporter, and custom gauges.""" + global _METER_PROVIDER, _METRICS_INITIALIZED + if _METRICS_INITIALIZED: + return + + from opentelemetry import metrics + from opentelemetry.sdk.metrics import MeterProvider + from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader + + interval_ms = int(os.environ.get("OTEL_METRIC_EXPORT_INTERVAL", "60000")) + reader = PeriodicExportingMetricReader( + _metric_exporter(), + export_interval_millis=interval_ms, + ) + provider = MeterProvider(metric_readers=[reader], resource=_build_resource()) + + try: + metrics.set_meter_provider(provider) + except Exception: + logger.warning( + "OpenTelemetry meter provider was already set; reusing existing provider", + exc_info=True, + ) + _METER_PROVIDER = metrics.get_meter_provider() + else: + _METER_PROVIDER = provider + + _METRICS_INITIALIZED = True + from app.observability.metrics import register_runtime_observables + + register_runtime_observables() + + +def init_logs() -> None: + """Enable trace/span correlation fields on stdlib LogRecords.""" + global _LOGS_INITIALIZED + if _LOGS_INITIALIZED: + return + + def _run() -> None: + from opentelemetry.instrumentation.logging import LoggingInstrumentor + + LoggingInstrumentor().instrument() + + if _safe_instrument("logging", _run): + _LOGS_INITIALIZED = True + + +def init_otel( + app: Any | None = None, + *, + traces: bool = True, + metrics: bool = True, + logs: bool = True, +) -> None: + """Initialize OpenTelemetry for a FastAPI or Celery process.""" + if is_otel_disabled() or not is_otel_configured(): + otel.reload_for_tests() + return + + if traces: + init_traces(app) + if metrics: + init_metrics() + if logs: + init_logs() + + +def shutdown_otel(timeout_millis: int = 5000) -> None: + """Best-effort flush and shutdown for installed providers.""" + for provider in (_TRACER_PROVIDER, _METER_PROVIDER): + if provider is None: + continue + with contextlib.suppress(Exception): + provider.force_flush(timeout_millis=timeout_millis) + with contextlib.suppress(Exception): + provider.shutdown() + + +__all__ = [ + "_BOOL_TRUE", + "_build_resource", + "init_logs", + "init_metrics", + "init_otel", + "init_traces", + "instrument_celery", + "instrument_sqlalchemy_engine", + "is_otel_configured", + "is_otel_disabled", + "shutdown_otel", +] diff --git a/surfsense_backend/app/observability/otel.py b/surfsense_backend/app/observability/otel.py index 6791ab499..f39cfe535 100644 --- a/surfsense_backend/app/observability/otel.py +++ b/surfsense_backend/app/observability/otel.py @@ -66,6 +66,8 @@ def _resolve_enabled() -> bool: # Honor an explicit kill-switch first. if os.environ.get("SURFSENSE_DISABLE_OTEL", "").lower() in {"1", "true", "yes"}: return False + if os.environ.get("OTEL_SDK_DISABLED", "").lower() in {"1", "true", "yes", "on"}: + return False # Treat a configured endpoint as the canonical "OTel is wired up" signal. if os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT"): return True @@ -198,8 +200,11 @@ def model_call_span( attrs: dict[str, Any] = {} if model_id: attrs["model.id"] = model_id + attrs["gen_ai.request.model"] = model_id if provider: attrs["model.provider"] = provider + attrs["gen_ai.provider.name"] = provider + attrs["gen_ai.operation.name"] = "chat" if extra: attrs.update(extra) return span("model.call", attributes=attrs) diff --git a/surfsense_backend/app/tasks/celery_tasks/__init__.py b/surfsense_backend/app/tasks/celery_tasks/__init__.py index b23359f36..6ea7a2e68 100644 --- a/surfsense_backend/app/tasks/celery_tasks/__init__.py +++ b/surfsense_backend/app/tasks/celery_tasks/__init__.py @@ -37,6 +37,10 @@ def get_celery_session_maker() -> async_sessionmaker: poolclass=NullPool, echo=False, ) + with contextlib.suppress(Exception): + from app.observability.bootstrap import instrument_sqlalchemy_engine + + instrument_sqlalchemy_engine(_celery_engine) _celery_session_maker = async_sessionmaker( _celery_engine, expire_on_commit=False ) diff --git a/surfsense_backend/main.py b/surfsense_backend/main.py index 4a7a9b7b1..54911a34d 100644 --- a/surfsense_backend/main.py +++ b/surfsense_backend/main.py @@ -12,9 +12,26 @@ if sys.platform == "win32": from app.config.uvicorn import load_uvicorn_config +_old_log_record_factory = logging.getLogRecordFactory() + + +def _otel_safe_log_record_factory(*args, **kwargs): + record = _old_log_record_factory(*args, **kwargs) + if not hasattr(record, "otelTraceID"): + record.otelTraceID = "0" + if not hasattr(record, "otelSpanID"): + record.otelSpanID = "0" + return record + + +logging.setLogRecordFactory(_otel_safe_log_record_factory) + logging.basicConfig( level=logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + format=( + "%(asctime)s - %(name)s - %(levelname)s - " + "[trace_id=%(otelTraceID)s span_id=%(otelSpanID)s] %(message)s" + ), datefmt="%Y-%m-%d %H:%M:%S", ) From 6095b48b5f06707f53876c76b801945f6644c5c5 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Thu, 21 May 2026 23:02:20 +0530 Subject: [PATCH 007/198] feat(observability): add SurfSense metric helpers --- .../app/observability/metrics.py | 416 ++++++++++++++++++ surfsense_backend/app/utils/perf.py | 4 + .../tests/unit/observability/test_otel.py | 163 ++++++- 3 files changed, 581 insertions(+), 2 deletions(-) create mode 100644 surfsense_backend/app/observability/metrics.py diff --git a/surfsense_backend/app/observability/metrics.py b/surfsense_backend/app/observability/metrics.py new file mode 100644 index 000000000..fb99b4cc8 --- /dev/null +++ b/surfsense_backend/app/observability/metrics.py @@ -0,0 +1,416 @@ +"""Custom OpenTelemetry metrics for SurfSense. + +This module owns all SurfSense-specific metric instruments. Callers use the +small helper functions below instead of constructing instruments directly so +attribute names and cardinality stay consistent across the backend. +""" + +from __future__ import annotations + +import contextlib +import gc +import logging +from functools import lru_cache +from importlib import metadata +from typing import Any + +from app.observability import otel + +logger = logging.getLogger(__name__) + +_INSTRUMENTATION_NAME = "surfsense.platform" +_OBSERVABLES_REGISTERED = False + + +def _package_version() -> str: + with contextlib.suppress(metadata.PackageNotFoundError): + return metadata.version("surf-new-backend") + return "unknown" + + +def _is_enabled() -> bool: + return otel.is_enabled() + + +def _clean_attrs(attrs: dict[str, Any]) -> dict[str, str | int | float | bool]: + """Drop empty values and coerce low-cardinality attrs to OTel-safe scalars.""" + cleaned: dict[str, str | int | float | bool] = {} + for key, value in attrs.items(): + if value is None: + continue + if isinstance(value, bool | int | float): + cleaned[key] = value + continue + text = str(value) + if text: + cleaned[key] = text + return cleaned + + +def _record(callable_obj: Any, value: int | float, attrs: dict[str, Any]) -> None: + if not _is_enabled(): + return + with contextlib.suppress(Exception): + callable_obj.record(value, _clean_attrs(attrs)) + + +def _add(callable_obj: Any, value: int, attrs: dict[str, Any]) -> None: + if not _is_enabled(): + return + with contextlib.suppress(Exception): + callable_obj.add(value, _clean_attrs(attrs)) + + +@lru_cache(maxsize=1) +def _get_meter(): + from opentelemetry import metrics + + return metrics.get_meter(_INSTRUMENTATION_NAME, _package_version()) + + +@lru_cache(maxsize=1) +def _model_call_duration(): + return _get_meter().create_histogram( + "surfsense.model.call.duration", + unit="ms", + description="Duration of SurfSense LLM model calls.", + ) + + +@lru_cache(maxsize=1) +def _model_token_usage(): + return _get_meter().create_histogram( + "gen_ai.client.token.usage", + unit="{token}", + description="Token usage reported by GenAI model responses.", + ) + + +@lru_cache(maxsize=1) +def _tool_call_duration(): + return _get_meter().create_histogram( + "surfsense.tool.call.duration", + unit="ms", + description="Duration of SurfSense agent tool calls.", + ) + + +@lru_cache(maxsize=1) +def _tool_call_errors(): + return _get_meter().create_counter( + "surfsense.tool.call.errors", + description="Count of SurfSense agent tool call errors.", + ) + + +@lru_cache(maxsize=1) +def _kb_search_duration(): + return _get_meter().create_histogram( + "surfsense.kb.search.duration", + unit="ms", + description="Duration of SurfSense knowledge-base search calls.", + ) + + +@lru_cache(maxsize=1) +def _compaction_runs(): + return _get_meter().create_counter( + "surfsense.compaction.runs", + description="Count of SurfSense conversation compaction runs.", + ) + + +@lru_cache(maxsize=1) +def _permission_asks(): + return _get_meter().create_counter( + "surfsense.permission.asks", + description="Count of SurfSense permission asks.", + ) + + +@lru_cache(maxsize=1) +def _interrupts(): + return _get_meter().create_counter( + "surfsense.interrupt.raised", + description="Count of SurfSense interrupts raised.", + ) + + +@lru_cache(maxsize=1) +def _indexing_document_duration(): + return _get_meter().create_histogram( + "surfsense.indexing.document.duration", + unit="s", + description="Duration of SurfSense document indexing.", + ) + + +@lru_cache(maxsize=1) +def _indexing_document_outcome(): + return _get_meter().create_counter( + "surfsense.indexing.document.outcome", + description="Count of SurfSense document indexing outcomes.", + ) + + +@lru_cache(maxsize=1) +def _connector_sync_duration(): + return _get_meter().create_histogram( + "surfsense.connector.sync.duration", + unit="s", + description="Duration of SurfSense connector sync tasks.", + ) + + +@lru_cache(maxsize=1) +def _connector_sync_outcome(): + return _get_meter().create_counter( + "surfsense.connector.sync.outcome", + description="Count of SurfSense connector sync outcomes.", + ) + + +@lru_cache(maxsize=1) +def _auth_failures(): + return _get_meter().create_counter( + "surfsense.auth.failures", + description="Count of SurfSense authentication failures.", + ) + + +@lru_cache(maxsize=1) +def _rate_limit_rejections(): + return _get_meter().create_counter( + "surfsense.rate_limit.rejections", + description="Count of SurfSense rate-limit rejections.", + ) + + +@lru_cache(maxsize=1) +def _perf_elapsed(): + return _get_meter().create_histogram( + "surfsense.perf.elapsed_ms", + unit="ms", + description="Elapsed time recorded by SurfSense perf timers.", + ) + + +def record_model_call_duration( + duration_ms: float, *, model: str | None, provider: str | None +) -> None: + _record( + _model_call_duration(), + duration_ms, + { + "gen_ai.request.model": model, + "gen_ai.provider.name": provider, + }, + ) + + +def record_model_token_usage( + *, + input_tokens: int | None, + output_tokens: int | None, + model: str | None, + provider: str | None, +) -> None: + base = { + "gen_ai.request.model": model, + "gen_ai.provider.name": provider, + "gen_ai.operation.name": "chat", + } + if input_tokens is not None: + _record( + _model_token_usage(), + int(input_tokens), + {**base, "gen_ai.token.type": "input"}, + ) + if output_tokens is not None: + _record( + _model_token_usage(), + int(output_tokens), + {**base, "gen_ai.token.type": "output"}, + ) + + +def record_tool_call_duration(duration_ms: float, *, tool_name: str) -> None: + _record(_tool_call_duration(), duration_ms, {"tool.name": tool_name}) + + +def record_tool_call_error(*, tool_name: str) -> None: + _add(_tool_call_errors(), 1, {"tool.name": tool_name}) + + +def record_kb_search_duration( + duration_ms: float, *, search_space_id: int | None, surface: str +) -> None: + _record( + _kb_search_duration(), + duration_ms, + {"search_space.id": search_space_id, "search.surface": surface}, + ) + + +def record_compaction_run(*, reason: str | None) -> None: + _add(_compaction_runs(), 1, {"compaction.reason": reason or "unknown"}) + + +def record_permission_ask(*, permission: str) -> None: + _add(_permission_asks(), 1, {"permission.permission": permission}) + + +def record_interrupt(*, interrupt_type: str) -> None: + _add(_interrupts(), 1, {"interrupt.type": interrupt_type}) + + +def record_indexing_document_duration( + duration_s: float, *, document_type: str | None +) -> None: + _record( + _indexing_document_duration(), + duration_s, + {"document.type": document_type or "unknown"}, + ) + + +def record_indexing_document_outcome(*, document_type: str | None, status: str) -> None: + _add( + _indexing_document_outcome(), + 1, + {"document.type": document_type or "unknown", "status": status}, + ) + + +def record_connector_sync_duration( + duration_s: float, *, connector_type: str | None +) -> None: + _record( + _connector_sync_duration(), + duration_s, + {"connector.type": connector_type or "unknown"}, + ) + + +def record_connector_sync_outcome(*, connector_type: str | None, status: str) -> None: + _add( + _connector_sync_outcome(), + 1, + {"connector.type": connector_type or "unknown", "status": status}, + ) + + +def record_auth_failure(*, reason: str) -> None: + _add(_auth_failures(), 1, {"reason": reason}) + + +def record_rate_limit_rejection(*, scope: str) -> None: + _add(_rate_limit_rejections(), 1, {"scope": scope}) + + +def record_perf_elapsed(duration_ms: float, *, label: str) -> None: + _record(_perf_elapsed(), duration_ms, {"label": label}) + + +def _runtime_snapshot_value(key: str, transform: Any = None) -> list[Any]: + from opentelemetry.metrics import Observation + + from app.utils.perf import system_snapshot + + snap = system_snapshot() + value = snap.get(key) + if not isinstance(value, int | float) or value < 0: + return [] + if transform is not None: + value = transform(value) + return [Observation(value)] + + +def _observe_gc_collections(_options: Any) -> list[Any]: + from opentelemetry.metrics import Observation + + return [ + Observation(count, {"generation": str(generation)}) + for generation, count in enumerate(gc.get_count()) + ] + + +def register_runtime_observables() -> None: + """Register process/runtime observable gauges once per process.""" + global _OBSERVABLES_REGISTERED + if _OBSERVABLES_REGISTERED or not _is_enabled(): + return + + meter = _get_meter() + try: + # Each callback returns the value for a single gauge except GC, whose + # callback carries a generation attribute. + meter.create_observable_gauge( + "process.runtime.cpython.memory.rss", + callbacks=[ + lambda _options: _runtime_snapshot_value( + "rss_mb", lambda v: float(v) * 1024 * 1024 + ) + ], + unit="By", + description="Resident set size of the SurfSense backend process.", + ) + meter.create_observable_gauge( + "process.runtime.cpython.cpu.utilization", + callbacks=[ + lambda _options: _runtime_snapshot_value( + "cpu_percent", lambda v: float(v) / 100.0 + ) + ], + unit="1", + description="CPU utilization of the SurfSense backend process.", + ) + meter.create_observable_gauge( + "process.runtime.cpython.threads", + callbacks=[lambda _options: _runtime_snapshot_value("threads")], + unit="{thread}", + description="Thread count of the SurfSense backend process.", + ) + meter.create_observable_gauge( + "process.runtime.cpython.open_fds", + callbacks=[lambda _options: _runtime_snapshot_value("open_fds")], + unit="{fd}", + description="Open file descriptor count of the SurfSense backend process.", + ) + meter.create_observable_gauge( + "python.asyncio.tasks", + callbacks=[lambda _options: _runtime_snapshot_value("asyncio_tasks")], + unit="{task}", + description="Live asyncio task count in the current event loop.", + ) + meter.create_observable_gauge( + "process.runtime.cpython.gc.collections", + callbacks=[_observe_gc_collections], + unit="{collection}", + description="CPython GC counters by generation.", + ) + except Exception: + logger.warning("Failed to register OTel runtime observables", exc_info=True) + return + + _OBSERVABLES_REGISTERED = True + + +__all__ = [ + "record_auth_failure", + "record_compaction_run", + "record_connector_sync_duration", + "record_connector_sync_outcome", + "record_indexing_document_duration", + "record_indexing_document_outcome", + "record_interrupt", + "record_kb_search_duration", + "record_model_call_duration", + "record_model_token_usage", + "record_perf_elapsed", + "record_permission_ask", + "record_rate_limit_rejection", + "record_tool_call_duration", + "record_tool_call_error", + "register_runtime_observables", +] diff --git a/surfsense_backend/app/utils/perf.py b/surfsense_backend/app/utils/perf.py index b2b26897c..541ee5756 100644 --- a/surfsense_backend/app/utils/perf.py +++ b/surfsense_backend/app/utils/perf.py @@ -16,6 +16,8 @@ import time from contextlib import asynccontextmanager, contextmanager from typing import Any +from app.observability import metrics as ot_metrics + _perf_log: logging.Logger | None = None _last_rss_mb: float = 0.0 @@ -50,6 +52,7 @@ def perf_timer(label: str, *, extra: dict[str, Any] | None = None): if extra: suffix = " " + " ".join(f"{k}={v}" for k, v in extra.items()) log.info("%s in %.3fs%s", label, elapsed, suffix) + ot_metrics.record_perf_elapsed(elapsed * 1000, label=label) @asynccontextmanager @@ -68,6 +71,7 @@ async def perf_async_timer(label: str, *, extra: dict[str, Any] | None = None): if extra: suffix = " " + " ".join(f"{k}={v}" for k, v in extra.items()) log.info("%s in %.3fs%s", label, elapsed, suffix) + ot_metrics.record_perf_elapsed(elapsed * 1000, label=label) def system_snapshot() -> dict[str, Any]: diff --git a/surfsense_backend/tests/unit/observability/test_otel.py b/surfsense_backend/tests/unit/observability/test_otel.py index fc5813973..be40cccde 100644 --- a/surfsense_backend/tests/unit/observability/test_otel.py +++ b/surfsense_backend/tests/unit/observability/test_otel.py @@ -4,7 +4,7 @@ from __future__ import annotations import pytest -from app.observability import otel +from app.observability import bootstrap, metrics, otel pytestmark = pytest.mark.unit @@ -12,7 +12,14 @@ pytestmark = pytest.mark.unit @pytest.fixture(autouse=True) def _reset_otel_state(monkeypatch: pytest.MonkeyPatch): """Force a clean OTel disabled state per test, then restore after.""" - for env in ("OTEL_EXPORTER_OTLP_ENDPOINT", "SURFSENSE_DISABLE_OTEL"): + for env in ( + "OTEL_EXPORTER_OTLP_ENDPOINT", + "OTEL_EXPORTER_OTLP_PROTOCOL", + "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", + "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", + "SURFSENSE_DISABLE_OTEL", + "OTEL_SDK_DISABLED", + ): monkeypatch.delenv(env, raising=False) monkeypatch.setenv("SURFSENSE_DISABLE_OTEL", "true") otel.reload_for_tests() @@ -36,6 +43,158 @@ def test_kill_switch_overrides_endpoint(monkeypatch: pytest.MonkeyPatch) -> None assert otel.reload_for_tests() is False +def test_spec_kill_switch_overrides_endpoint(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("SURFSENSE_DISABLE_OTEL", raising=False) + monkeypatch.setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317") + monkeypatch.setenv("OTEL_SDK_DISABLED", "true") + assert otel.reload_for_tests() is False + + +class TestBootstrapConfig: + def test_disabled_checks_both_kill_switches( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.delenv("SURFSENSE_DISABLE_OTEL", raising=False) + monkeypatch.delenv("OTEL_SDK_DISABLED", raising=False) + assert bootstrap.is_otel_disabled() is False + + monkeypatch.setenv("OTEL_SDK_DISABLED", "on") + assert bootstrap.is_otel_disabled() is True + + def test_configured_by_shared_or_signal_endpoint( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.delenv("SURFSENSE_DISABLE_OTEL", raising=False) + assert bootstrap.is_otel_configured() is False + + monkeypatch.setenv( + "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "http://localhost:4317" + ) + assert bootstrap.is_otel_configured() is True + + def test_init_otel_noops_when_disabled( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + called = {"traces": False} + + def fake_init_traces(app=None): + del app + called["traces"] = True + + monkeypatch.setenv("SURFSENSE_DISABLE_OTEL", "true") + monkeypatch.setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317") + monkeypatch.setattr(bootstrap, "init_traces", fake_init_traces) + + bootstrap.init_otel() + assert called["traces"] is False + + def test_init_otel_dispatches_enabled_signals( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + called: list[str] = [] + + monkeypatch.delenv("SURFSENSE_DISABLE_OTEL", raising=False) + monkeypatch.setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317") + monkeypatch.setattr( + bootstrap, "init_traces", lambda app=None: called.append("traces") + ) + monkeypatch.setattr(bootstrap, "init_metrics", lambda: called.append("metrics")) + monkeypatch.setattr(bootstrap, "init_logs", lambda: called.append("logs")) + + bootstrap.init_otel() + assert called == ["traces", "metrics", "logs"] + + def test_resource_defaults_include_service_metadata( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.setenv("OTEL_SERVICE_NAME", "custom-backend") + monkeypatch.setenv("SURFSENSE_ENV", "test") + + resource = bootstrap._build_resource() + attrs = dict(resource.attributes) + assert attrs["service.name"] == "custom-backend" + assert attrs["deployment.environment"] == "test" + assert attrs["service.instance.id"] + + def test_shutdown_is_safe_without_providers(self) -> None: + bootstrap.shutdown_otel() + + +class TestMetricHelpers: + def test_all_metric_helpers_noop_safely_when_disabled(self) -> None: + metrics.record_model_call_duration(12.5, model="gpt-4o", provider="openai") + metrics.record_model_token_usage( + input_tokens=10, + output_tokens=5, + model="gpt-4o", + provider="openai", + ) + metrics.record_tool_call_duration(3.0, tool_name="web_search") + metrics.record_tool_call_error(tool_name="web_search") + metrics.record_kb_search_duration( + 4.0, + search_space_id=1, + surface="documents", + ) + metrics.record_compaction_run(reason="auto") + metrics.record_permission_ask(permission="write_file") + metrics.record_interrupt(interrupt_type="permission_ask") + metrics.record_indexing_document_duration(1.2, document_type="FILE") + metrics.record_indexing_document_outcome(document_type="FILE", status="success") + metrics.record_connector_sync_duration( + 2.3, + connector_type="index_notion_pages", + ) + metrics.record_connector_sync_outcome( + connector_type="index_notion_pages", + status="success", + ) + metrics.record_auth_failure(reason="UNAUTHORIZED") + metrics.record_rate_limit_rejection(scope="login") + metrics.record_perf_elapsed(7.0, label="[test]") + + def test_runtime_observables_register_once( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + class FakeMeter: + def __init__(self) -> None: + self.names: list[str] = [] + + def create_observable_gauge(self, name: str, **kwargs) -> None: + del kwargs + self.names.append(name) + + fake_meter = FakeMeter() + monkeypatch.setattr(metrics, "_OBSERVABLES_REGISTERED", False) + monkeypatch.setattr(metrics, "_is_enabled", lambda: True) + monkeypatch.setattr(metrics, "_get_meter", lambda: fake_meter) + + metrics.register_runtime_observables() + metrics.register_runtime_observables() + + assert len(fake_meter.names) == 6 + assert fake_meter.names.count("python.asyncio.tasks") == 1 + monkeypatch.setattr(metrics, "_OBSERVABLES_REGISTERED", False) + + +def test_log_record_factory_provides_zero_otel_fields() -> None: + import logging + + import main # noqa: F401 + + record = logging.getLogRecordFactory()( + "test", + logging.INFO, + __file__, + 1, + "hello", + (), + None, + ) + assert record.otelTraceID == "0" + assert record.otelSpanID == "0" + + class TestNoopSpansWhenDisabled: def test_generic_span_yields_noop(self) -> None: with otel.span("any.thing", attributes={"x": 1}) as sp: From ea3d0a64638c1bac1e3c6585fb2d53b7e6e17373 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Thu, 21 May 2026 23:02:36 +0530 Subject: [PATCH 008/198] feat(agents): emit metrics for model and tool calls --- .../agents/new_chat/middleware/otel_span.py | 103 +++++++++++++++-- .../unit/agents/new_chat/test_otel_span.py | 104 ++++++++++++++++-- 2 files changed, 190 insertions(+), 17 deletions(-) diff --git a/surfsense_backend/app/agents/new_chat/middleware/otel_span.py b/surfsense_backend/app/agents/new_chat/middleware/otel_span.py index cfe1edae4..ecaa042a9 100644 --- a/surfsense_backend/app/agents/new_chat/middleware/otel_span.py +++ b/surfsense_backend/app/agents/new_chat/middleware/otel_span.py @@ -16,13 +16,14 @@ dashboards expect. from __future__ import annotations import logging +import time from collections.abc import Awaitable, Callable from typing import TYPE_CHECKING, Any from langchain.agents.middleware import AgentMiddleware from langchain_core.messages import AIMessage, ToolMessage -from app.observability import otel as ot +from app.observability import metrics as ot_metrics, otel as ot if TYPE_CHECKING: # pragma: no cover — type-only from langchain.agents.middleware.types import ( @@ -62,14 +63,37 @@ class OtelSpanMiddleware(AgentMiddleware): return await handler(request) model_id, provider = _resolve_model_attrs(request) + t0 = time.perf_counter() with ot.model_call_span(model_id=model_id, provider=provider) as sp: + _annotate_model_request(sp, model_id=model_id, provider=provider) try: result = await handler(request) except Exception: + ot_metrics.record_model_call_duration( + (time.perf_counter() - t0) * 1000, + model=model_id, + provider=provider, + ) # span context manager records + re-raises raise else: - _annotate_model_response(sp, result) + input_tokens, output_tokens = _annotate_model_response( + sp, + result, + model_id=model_id, + provider=provider, + ) + ot_metrics.record_model_call_duration( + (time.perf_counter() - t0) * 1000, + model=model_id, + provider=provider, + ) + ot_metrics.record_model_token_usage( + input_tokens=input_tokens, + output_tokens=output_tokens, + model=model_id, + provider=provider, + ) return result # ------------------------------------------------------------------ @@ -87,9 +111,24 @@ class OtelSpanMiddleware(AgentMiddleware): tool_name = _resolve_tool_name(request) input_size = _resolve_input_size(request) + t0 = time.perf_counter() with ot.tool_call_span(tool_name, input_size=input_size) as sp: - result = await handler(request) - _annotate_tool_result(sp, result) + try: + result = await handler(request) + except Exception: + ot_metrics.record_tool_call_duration( + (time.perf_counter() - t0) * 1000, + tool_name=tool_name, + ) + ot_metrics.record_tool_call_error(tool_name=tool_name) + raise + errored = _annotate_tool_result(sp, result) + ot_metrics.record_tool_call_duration( + (time.perf_counter() - t0) * 1000, + tool_name=tool_name, + ) + if errored: + ot_metrics.record_tool_call_error(tool_name=tool_name) return result @@ -154,8 +193,29 @@ def _resolve_input_size(request: Any) -> int | None: return None -def _annotate_model_response(span: Any, result: Any) -> None: +def _annotate_model_request( + span: Any, *, model_id: str | None, provider: str | None +) -> None: + try: + span.set_attribute("gen_ai.operation.name", "chat") + if model_id: + span.set_attribute("gen_ai.request.model", model_id) + if provider: + span.set_attribute("gen_ai.provider.name", provider) + except Exception: # pragma: no cover — defensive + pass + + +def _annotate_model_response( + span: Any, + result: Any, + *, + model_id: str | None = None, + provider: str | None = None, +) -> tuple[int | None, int | None]: """Best-effort: attach prompt/completion token counts when available.""" + input_tokens: int | None = None + output_tokens: int | None = None try: # ModelResponse may be a dataclass with .result containing AIMessage msg: Any @@ -165,22 +225,42 @@ def _annotate_model_response(span: Any, result: Any) -> None: inner = getattr(result, "result", None) msg = inner[-1] if isinstance(inner, list) and inner else inner if msg is None: - return + return None, None + if provider: + span.set_attribute("gen_ai.provider.name", provider) + if model_id: + span.set_attribute("gen_ai.request.model", model_id) + response_model = getattr(msg, "response_metadata", {}) or {} + if isinstance(response_model, dict): + response_model = ( + response_model.get("model_name") + or response_model.get("model") + or response_model.get("model_id") + ) + if not response_model: + response_model = model_id + if response_model: + span.set_attribute("gen_ai.response.model", str(response_model)) + span.set_attribute("gen_ai.operation.name", "chat") usage = getattr(msg, "usage_metadata", None) or {} if isinstance(usage, dict): if (n := usage.get("input_tokens")) is not None: - span.set_attribute("tokens.prompt", int(n)) + input_tokens = int(n) + span.set_attribute("gen_ai.usage.input_tokens", input_tokens) if (n := usage.get("output_tokens")) is not None: - span.set_attribute("tokens.completion", int(n)) + output_tokens = int(n) + span.set_attribute("gen_ai.usage.output_tokens", output_tokens) if (n := usage.get("total_tokens")) is not None: - span.set_attribute("tokens.total", int(n)) + span.set_attribute("gen_ai.usage.total_tokens", int(n)) tool_calls = getattr(msg, "tool_calls", None) or [] span.set_attribute("model.tool_calls", len(tool_calls)) except Exception: # pragma: no cover — defensive pass + return input_tokens, output_tokens -def _annotate_tool_result(span: Any, result: Any) -> None: +def _annotate_tool_result(span: Any, result: Any) -> bool: + errored = False try: if isinstance(result, ToolMessage): content = ( @@ -192,11 +272,14 @@ def _annotate_tool_result(span: Any, result: Any) -> None: status = getattr(result, "status", None) if isinstance(status, str): span.set_attribute("tool.status", status) + errored = status.lower() == "error" kwargs = getattr(result, "additional_kwargs", None) or {} if isinstance(kwargs, dict) and kwargs.get("error"): span.set_attribute("tool.error", True) + errored = True except Exception: # pragma: no cover — defensive pass + return errored __all__ = ["OtelSpanMiddleware"] diff --git a/surfsense_backend/tests/unit/agents/new_chat/test_otel_span.py b/surfsense_backend/tests/unit/agents/new_chat/test_otel_span.py index 55434c04d..dc59c6dac 100644 --- a/surfsense_backend/tests/unit/agents/new_chat/test_otel_span.py +++ b/surfsense_backend/tests/unit/agents/new_chat/test_otel_span.py @@ -23,6 +23,7 @@ pytestmark = pytest.mark.unit @pytest.fixture(autouse=True) def _disable_otel(monkeypatch: pytest.MonkeyPatch): monkeypatch.delenv("OTEL_EXPORTER_OTLP_ENDPOINT", raising=False) + monkeypatch.delenv("OTEL_SDK_DISABLED", raising=False) monkeypatch.setenv("SURFSENSE_DISABLE_OTEL", "true") from app.observability import otel as ot @@ -99,16 +100,17 @@ class TestAnnotateModelResponse: "total_tokens": 150, }, ) - _annotate_model_response(sp, msg) - sp.set_attribute.assert_any_call("tokens.prompt", 100) - sp.set_attribute.assert_any_call("tokens.completion", 50) - sp.set_attribute.assert_any_call("tokens.total", 150) + assert _annotate_model_response(sp, msg) == (100, 50) + sp.set_attribute.assert_any_call("gen_ai.usage.input_tokens", 100) + sp.set_attribute.assert_any_call("gen_ai.usage.output_tokens", 50) + sp.set_attribute.assert_any_call("gen_ai.usage.total_tokens", 150) + sp.set_attribute.assert_any_call("gen_ai.operation.name", "chat") def test_handles_response_with_no_metadata(self) -> None: sp = MagicMock() msg = AIMessage(content="hello") # Should not raise even when usage_metadata is missing - _annotate_model_response(sp, msg) + assert _annotate_model_response(sp, msg) == (None, None) class TestAnnotateToolResult: @@ -119,7 +121,7 @@ class TestAnnotateToolResult: tool_call_id="abc", status="success", ) - _annotate_tool_result(sp, result) + assert _annotate_tool_result(sp, result) is False sp.set_attribute.assert_any_call("tool.output.size", len("result text")) sp.set_attribute.assert_any_call("tool.status", "success") @@ -130,7 +132,7 @@ class TestAnnotateToolResult: tool_call_id="abc", additional_kwargs={"error": {"code": "x"}}, ) - _annotate_tool_result(sp, result) + assert _annotate_tool_result(sp, result) is True sp.set_attribute.assert_any_call("tool.error", True) @@ -193,3 +195,91 @@ class TestMiddlewareIntegration: assert result.content == "enabled" finally: ot.reload_for_tests() + + async def test_enabled_model_call_records_metrics( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.delenv("SURFSENSE_DISABLE_OTEL", raising=False) + monkeypatch.setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317") + from app.observability import otel as ot + + duration_calls: list[dict[str, Any]] = [] + token_calls: list[dict[str, Any]] = [] + monkeypatch.setattr( + "app.agents.new_chat.middleware.otel_span.ot_metrics.record_model_call_duration", + lambda duration_ms, **attrs: duration_calls.append( + {"duration_ms": duration_ms, **attrs} + ), + ) + monkeypatch.setattr( + "app.agents.new_chat.middleware.otel_span.ot_metrics.record_model_token_usage", + lambda **attrs: token_calls.append(attrs), + ) + + ot.reload_for_tests() + try: + mw = OtelSpanMiddleware() + + async def handler(req): + return AIMessage( + content="enabled", + usage_metadata={ + "input_tokens": 3, + "output_tokens": 5, + "total_tokens": 8, + }, + ) + + request = MagicMock() + request.model = MagicMock() + request.model.model_name = "gpt-4o" + request.model.provider = "openai" + await mw.awrap_model_call(request, handler) + + assert duration_calls + assert token_calls == [ + { + "input_tokens": 3, + "output_tokens": 5, + "model": "gpt-4o", + "provider": "openai", + } + ] + finally: + ot.reload_for_tests() + + async def test_enabled_tool_call_records_error_metric( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.delenv("SURFSENSE_DISABLE_OTEL", raising=False) + monkeypatch.setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317") + from app.observability import otel as ot + + errors: list[str] = [] + monkeypatch.setattr( + "app.agents.new_chat.middleware.otel_span.ot_metrics.record_tool_call_error", + lambda *, tool_name: errors.append(tool_name), + ) + monkeypatch.setattr( + "app.agents.new_chat.middleware.otel_span.ot_metrics.record_tool_call_duration", + lambda *args, **kwargs: None, + ) + + ot.reload_for_tests() + try: + mw = OtelSpanMiddleware() + + async def handler(req): + return ToolMessage( + content="failed", + tool_call_id="abc", + status="error", + ) + + request = MagicMock() + request.tool = MagicMock() + request.tool.name = "web_search" + await mw.awrap_tool_call(request, handler) + assert errors == ["web_search"] + finally: + ot.reload_for_tests() From 53691f9c518c1a95be9f6f2881f0845603bfb4df Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Thu, 21 May 2026 23:02:54 +0530 Subject: [PATCH 009/198] feat(agents): track permission and compaction events --- .../middleware/shared/permissions/ask/request.py | 3 +++ surfsense_backend/app/agents/new_chat/middleware/compaction.py | 2 ++ surfsense_backend/app/agents/new_chat/middleware/doom_loop.py | 2 ++ surfsense_backend/app/agents/new_chat/middleware/permission.py | 3 +++ 4 files changed, 10 insertions(+) diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/ask/request.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/ask/request.py index d61d38f34..a725bfee1 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/ask/request.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/ask/request.py @@ -17,6 +17,7 @@ from langchain_core.tools import BaseTool from langgraph.types import interrupt from app.agents.new_chat.permissions import Rule +from app.observability import metrics as ot_metrics from app.observability import otel as ot from .decision import normalize_permission_decision @@ -52,6 +53,8 @@ def request_permission_decision( ), ot.interrupt_span(interrupt_type=PERMISSION_ASK_INTERRUPT_TYPE), ): + ot_metrics.record_permission_ask(permission=tool_name) + ot_metrics.record_interrupt(interrupt_type=PERMISSION_ASK_INTERRUPT_TYPE) decision = interrupt(payload) return normalize_permission_decision(decision) diff --git a/surfsense_backend/app/agents/new_chat/middleware/compaction.py b/surfsense_backend/app/agents/new_chat/middleware/compaction.py index 16361e16b..8173976fe 100644 --- a/surfsense_backend/app/agents/new_chat/middleware/compaction.py +++ b/surfsense_backend/app/agents/new_chat/middleware/compaction.py @@ -34,6 +34,7 @@ from deepagents.middleware.summarization import ( ) from langchain_core.messages import SystemMessage +from app.observability import metrics as ot_metrics from app.observability import otel as ot if TYPE_CHECKING: @@ -178,6 +179,7 @@ class SurfSenseCompactionMiddleware(SummarizationMiddleware): messages_in=len(conversation_messages), extra={"compaction.cutoff_index": int(cutoff_index)}, ): + ot_metrics.record_compaction_run(reason="auto") messages_to_summarize, preserved_messages = super()._partition_messages( conversation_messages, cutoff_index ) diff --git a/surfsense_backend/app/agents/new_chat/middleware/doom_loop.py b/surfsense_backend/app/agents/new_chat/middleware/doom_loop.py index 850ecd1d2..d50cadc0e 100644 --- a/surfsense_backend/app/agents/new_chat/middleware/doom_loop.py +++ b/surfsense_backend/app/agents/new_chat/middleware/doom_loop.py @@ -47,6 +47,7 @@ from langgraph.config import get_config from langgraph.runtime import Runtime from langgraph.types import interrupt +from app.observability import metrics as ot_metrics from app.observability import otel as ot logger = logging.getLogger(__name__) @@ -195,6 +196,7 @@ class DoomLoopMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, Respon "interrupt.tool": (action or {}).get("tool", ""), }, ): + ot_metrics.record_interrupt(interrupt_type="permission_ask") decision = interrupt( { "type": "permission_ask", diff --git a/surfsense_backend/app/agents/new_chat/middleware/permission.py b/surfsense_backend/app/agents/new_chat/middleware/permission.py index f77b7e387..e174ab0bd 100644 --- a/surfsense_backend/app/agents/new_chat/middleware/permission.py +++ b/surfsense_backend/app/agents/new_chat/middleware/permission.py @@ -61,6 +61,7 @@ from app.agents.new_chat.permissions import ( aggregate_action, evaluate_many, ) +from app.observability import metrics as ot_metrics from app.observability import otel as ot logger = logging.getLogger(__name__) @@ -284,6 +285,8 @@ class PermissionMiddleware(AgentMiddleware): # type: ignore[type-arg] ), ot.interrupt_span(interrupt_type="permission_ask"), ): + ot_metrics.record_permission_ask(permission=tool_name) + ot_metrics.record_interrupt(interrupt_type="permission_ask") decision = interrupt(payload) return _normalize_permission_decision(decision) From b9d76f006dff959879b190af3c50d3925466d351 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Thu, 21 May 2026 23:03:31 +0530 Subject: [PATCH 010/198] feat(retriever): instrument knowledge base search --- .../app/retriever/chunks_hybrid_search.py | 41 +++++++++++++ .../app/retriever/documents_hybrid_search.py | 41 +++++++++++++ .../unit/observability/test_retriever_otel.py | 61 +++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 surfsense_backend/tests/unit/observability/test_retriever_otel.py diff --git a/surfsense_backend/app/retriever/chunks_hybrid_search.py b/surfsense_backend/app/retriever/chunks_hybrid_search.py index e32c6c43d..47f7fe6b1 100644 --- a/surfsense_backend/app/retriever/chunks_hybrid_search.py +++ b/surfsense_backend/app/retriever/chunks_hybrid_search.py @@ -1,13 +1,51 @@ import asyncio import contextlib +import functools import time from datetime import datetime +from app.observability import metrics as ot_metrics, otel as ot from app.utils.perf import get_perf_logger _MAX_FETCH_CHUNKS_PER_DOC = 20 +def _instrument_search(mode: str): + def _decorator(func): + @functools.wraps(func) + async def _wrapper( + self, query_text: str, top_k: int, search_space_id: int, *args, **kwargs + ): + t0 = time.perf_counter() + with ot.kb_search_span( + search_space_id=search_space_id, + query_chars=len(query_text), + extra={"search.surface": "chunks", "search.mode": mode}, + ) as sp: + try: + result = await func( + self, query_text, top_k, search_space_id, *args, **kwargs + ) + except Exception: + ot_metrics.record_kb_search_duration( + (time.perf_counter() - t0) * 1000, + search_space_id=search_space_id, + surface="chunks", + ) + raise + sp.set_attribute("result.count", len(result)) + ot_metrics.record_kb_search_duration( + (time.perf_counter() - t0) * 1000, + search_space_id=search_space_id, + surface="chunks", + ) + return result + + return _wrapper + + return _decorator + + class ChucksHybridSearchRetriever: def __init__(self, db_session): """ @@ -18,6 +56,7 @@ class ChucksHybridSearchRetriever: """ self.db_session = db_session + @_instrument_search("vector") async def vector_search( self, query_text: str, @@ -88,6 +127,7 @@ class ChucksHybridSearchRetriever: return chunks + @_instrument_search("full_text") async def full_text_search( self, query_text: str, @@ -153,6 +193,7 @@ class ChucksHybridSearchRetriever: return chunks + @_instrument_search("hybrid") async def hybrid_search( self, query_text: str, diff --git a/surfsense_backend/app/retriever/documents_hybrid_search.py b/surfsense_backend/app/retriever/documents_hybrid_search.py index 3eabdb004..9ce86d404 100644 --- a/surfsense_backend/app/retriever/documents_hybrid_search.py +++ b/surfsense_backend/app/retriever/documents_hybrid_search.py @@ -1,12 +1,50 @@ import contextlib +import functools import time from datetime import datetime +from app.observability import metrics as ot_metrics, otel as ot from app.utils.perf import get_perf_logger _MAX_FETCH_CHUNKS_PER_DOC = 20 +def _instrument_search(mode: str): + def _decorator(func): + @functools.wraps(func) + async def _wrapper( + self, query_text: str, top_k: int, search_space_id: int, *args, **kwargs + ): + t0 = time.perf_counter() + with ot.kb_search_span( + search_space_id=search_space_id, + query_chars=len(query_text), + extra={"search.surface": "documents", "search.mode": mode}, + ) as sp: + try: + result = await func( + self, query_text, top_k, search_space_id, *args, **kwargs + ) + except Exception: + ot_metrics.record_kb_search_duration( + (time.perf_counter() - t0) * 1000, + search_space_id=search_space_id, + surface="documents", + ) + raise + sp.set_attribute("result.count", len(result)) + ot_metrics.record_kb_search_duration( + (time.perf_counter() - t0) * 1000, + search_space_id=search_space_id, + surface="documents", + ) + return result + + return _wrapper + + return _decorator + + class DocumentHybridSearchRetriever: def __init__(self, db_session): """ @@ -17,6 +55,7 @@ class DocumentHybridSearchRetriever: """ self.db_session = db_session + @_instrument_search("vector") async def vector_search( self, query_text: str, @@ -81,6 +120,7 @@ class DocumentHybridSearchRetriever: return documents + @_instrument_search("full_text") async def full_text_search( self, query_text: str, @@ -145,6 +185,7 @@ class DocumentHybridSearchRetriever: return documents + @_instrument_search("hybrid") async def hybrid_search( self, query_text: str, diff --git a/surfsense_backend/tests/unit/observability/test_retriever_otel.py b/surfsense_backend/tests/unit/observability/test_retriever_otel.py new file mode 100644 index 000000000..9712a3150 --- /dev/null +++ b/surfsense_backend/tests/unit/observability/test_retriever_otel.py @@ -0,0 +1,61 @@ +"""Tests for retriever OTel wrappers.""" + +from __future__ import annotations + +from contextlib import contextmanager +from typing import Any + +import pytest + +from app.retriever.documents_hybrid_search import _instrument_search + +pytestmark = pytest.mark.unit + + +class _Span: + def __init__(self) -> None: + self.attrs: dict[str, Any] = {} + + def set_attribute(self, key: str, value: Any) -> None: + self.attrs[key] = value + + +@contextmanager +def _fake_span(**kwargs): + span = _Span() + span.attrs.update(kwargs) + yield span + + +@pytest.mark.asyncio +async def test_retriever_wrapper_records_one_span_and_metric(monkeypatch) -> None: + calls: list[dict[str, Any]] = [] + + monkeypatch.setattr( + "app.retriever.documents_hybrid_search.ot.kb_search_span", + lambda **kwargs: _fake_span(**kwargs), + ) + monkeypatch.setattr( + "app.retriever.documents_hybrid_search.ot_metrics.record_kb_search_duration", + lambda duration_ms, **attrs: calls.append( + {"duration_ms": duration_ms, **attrs} + ), + ) + + class Retriever: + @_instrument_search("hybrid") + async def search( + self, + query_text: str, + top_k: int, + search_space_id: int, + ) -> list[str]: + del query_text, top_k, search_space_id + return ["doc-1", "doc-2"] + + result = await Retriever().search("hello", 3, 42) + + assert result == ["doc-1", "doc-2"] + assert len(calls) == 1 + assert calls[0]["search_space_id"] == 42 + assert calls[0]["surface"] == "documents" From cea5605e327c4969efe133dd8223c4cf2d08184c Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Thu, 21 May 2026 23:03:43 +0530 Subject: [PATCH 011/198] feat(indexing): track indexing and connector outcomes --- .../indexing_pipeline_service.py | 24 ++++++++++++++ .../app/tasks/celery_tasks/connector_tasks.py | 31 ++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/surfsense_backend/app/indexing_pipeline/indexing_pipeline_service.py b/surfsense_backend/app/indexing_pipeline/indexing_pipeline_service.py index 2339647ea..2aa92bd9b 100644 --- a/surfsense_backend/app/indexing_pipeline/indexing_pipeline_service.py +++ b/surfsense_backend/app/indexing_pipeline/indexing_pipeline_service.py @@ -57,6 +57,7 @@ from app.indexing_pipeline.pipeline_logger import ( log_retryable_llm_error, log_unexpected_error, ) +from app.observability import metrics as ot_metrics, otel as ot from app.utils.perf import get_perf_logger @@ -362,6 +363,16 @@ class IndexingPipelineService: ) perf = get_perf_logger() t_index = time.perf_counter() + document_type = ( + document.document_type.value + if getattr(document, "document_type", None) + else None + ) + persist_span_cm = ot.kb_persist_span( + document_type=document_type, + ) + persist_span = persist_span_cm.__enter__() + outcome_status = "failed" try: log_index_started(ctx) document.status = DocumentStatus.processing() @@ -429,11 +440,13 @@ class IndexingPipelineService: time.perf_counter() - t_index, ) log_index_success(ctx, chunk_count=len(chunks)) + outcome_status = "success" await self._enqueue_ai_sort_if_enabled(document) except RETRYABLE_LLM_ERRORS as e: log_retryable_llm_error(ctx, e) + outcome_status = "requeued" await rollback_and_persist_failure( self.session, document, llm_retryable_message(e) ) @@ -465,6 +478,17 @@ class IndexingPipelineService: with contextlib.suppress(Exception): await self.session.refresh(document) + with contextlib.suppress(Exception): + persist_span.set_attribute("indexing.status", outcome_status) + ot_metrics.record_indexing_document_duration( + time.perf_counter() - t_index, + document_type=document_type, + ) + ot_metrics.record_indexing_document_outcome( + document_type=document_type, + status=outcome_status, + ) + persist_span_cm.__exit__(None, None, None) return document async def _enqueue_ai_sort_if_enabled(self, document: Document) -> None: diff --git a/surfsense_backend/app/tasks/celery_tasks/connector_tasks.py b/surfsense_backend/app/tasks/celery_tasks/connector_tasks.py index 08d96cfa0..86296c3d9 100644 --- a/surfsense_backend/app/tasks/celery_tasks/connector_tasks.py +++ b/surfsense_backend/app/tasks/celery_tasks/connector_tasks.py @@ -1,14 +1,43 @@ """Celery tasks for connector indexing.""" import logging +import time import traceback +from collections.abc import Awaitable, Callable + +from celery import current_task from app.celery_app import celery_app -from app.tasks.celery_tasks import get_celery_session_maker, run_async_celery_task +from app.observability import metrics as ot_metrics +from app.tasks.celery_tasks import ( + get_celery_session_maker, + run_async_celery_task as _run_async_celery_task, +) logger = logging.getLogger(__name__) +def run_async_celery_task[T](coro_factory: Callable[[], Awaitable[T]]) -> T: + """Run connector sync work and record aggregate connector metrics.""" + task_name = getattr(current_task, "name", None) or "unknown" + t0 = time.perf_counter() + status = "failed" + try: + result = _run_async_celery_task(coro_factory) + status = "success" + return result + finally: + elapsed_s = time.perf_counter() - t0 + ot_metrics.record_connector_sync_duration( + elapsed_s, + connector_type=task_name, + ) + ot_metrics.record_connector_sync_outcome( + connector_type=task_name, + status=status, + ) + + def _handle_greenlet_error(e: Exception, task_name: str, connector_id: int) -> None: """ Handle greenlet_spawn errors with detailed logging for debugging. From a66d65a835bd8969868618ee9f3b7b20ce5c339b Mon Sep 17 00:00:00 2001 From: guangyang1206 Date: Fri, 22 May 2026 12:08:00 +0800 Subject: [PATCH 012/198] refactor: extract shared hasPermission helper (MODSetter/SurfSense#1366) - Add canPerform() helper function to members-query.atoms.ts - Add usePermissionGate() hook for convenience - Update team-content.tsx to use canPerform() - Update roles-manager.tsx to use canPerform() - Eliminates duplicated permission check logic - Centralizes permission policy in one location Fixes #1366 --- .../[search_space_id]/team/team-content.tsx | 9 ++--- .../atoms/members/members-query.atoms.ts | 35 +++++++++++++++++++ .../components/settings/roles-manager.tsx | 5 ++- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx index f003dde1b..17c1dd121 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx @@ -31,7 +31,7 @@ import { deleteMemberMutationAtom, updateMemberMutationAtom, } from "@/atoms/members/members-mutation.atoms"; -import { membersAtom, myAccessAtom } from "@/atoms/members/members-query.atoms"; +import { membersAtom, myAccessAtom, canPerform } from "@/atoms/members/members-query.atoms"; import { AlertDialog, AlertDialogAction, @@ -126,14 +126,9 @@ export function TeamContent({ searchSpaceId }: TeamContentProps) { const { data: access = null, isLoading: accessLoading } = useAtomValue(myAccessAtom); const hasPermission = useCallback( - (permission: string) => { - if (!access) return false; - if (access.is_owner) return true; - return access.permissions?.includes(permission) ?? false; - }, + (permission: string) => canPerform(access, permission), [access] ); - const { data: members = [], isLoading: membersLoading } = useAtomValue(membersAtom); const { mutateAsync: updateMember } = useAtomValue(updateMemberMutationAtom); diff --git a/surfsense_web/atoms/members/members-query.atoms.ts b/surfsense_web/atoms/members/members-query.atoms.ts index c08a7a337..f8e4b2cf6 100644 --- a/surfsense_web/atoms/members/members-query.atoms.ts +++ b/surfsense_web/atoms/members/members-query.atoms.ts @@ -39,3 +39,38 @@ export const myAccessAtom = atomWithQuery((get) => { }, }; }); + +/** + * Helper function to check if the current user has a specific permission. + * + * @param access - The access object from useAtomValue(myAccessAtom) + * @param permission - The permission string to check + * @returns boolean indicating if the user has the permission + * + * @example + * const access = useAtomValue(myAccessAtom); + * if (canPerform(access, 'manage_members')) { ... } + */ +export function canPerform( + access: { is_owner: boolean; permissions?: string[] } | null | undefined, + permission: string +): boolean { + if (!access) return false; + if (access.is_owner) return true; + return access.permissions?.includes(permission) ?? false; +} + +/** + * Hook wrapper for canPerform that reads from myAccessAtom internally. + * Use this if you want to avoid calling useAtomValue(myAccessAtom) separately. + * + * @param permission - The permission string to check + * @returns boolean indicating if the user has the permission + * + * @example + * const canManageMembers = usePermissionGate('manage_members'); + */ +export function usePermissionGate(permission: string): boolean { + const access = useAtomValue(myAccessAtom); + return canPerform(access, permission); +} diff --git a/surfsense_web/components/settings/roles-manager.tsx b/surfsense_web/components/settings/roles-manager.tsx index ee32f6e69..65f47bc43 100644 --- a/surfsense_web/components/settings/roles-manager.tsx +++ b/surfsense_web/components/settings/roles-manager.tsx @@ -26,7 +26,7 @@ import { } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; -import { myAccessAtom } from "@/atoms/members/members-query.atoms"; +import { myAccessAtom, canPerform } from "@/atoms/members/members-query.atoms"; import { permissionsAtom } from "@/atoms/permissions/permissions-query.atoms"; import { createRoleMutationAtom, @@ -257,6 +257,9 @@ export function RolesManager({ searchSpaceId }: { searchSpaceId: number }) { const { data: access = null } = useAtomValue(myAccessAtom); const hasPermission = useCallback( + (permission: string) => canPerform(access, permission), + [access] + ); (permission: string) => { if (!access) return false; if (access.is_owner) return true; From a1397a51b50bab3d1e8d64d81f3aeb4a5df3b63a Mon Sep 17 00:00:00 2001 From: guangyang1206 Date: Fri, 22 May 2026 12:15:20 +0800 Subject: [PATCH 013/198] fix: move getConnectorTelemetryMeta from components/ to lib/ to fix dependency inversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lib/posthog/events.ts was importing from components/assistant-ui/..., creating an inverted dependency layer (lib → components). Move ConnectorTelemetryMeta type, CONNECTOR_TELEMETRY_REGISTRY, getConnectorTelemetryMeta, and getReauthEndpoint into the new lib/connector-telemetry.ts module so that lib/ no longer depends upward into the UI tree. connector-constants.ts now re-exports from the new module for backward compatibility. Fixes #1375 --- .../constants/connector-constants.ts | 114 ++------------ surfsense_web/lib/connector-telemetry.ts | 149 ++++++++++++++++++ surfsense_web/lib/posthog/events.ts | 4 +- 3 files changed, 161 insertions(+), 106 deletions(-) create mode 100644 surfsense_web/lib/connector-telemetry.ts diff --git a/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts b/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts index 2f9605ea7..01a911d70 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts @@ -303,110 +303,16 @@ export const AUTO_INDEX_CONNECTOR_TYPES = new Set(Object.keys(AUTO_INDEX // `lib/posthog/events.ts` or per-connector tracking code. // ============================================================================ -export type ConnectorTelemetryGroup = "oauth" | "composio" | "crawler" | "other" | "unknown"; - -export interface ConnectorTelemetryMeta { - connector_type: string; - connector_title: string; - connector_group: ConnectorTelemetryGroup; - is_oauth: boolean; -} - -const CONNECTOR_TELEMETRY_REGISTRY: ReadonlyMap = (() => { - const map = new Map(); - - for (const c of OAUTH_CONNECTORS) { - map.set(c.connectorType, { - connector_type: c.connectorType, - connector_title: c.title, - connector_group: "oauth", - is_oauth: true, - }); - } - for (const c of COMPOSIO_CONNECTORS) { - map.set(c.connectorType, { - connector_type: c.connectorType, - connector_title: c.title, - connector_group: "composio", - is_oauth: true, - }); - } - for (const c of CRAWLERS) { - map.set(c.connectorType, { - connector_type: c.connectorType, - connector_title: c.title, - connector_group: "crawler", - is_oauth: false, - }); - } - for (const c of OTHER_CONNECTORS) { - map.set(c.connectorType, { - connector_type: c.connectorType, - connector_title: c.title, - connector_group: "other", - is_oauth: false, - }); - } - - return map; -})(); - -/** - * Returns telemetry metadata for a connector_type, or a minimal "unknown" - * record so tracking never no-ops for connectors that exist in the backend - * but were forgotten in the UI registry. - */ -export function getConnectorTelemetryMeta(connectorType: string): ConnectorTelemetryMeta { - const hit = CONNECTOR_TELEMETRY_REGISTRY.get(connectorType); - if (hit) return hit; - - return { - connector_type: connectorType, - connector_title: connectorType, - connector_group: "unknown", - is_oauth: false, - }; -} - -// ============================================================================= -// REAUTH ENDPOINTS -// ============================================================================= - -/** - * Legacy (non-MCP) OAuth reauth endpoints, keyed by connector type. - * These are used for connectors that were NOT created via MCP OAuth. - */ -export const LEGACY_REAUTH_ENDPOINTS: Partial> = { - [EnumConnectorName.LINEAR_CONNECTOR]: "/api/v1/auth/linear/connector/reauth", - [EnumConnectorName.JIRA_CONNECTOR]: "/api/v1/auth/jira/connector/reauth", - [EnumConnectorName.NOTION_CONNECTOR]: "/api/v1/auth/notion/connector/reauth", - [EnumConnectorName.GOOGLE_DRIVE_CONNECTOR]: "/api/v1/auth/google/drive/connector/reauth", - [EnumConnectorName.GOOGLE_GMAIL_CONNECTOR]: "/api/v1/auth/google/gmail/connector/reauth", - [EnumConnectorName.GOOGLE_CALENDAR_CONNECTOR]: "/api/v1/auth/google/calendar/connector/reauth", - [EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR]: "/api/v1/auth/composio/connector/reauth", - [EnumConnectorName.COMPOSIO_GMAIL_CONNECTOR]: "/api/v1/auth/composio/connector/reauth", - [EnumConnectorName.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR]: "/api/v1/auth/composio/connector/reauth", - [EnumConnectorName.ONEDRIVE_CONNECTOR]: "/api/v1/auth/onedrive/connector/reauth", - [EnumConnectorName.DROPBOX_CONNECTOR]: "/api/v1/auth/dropbox/connector/reauth", - [EnumConnectorName.CONFLUENCE_CONNECTOR]: "/api/v1/auth/confluence/connector/reauth", - [EnumConnectorName.TEAMS_CONNECTOR]: "/api/v1/auth/teams/connector/reauth", - [EnumConnectorName.DISCORD_CONNECTOR]: "/api/v1/auth/discord/connector/reauth", -}; - -/** - * Resolve the reauth endpoint for a connector. - * - * MCP OAuth connectors (those with ``config.mcp_service``) dynamically build - * the URL from the service key. Legacy OAuth connectors fall back to the - * static ``LEGACY_REAUTH_ENDPOINTS`` map. - */ -export function getReauthEndpoint(connector: SearchSourceConnector): string | undefined { - const mcpService = connector.config?.mcp_service as string | undefined; - if (mcpService) { - return `/api/v1/auth/mcp/${mcpService}/connector/reauth`; - } - return LEGACY_REAUTH_ENDPOINTS[connector.connector_type]; -} +// Telemetry types & helpers are now defined in `@/lib/connector-telemetry`. +// Re-exported here for backward compatibility with existing imports. +export type { + ConnectorTelemetryGroup, + ConnectorTelemetryMeta, +} from "@/lib/connector-telemetry"; +export { + getConnectorTelemetryMeta, + getReauthEndpoint, +} from "@/lib/connector-telemetry"; // Re-export IndexingConfigState from schemas for backward compatibility export type { IndexingConfigState } from "./connector-popup.schemas"; diff --git a/surfsense_web/lib/connector-telemetry.ts b/surfsense_web/lib/connector-telemetry.ts new file mode 100644 index 000000000..ef1b3de32 --- /dev/null +++ b/surfsense_web/lib/connector-telemetry.ts @@ -0,0 +1,149 @@ +import { EnumConnectorName } from "@/contracts/enums/connector"; +import type { SearchSourceConnector } from "@/contracts/types/connector.types"; +import { + OAUTH_CONNECTORS, + COMPOSIO_CONNECTORS, + CRAWLERS, + OTHER_CONNECTORS, +} from "@/components/assistant-ui/connector-popup/constants/connector-constants"; + +// ============================================================================= +// Connector Telemetry Types & Registry +// ============================================================================= + +export type ConnectorTelemetryGroup = + | "oauth" + | "composio" + | "crawler" + | "other" + | "unknown"; + +export interface ConnectorTelemetryMeta { + connector_type: string; + connector_title: string; + connector_group: ConnectorTelemetryGroup; + is_oauth: boolean; +} + +/** + * Single source of truth for "what does this connector_type look like in + * analytics?". Any connector added to the lists above is automatically + * picked up here, so adding a new integration does NOT require touching + * `lib/posthog/events.ts` or per-connector tracking code. + */ +const CONNECTOR_TELEMETRY_REGISTRY: ReadonlyMap< + string, + ConnectorTelemetryMeta +> = (() => { + const map = new Map(); + + for (const c of OAUTH_CONNECTORS) { + map.set(c.connectorType, { + connector_type: c.connectorType, + connector_title: c.title, + connector_group: "oauth", + is_oauth: true, + }); + } + for (const c of COMPOSIO_CONNECTORS) { + map.set(c.connectorType, { + connector_type: c.connectorType, + connector_title: c.title, + connector_group: "composio", + is_oauth: true, + }); + } + for (const c of CRAWLERS) { + map.set(c.connectorType, { + connector_type: c.connectorType, + connector_title: c.title, + connector_group: "crawler", + is_oauth: false, + }); + } + for (const c of OTHER_CONNECTORS) { + map.set(c.connectorType, { + connector_type: c.connectorType, + connector_title: c.title, + connector_group: "other", + is_oauth: false, + }); + } + + return map; +})(); + +/** + * Returns telemetry metadata for a connector_type, or a minimal "unknown" + * record so tracking never no-ops for connectors that exist in the backend + * but were forgotten in the UI registry. + */ +export function getConnectorTelemetryMeta( + connectorType: string, +): ConnectorTelemetryMeta { + const hit = CONNECTOR_TELEMETRY_REGISTRY.get(connectorType); + if (hit) return hit; + + return { + connector_type: connectorType, + connector_title: connectorType, + connector_group: "unknown", + is_oauth: false, + }; +} + +// ============================================================================= +// Reauth Endpoint Resolution +// ============================================================================= + +/** + * Legacy (non-MCP) OAuth reauth endpoints, keyed by connector type. + * These are used for connectors that were NOT created via MCP OAuth. + */ +const LEGACY_REAUTH_ENDPOINTS: Partial> = { + [EnumConnectorName.LINEAR_CONNECTOR]: + "/api/v1/auth/linear/connector/reauth", + [EnumConnectorName.JIRA_CONNECTOR]: + "/api/v1/auth/jira/connector/reauth", + [EnumConnectorName.NOTION_CONNECTOR]: + "/api/v1/auth/notion/connector/reauth", + [EnumConnectorName.GOOGLE_DRIVE_CONNECTOR]: + "/api/v1/auth/google/drive/connector/reauth", + [EnumConnectorName.GOOGLE_GMAIL_CONNECTOR]: + "/api/v1/auth/google/gmail/connector/reauth", + [EnumConnectorName.GOOGLE_CALENDAR_CONNECTOR]: + "/api/v1/auth/google/calendar/connector/reauth", + [EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR]: + "/api/v1/auth/composio/connector/reauth", + [EnumConnectorName.COMPOSIO_GMAIL_CONNECTOR]: + "/api/v1/auth/composio/connector/reauth", + [EnumConnectorName.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR]: + "/api/v1/auth/composio/connector/reauth", + [EnumConnectorName.ONEDRIVE_CONNECTOR]: + "/api/v1/auth/onedrive/connector/reauth", + [EnumConnectorName.DROPBOX_CONNECTOR]: + "/api/v1/auth/dropbox/connector/reauth", + [EnumConnectorName.CONFLUENCE_CONNECTOR]: + "/api/v1/auth/confluence/connector/reauth", + [EnumConnectorName.TEAMS_CONNECTOR]: + "/api/v1/auth/teams/connector/reauth", + [EnumConnectorName.DISCORD_CONNECTOR]: + "/api/v1/auth/discord/connector/reauth", +}; + +/** + * Resolve the reauth endpoint for a connector. + * + * MCP OAuth connectors (those with ``config.mcp_service``) dynamically build + * the URL from the service key. Legacy OAuth connectors fall back to the + * static ``LEGACY_REAUTH_ENDPOINTS`` map. + */ +export function getReauthEndpoint( + connector: SearchSourceConnector, +): string | undefined { + const mcpService = connector.config?.mcp_service as string | undefined; + if (mcpService) { + return `/api/v1/auth/mcp/${mcpService}/connector/reauth`; + } + return LEGACY_REAUTH_ENDPOINTS[connector.connector_type]; +} diff --git a/surfsense_web/lib/posthog/events.ts b/surfsense_web/lib/posthog/events.ts index 127685fe8..687d589f9 100644 --- a/surfsense_web/lib/posthog/events.ts +++ b/surfsense_web/lib/posthog/events.ts @@ -1,5 +1,5 @@ -import posthog from "posthog-js"; -import { getConnectorTelemetryMeta } from "@/components/assistant-ui/connector-popup/constants/connector-constants"; +import type { ConnectorTelemetryMeta } from "@/lib/connector-telemetry"; +import { getConnectorTelemetryMeta } from "@/lib/connector-telemetry"; import type { ChatErrorKind, ChatErrorSeverity, ChatFlow } from "@/lib/chat/chat-error-classifier"; /** From 21d9b1f2186d832bed0348938cc87a08d9d430d3 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 22 May 2026 13:47:10 +0530 Subject: [PATCH 014/198] fix(observability): sanitize outbound HTTP span URLs --- .../app/observability/bootstrap.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/surfsense_backend/app/observability/bootstrap.py b/surfsense_backend/app/observability/bootstrap.py index a1b9817da..ad1d02ea8 100644 --- a/surfsense_backend/app/observability/bootstrap.py +++ b/surfsense_backend/app/observability/bootstrap.py @@ -8,6 +8,7 @@ import os import socket from importlib import metadata from typing import Any +from urllib.parse import urlsplit, urlunsplit from app.observability import otel @@ -122,6 +123,27 @@ def _safe_instrument(name: str, instrument: Any) -> bool: return True +def _url_without_query(raw_url: Any) -> str | None: + try: + parts = urlsplit(str(raw_url)) + except Exception: + return None + if not parts.scheme or not parts.netloc: + return None + return urlunsplit((parts.scheme, parts.netloc, parts.path or "/", "", "")) + + +def _sanitize_http_span_url(span: Any, request: Any) -> None: + sanitized = _url_without_query(getattr(request, "url", None)) + if not sanitized: + return + with contextlib.suppress(Exception): + # Keep both old and current semantic-convention names safe. The + # collector can drop one later without needing application changes. + span.set_attribute("http.url", sanitized) + span.set_attribute("url.full", sanitized) + + def _instrument_fastapi(app: Any | None) -> None: global _FASTAPI_INSTRUMENTED if app is None or _FASTAPI_INSTRUMENTED: @@ -202,7 +224,12 @@ def _instrument_httpx() -> None: def _run() -> None: from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor - HTTPXClientInstrumentor().instrument() + HTTPXClientInstrumentor().instrument( + request_hook=lambda span, request: _sanitize_http_span_url(span, request), + response_hook=lambda span, request, _response: _sanitize_http_span_url( + span, request + ), + ) if _safe_instrument("HTTPX", _run): _HTTPX_INSTRUMENTED = True From f7f49de10941cc8c3f3337207f28780cb04d285f Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 22 May 2026 13:47:50 +0530 Subject: [PATCH 015/198] feat(observability): add chat subagent and ETL telemetry primitives --- .../app/observability/metrics.py | 180 ++++++++++++++++++ surfsense_backend/app/observability/otel.py | 154 +++++++++++++++ 2 files changed, 334 insertions(+) diff --git a/surfsense_backend/app/observability/metrics.py b/surfsense_backend/app/observability/metrics.py index fb99b4cc8..53beb959d 100644 --- a/surfsense_backend/app/observability/metrics.py +++ b/surfsense_backend/app/observability/metrics.py @@ -195,6 +195,73 @@ def _perf_elapsed(): ) +@lru_cache(maxsize=1) +def _chat_request_duration(): + return _get_meter().create_histogram( + "surfsense.chat.request.duration", + unit="ms", + description="Duration of SurfSense streamed chat requests.", + ) + + +@lru_cache(maxsize=1) +def _chat_request_outcome(): + return _get_meter().create_counter( + "surfsense.chat.request.outcome", + description="Count of SurfSense chat request outcomes.", + ) + + +@lru_cache(maxsize=1) +def _subagent_invoke_duration(): + return _get_meter().create_histogram( + "surfsense.subagent.invoke.duration", + unit="ms", + description="Duration of SurfSense subagent invocations.", + ) + + +@lru_cache(maxsize=1) +def _subagent_invoke_outcome(): + return _get_meter().create_counter( + "surfsense.subagent.invoke.outcome", + description="Count of SurfSense subagent invocation outcomes.", + ) + + +@lru_cache(maxsize=1) +def _etl_extract_duration(): + return _get_meter().create_histogram( + "surfsense.etl.extract.duration", + unit="s", + description="Duration of SurfSense ETL extraction.", + ) + + +@lru_cache(maxsize=1) +def _etl_extract_outcome(): + return _get_meter().create_counter( + "surfsense.etl.extract.outcome", + description="Count of SurfSense ETL extraction outcomes.", + ) + + +@lru_cache(maxsize=1) +def _celery_heartbeat_refreshes(): + return _get_meter().create_counter( + "surfsense.celery.heartbeat.refreshes", + description="Count of SurfSense Celery heartbeat refreshes.", + ) + + +@lru_cache(maxsize=1) +def _celery_heartbeat_failures(): + return _get_meter().create_counter( + "surfsense.celery.heartbeat.failures", + description="Count of SurfSense Celery heartbeat failures.", + ) + + def record_model_call_duration( duration_ms: float, *, model: str | None, provider: str | None ) -> None: @@ -312,6 +379,111 @@ def record_perf_elapsed(duration_ms: float, *, label: str) -> None: _record(_perf_elapsed(), duration_ms, {"label": label}) +def record_chat_request_duration( + duration_ms: float, + *, + flow: str, + outcome: str, + agent_mode: str | None = None, +) -> None: + _record( + _chat_request_duration(), + duration_ms, + {"chat.flow": flow, "outcome": outcome, "agent.mode": agent_mode}, + ) + + +def record_chat_request_outcome( + *, + flow: str, + outcome: str, + agent_mode: str | None = None, +) -> None: + _add( + _chat_request_outcome(), + 1, + {"chat.flow": flow, "outcome": outcome, "agent.mode": agent_mode}, + ) + + +def record_subagent_invoke_duration( + duration_ms: float, + *, + subagent_type: str, + path: str | None, + outcome: str, +) -> None: + _record( + _subagent_invoke_duration(), + duration_ms, + { + "subagent.type": subagent_type, + "subagent.path": path or "unknown", + "outcome": outcome, + }, + ) + + +def record_subagent_invoke_outcome( + *, + subagent_type: str, + path: str | None, + outcome: str, +) -> None: + _add( + _subagent_invoke_outcome(), + 1, + { + "subagent.type": subagent_type, + "subagent.path": path or "unknown", + "outcome": outcome, + }, + ) + + +def record_etl_extract_duration( + duration_s: float, + *, + etl_service: str | None, + content_type: str | None, + status: str, +) -> None: + _record( + _etl_extract_duration(), + duration_s, + { + "etl.service": etl_service or "unknown", + "content.type": content_type or "unknown", + "status": status, + }, + ) + + +def record_etl_extract_outcome( + *, + etl_service: str | None, + content_type: str | None, + status: str, +) -> None: + _add( + _etl_extract_outcome(), + 1, + { + "etl.service": etl_service or "unknown", + "content.type": content_type or "unknown", + "status": status, + }, + ) + + +def record_celery_heartbeat_refresh(*, heartbeat_type: str) -> None: + _add(_celery_heartbeat_refreshes(), 1, {"heartbeat.type": heartbeat_type}) + + +def record_celery_heartbeat_failure(*, heartbeat_type: str) -> None: + _add(_celery_heartbeat_failures(), 1, {"heartbeat.type": heartbeat_type}) + + def _runtime_snapshot_value(key: str, transform: Any = None) -> list[Any]: from opentelemetry.metrics import Observation @@ -398,9 +570,15 @@ def register_runtime_observables() -> None: __all__ = [ "record_auth_failure", + "record_celery_heartbeat_failure", + "record_celery_heartbeat_refresh", + "record_chat_request_duration", + "record_chat_request_outcome", "record_compaction_run", "record_connector_sync_duration", "record_connector_sync_outcome", + "record_etl_extract_duration", + "record_etl_extract_outcome", "record_indexing_document_duration", "record_indexing_document_outcome", "record_interrupt", @@ -410,6 +588,8 @@ __all__ = [ "record_perf_elapsed", "record_permission_ask", "record_rate_limit_rejection", + "record_subagent_invoke_duration", + "record_subagent_invoke_outcome", "record_tool_call_duration", "record_tool_call_error", "register_runtime_observables", diff --git a/surfsense_backend/app/observability/otel.py b/surfsense_backend/app/observability/otel.py index f39cfe535..e4d4a1fd9 100644 --- a/surfsense_backend/app/observability/otel.py +++ b/surfsense_backend/app/observability/otel.py @@ -244,6 +244,152 @@ def kb_persist_span( return span("kb.persist", attributes=attrs) +def chat_request_span( + *, + chat_id: int | None = None, + search_space_id: int | None = None, + flow: str | None = None, + request_id: str | None = None, + turn_id: str | None = None, + filesystem_mode: str | None = None, + client_platform: str | None = None, + agent_mode: str | None = None, + extra: dict[str, Any] | None = None, +): + """Parent span for a single streamed chat or resume turn.""" + attrs: dict[str, Any] = {} + if chat_id is not None: + attrs["chat.id"] = int(chat_id) + if search_space_id is not None: + attrs["search_space.id"] = int(search_space_id) + if flow: + attrs["chat.flow"] = flow + if request_id: + attrs["request.id"] = request_id + if turn_id: + attrs["turn.id"] = turn_id + if filesystem_mode: + attrs["filesystem.mode"] = filesystem_mode + if client_platform: + attrs["client.platform"] = client_platform + if agent_mode: + attrs["agent.mode"] = agent_mode + if extra: + attrs.update(extra) + return span("chat.request", attributes=attrs) + + +def subagent_invoke_span( + *, + subagent_type: str, + path: str | None = None, + extra: dict[str, Any] | None = None, +): + """Span around invoking a delegated subagent from the main agent.""" + attrs: dict[str, Any] = {"subagent.type": subagent_type} + if path: + attrs["subagent.path"] = path + if extra: + attrs.update(extra) + return span("subagent.invoke", attributes=attrs) + + +def connector_sync_span( + *, + connector_type: str | None, + extra: dict[str, Any] | None = None, +): + """Business-level span around connector indexing task execution.""" + attrs: dict[str, Any] = {"connector.type": connector_type or "unknown"} + if extra: + attrs.update(extra) + return span("connector.sync", attributes=attrs) + + +def etl_extract_span( + *, + content_type: str | None = None, + file_extension: str | None = None, + processing_mode: str | None = None, + extra: dict[str, Any] | None = None, +): + """Span around top-level ETL extraction for a file.""" + attrs: dict[str, Any] = {} + if content_type: + attrs["content.type"] = content_type + if file_extension: + attrs["file.extension"] = file_extension + if processing_mode: + attrs["processing.mode"] = processing_mode + if extra: + attrs.update(extra) + return span("etl.extract", attributes=attrs) + + +def etl_parse_span( + *, + etl_service: str | None, + content_type: str | None = None, + file_extension: str | None = None, + processing_mode: str | None = None, + extra: dict[str, Any] | None = None, +): + """Span around a concrete ETL parser/backend call.""" + attrs: dict[str, Any] = {"etl.service": etl_service or "unknown"} + if content_type: + attrs["content.type"] = content_type + if file_extension: + attrs["file.extension"] = file_extension + if processing_mode: + attrs["processing.mode"] = processing_mode + if extra: + attrs.update(extra) + return span("etl.parse", attributes=attrs) + + +def etl_ocr_span( + *, + etl_service: str | None, + file_extension: str | None = None, + extra: dict[str, Any] | None = None, +): + """Span around OCR extraction from image content.""" + attrs: dict[str, Any] = {"etl.service": etl_service or "unknown"} + if file_extension: + attrs["file.extension"] = file_extension + if extra: + attrs.update(extra) + return span("etl.ocr", attributes=attrs) + + +def etl_picture_describe_span( + *, + image_count: int | None = None, + extra: dict[str, Any] | None = None, +): + """Span around describing embedded images in a document.""" + attrs: dict[str, Any] = {} + if image_count is not None: + attrs["image.count"] = int(image_count) + if extra: + attrs.update(extra) + return span("etl.picture.describe", attributes=attrs) + + +def etl_picture_ocr_span( + *, + file_extension: str | None = None, + extra: dict[str, Any] | None = None, +): + """Span around per-image OCR during picture description.""" + attrs: dict[str, Any] = {} + if file_extension: + attrs["file.extension"] = file_extension + if extra: + attrs.update(extra) + return span("etl.picture.ocr", attributes=attrs) + + def compaction_span( *, reason: str | None = None, @@ -306,7 +452,14 @@ def reload_for_tests() -> bool: __all__ = [ + "chat_request_span", "compaction_span", + "connector_sync_span", + "etl_extract_span", + "etl_ocr_span", + "etl_parse_span", + "etl_picture_describe_span", + "etl_picture_ocr_span", "interrupt_span", "is_enabled", "kb_persist_span", @@ -315,5 +468,6 @@ __all__ = [ "permission_asked_span", "reload_for_tests", "span", + "subagent_invoke_span", "tool_call_span", ] From 5a6b92c2b64a48ecb4a51ab32dd674bb4ac219ee Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 22 May 2026 13:48:19 +0530 Subject: [PATCH 016/198] feat(chat): instrument streamed chat request telemetry --- .../app/tasks/chat/stream_new_chat.py | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/surfsense_backend/app/tasks/chat/stream_new_chat.py b/surfsense_backend/app/tasks/chat/stream_new_chat.py index c9faa1691..b961733b4 100644 --- a/surfsense_backend/app/tasks/chat/stream_new_chat.py +++ b/surfsense_backend/app/tasks/chat/stream_new_chat.py @@ -58,6 +58,7 @@ from app.db import ( async_session_maker, shielded_async_session, ) +from app.observability import metrics as ot_metrics, otel as ot from app.prompts import TITLE_GENERATION_PROMPT from app.services.auto_model_pin_service import ( mark_runtime_cooldown, @@ -883,6 +884,19 @@ async def stream_new_chat( stream_result.turn_id = f"{chat_id}:{int(time.time() * 1000)}" stream_result.filesystem_mode = fs_mode stream_result.client_platform = fs_platform + chat_agent_mode = "unknown" + chat_outcome = "success" + chat_span_cm = ot.chat_request_span( + chat_id=chat_id, + search_space_id=search_space_id, + flow=flow, + request_id=request_id, + turn_id=stream_result.turn_id, + filesystem_mode=fs_mode, + client_platform=fs_platform, + agent_mode=chat_agent_mode, + ) + chat_span = chat_span_cm.__enter__() _log_file_contract("turn_start", stream_result) _perf_log.info( "[stream_new_chat] filesystem_mode=%s client_platform=%s", @@ -1189,6 +1203,9 @@ async def stream_new_chat( from app.config import config as _app_config use_multi_agent = bool(_app_config.MULTI_AGENT_CHAT_ENABLED) + chat_agent_mode = "multi" if use_multi_agent else "single" + with contextlib.suppress(Exception): + chat_span.set_attribute("agent.mode", chat_agent_mode) _t0 = time.perf_counter() agent_factory = ( @@ -2011,6 +2028,10 @@ async def stream_new_chat( user_message, error_extra, ) = _classify_stream_exception(e, flow_label="chat") + chat_outcome = error_code or error_kind or "error" + with contextlib.suppress(Exception): + chat_span.set_attribute("chat.outcome", chat_outcome) + chat_span.record_exception(e) error_message = f"Error during chat: {e!s}" print(f"[stream_new_chat] {error_message}") print(f"[stream_new_chat] Exception type: {type(e).__name__}") @@ -2201,6 +2222,20 @@ async def stream_new_chat( ) trim_native_heap() log_system_snapshot("stream_new_chat_END") + with contextlib.suppress(Exception): + chat_span.set_attribute("chat.outcome", chat_outcome) + ot_metrics.record_chat_request_duration( + (time.perf_counter() - _t_total) * 1000, + flow=flow, + outcome=chat_outcome, + agent_mode=chat_agent_mode, + ) + ot_metrics.record_chat_request_outcome( + flow=flow, + outcome=chat_outcome, + agent_mode=chat_agent_mode, + ) + chat_span_cm.__exit__(None, None, None) async def stream_resume_chat( @@ -2225,6 +2260,19 @@ async def stream_resume_chat( stream_result.turn_id = f"{chat_id}:{int(time.time() * 1000)}" stream_result.filesystem_mode = fs_mode stream_result.client_platform = fs_platform + chat_agent_mode = "unknown" + chat_outcome = "success" + chat_span_cm = ot.chat_request_span( + chat_id=chat_id, + search_space_id=search_space_id, + flow="resume", + request_id=request_id, + turn_id=stream_result.turn_id, + filesystem_mode=fs_mode, + client_platform=fs_platform, + agent_mode=chat_agent_mode, + ) + chat_span = chat_span_cm.__enter__() _log_file_contract("turn_start", stream_result) _perf_log.info( "[stream_resume] filesystem_mode=%s client_platform=%s", @@ -2454,6 +2502,11 @@ async def stream_resume_chat( visibility = thread_visibility or ChatVisibility.PRIVATE from app.config import config as _app_config + chat_agent_mode = ( + "multi" if _app_config.MULTI_AGENT_CHAT_ENABLED else "single" + ) + with contextlib.suppress(Exception): + chat_span.set_attribute("agent.mode", chat_agent_mode) _t0 = time.perf_counter() agent_factory = ( create_multi_agent_chat_deep_agent @@ -2815,6 +2868,10 @@ async def stream_resume_chat( user_message, error_extra, ) = _classify_stream_exception(e, flow_label="resume") + chat_outcome = error_code or error_kind or "error" + with contextlib.suppress(Exception): + chat_span.set_attribute("chat.outcome", chat_outcome) + chat_span.record_exception(e) error_message = f"Error during resume: {e!s}" print(f"[stream_resume_chat] {error_message}") print(f"[stream_resume_chat] Traceback:\n{traceback.format_exc()}") @@ -2964,3 +3021,17 @@ async def stream_resume_chat( ) trim_native_heap() log_system_snapshot("stream_resume_chat_END") + with contextlib.suppress(Exception): + chat_span.set_attribute("chat.outcome", chat_outcome) + ot_metrics.record_chat_request_duration( + (time.perf_counter() - _t_total) * 1000, + flow="resume", + outcome=chat_outcome, + agent_mode=chat_agent_mode, + ) + ot_metrics.record_chat_request_outcome( + flow="resume", + outcome=chat_outcome, + agent_mode=chat_agent_mode, + ) + chat_span_cm.__exit__(None, None, None) From 8bca29fe0d165f763da70bc82d925931f8cf22e8 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 22 May 2026 13:48:57 +0530 Subject: [PATCH 017/198] feat(agents): track subagent invocation telemetry --- .../task_tool.py | 243 ++++++++++++++---- 1 file changed, 199 insertions(+), 44 deletions(-) diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py index f6a9ff146..c3babab83 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py @@ -20,6 +20,7 @@ from langchain_core.tools import StructuredTool from langgraph.errors import GraphInterrupt from langgraph.types import Command, Interrupt +from app.observability import metrics as ot_metrics, otel as ot from app.utils.perf import get_perf_logger from .config import ( @@ -173,6 +174,9 @@ def build_task_tool_with_parent_config( exc_info=True, ) + invoke_path = "resume" if pending_value is not None else "fresh" + invoke_start = time.perf_counter() + invoke_outcome = "ok" if pending_value is not None: resume_value = consume_surfsense_resume(runtime) if resume_value is None: @@ -188,18 +192,94 @@ def build_task_tool_with_parent_config( # Prevent the parent's resume payload from leaking into subagent # interrupts via langgraph's parent_scratchpad fallback. drain_parent_null_resume(runtime) - try: - result = subagent.invoke( - build_resume_command(resume_value, pending_id), - config=sub_config, - ) - except GraphInterrupt as gi: - _reraise_stamped_subagent_interrupt(gi, runtime.tool_call_id) + with ot.subagent_invoke_span( + subagent_type=subagent_type, path=invoke_path + ) as sp: + try: + result = subagent.invoke( + build_resume_command(resume_value, pending_id), + config=sub_config, + ) + sp.set_attribute("subagent.outcome", invoke_outcome) + except GraphInterrupt as gi: + invoke_outcome = "interrupted" + sp.set_attribute("subagent.outcome", invoke_outcome) + ot_metrics.record_subagent_invoke_duration( + (time.perf_counter() - invoke_start) * 1000, + subagent_type=subagent_type, + path=invoke_path, + outcome=invoke_outcome, + ) + ot_metrics.record_subagent_invoke_outcome( + subagent_type=subagent_type, + path=invoke_path, + outcome=invoke_outcome, + ) + _reraise_stamped_subagent_interrupt(gi, runtime.tool_call_id) + except Exception: + invoke_outcome = "error" + sp.set_attribute("subagent.outcome", invoke_outcome) + ot_metrics.record_subagent_invoke_duration( + (time.perf_counter() - invoke_start) * 1000, + subagent_type=subagent_type, + path=invoke_path, + outcome=invoke_outcome, + ) + ot_metrics.record_subagent_invoke_outcome( + subagent_type=subagent_type, + path=invoke_path, + outcome=invoke_outcome, + ) + raise else: - try: - result = subagent.invoke(subagent_state, config=sub_config) - except GraphInterrupt as gi: - _reraise_stamped_subagent_interrupt(gi, runtime.tool_call_id) + with ot.subagent_invoke_span( + subagent_type=subagent_type, path=invoke_path + ) as sp: + try: + result = subagent.invoke(subagent_state, config=sub_config) + sp.set_attribute("subagent.outcome", invoke_outcome) + except GraphInterrupt as gi: + invoke_outcome = "interrupted" + sp.set_attribute("subagent.outcome", invoke_outcome) + ot_metrics.record_subagent_invoke_duration( + (time.perf_counter() - invoke_start) * 1000, + subagent_type=subagent_type, + path=invoke_path, + outcome=invoke_outcome, + ) + ot_metrics.record_subagent_invoke_outcome( + subagent_type=subagent_type, + path=invoke_path, + outcome=invoke_outcome, + ) + _reraise_stamped_subagent_interrupt(gi, runtime.tool_call_id) + except Exception: + invoke_outcome = "error" + sp.set_attribute("subagent.outcome", invoke_outcome) + ot_metrics.record_subagent_invoke_duration( + (time.perf_counter() - invoke_start) * 1000, + subagent_type=subagent_type, + path=invoke_path, + outcome=invoke_outcome, + ) + ot_metrics.record_subagent_invoke_outcome( + subagent_type=subagent_type, + path=invoke_path, + outcome=invoke_outcome, + ) + raise + invoke_elapsed_ms = (time.perf_counter() - invoke_start) * 1000 + ot_metrics.record_subagent_invoke_duration( + invoke_elapsed_ms, + subagent_type=subagent_type, + path=invoke_path, + outcome=invoke_outcome, + ) + ot_metrics.record_subagent_invoke_outcome( + subagent_type=subagent_type, + path=invoke_path, + outcome=invoke_outcome, + ) return _return_command_with_state_update(result, runtime.tool_call_id) async def atask( @@ -274,40 +354,104 @@ def build_task_tool_with_parent_config( # Prevent the parent's resume payload from leaking into subagent # interrupts via langgraph's parent_scratchpad fallback. drain_parent_null_resume(runtime) - try: - result = await subagent.ainvoke( - build_resume_command(resume_value, pending_id), - config=sub_config, - ) - except GraphInterrupt as gi: - ainvoke_outcome = "interrupted" - _perf_log.info( - "[hitl_route] atask EXIT subagent_type=%r path=%s outcome=%s " - "aget_state=%.3fs ainvoke=%.3fs total=%.3fs", - subagent_type, - invoke_path, - ainvoke_outcome, - aget_state_elapsed, - time.perf_counter() - ainvoke_start, - time.perf_counter() - atask_start, - ) - _reraise_stamped_subagent_interrupt(gi, runtime.tool_call_id) + with ot.subagent_invoke_span( + subagent_type=subagent_type, path=invoke_path + ) as sp: + try: + result = await subagent.ainvoke( + build_resume_command(resume_value, pending_id), + config=sub_config, + ) + sp.set_attribute("subagent.outcome", ainvoke_outcome) + except GraphInterrupt as gi: + ainvoke_outcome = "interrupted" + sp.set_attribute("subagent.outcome", ainvoke_outcome) + ot_metrics.record_subagent_invoke_duration( + (time.perf_counter() - ainvoke_start) * 1000, + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + ot_metrics.record_subagent_invoke_outcome( + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + _perf_log.info( + "[hitl_route] atask EXIT subagent_type=%r path=%s outcome=%s " + "aget_state=%.3fs ainvoke=%.3fs total=%.3fs", + subagent_type, + invoke_path, + ainvoke_outcome, + aget_state_elapsed, + time.perf_counter() - ainvoke_start, + time.perf_counter() - atask_start, + ) + _reraise_stamped_subagent_interrupt(gi, runtime.tool_call_id) + except Exception: + ainvoke_outcome = "error" + sp.set_attribute("subagent.outcome", ainvoke_outcome) + ot_metrics.record_subagent_invoke_duration( + (time.perf_counter() - ainvoke_start) * 1000, + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + ot_metrics.record_subagent_invoke_outcome( + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + raise else: - try: - result = await subagent.ainvoke(subagent_state, config=sub_config) - except GraphInterrupt as gi: - ainvoke_outcome = "interrupted" - _perf_log.info( - "[hitl_route] atask EXIT subagent_type=%r path=%s outcome=%s " - "aget_state=%.3fs ainvoke=%.3fs total=%.3fs", - subagent_type, - invoke_path, - ainvoke_outcome, - aget_state_elapsed, - time.perf_counter() - ainvoke_start, - time.perf_counter() - atask_start, - ) - _reraise_stamped_subagent_interrupt(gi, runtime.tool_call_id) + with ot.subagent_invoke_span( + subagent_type=subagent_type, path=invoke_path + ) as sp: + try: + result = await subagent.ainvoke( + subagent_state, config=sub_config + ) + sp.set_attribute("subagent.outcome", ainvoke_outcome) + except GraphInterrupt as gi: + ainvoke_outcome = "interrupted" + sp.set_attribute("subagent.outcome", ainvoke_outcome) + ot_metrics.record_subagent_invoke_duration( + (time.perf_counter() - ainvoke_start) * 1000, + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + ot_metrics.record_subagent_invoke_outcome( + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + _perf_log.info( + "[hitl_route] atask EXIT subagent_type=%r path=%s outcome=%s " + "aget_state=%.3fs ainvoke=%.3fs total=%.3fs", + subagent_type, + invoke_path, + ainvoke_outcome, + aget_state_elapsed, + time.perf_counter() - ainvoke_start, + time.perf_counter() - atask_start, + ) + _reraise_stamped_subagent_interrupt(gi, runtime.tool_call_id) + except Exception: + ainvoke_outcome = "error" + sp.set_attribute("subagent.outcome", ainvoke_outcome) + ot_metrics.record_subagent_invoke_duration( + (time.perf_counter() - ainvoke_start) * 1000, + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + ot_metrics.record_subagent_invoke_outcome( + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + raise ainvoke_elapsed = time.perf_counter() - ainvoke_start except GraphInterrupt: raise @@ -326,6 +470,17 @@ def build_task_tool_with_parent_config( merge_elapsed, time.perf_counter() - atask_start, ) + ot_metrics.record_subagent_invoke_duration( + ainvoke_elapsed * 1000, + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + ot_metrics.record_subagent_invoke_outcome( + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) return cmd return StructuredTool.from_function( From 4e3a6dff465f979c3705a53942695ad2f899c7ee Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 22 May 2026 13:49:42 +0530 Subject: [PATCH 018/198] feat(etl): instrument extraction spans and outcomes --- .../app/etl_pipeline/etl_pipeline_service.py | 176 ++++++++++++------ 1 file changed, 124 insertions(+), 52 deletions(-) diff --git a/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py b/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py index 87e8138fd..4cdd387b0 100644 --- a/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py +++ b/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py @@ -1,4 +1,7 @@ +import contextlib import logging +import time +from pathlib import PurePosixPath from app.config import config as app_config from app.etl_pipeline.etl_document import EtlRequest, EtlResult @@ -10,6 +13,11 @@ from app.etl_pipeline.file_classifier import FileCategory, classify_file from app.etl_pipeline.parsers.audio import transcribe_audio from app.etl_pipeline.parsers.direct_convert import convert_file_directly from app.etl_pipeline.parsers.plaintext import read_plaintext +from app.observability import metrics as ot_metrics, otel as ot + + +def _file_extension(filename: str) -> str: + return PurePosixPath(filename).suffix.lower() or "none" class EtlPipelineService: @@ -20,49 +28,88 @@ class EtlPipelineService: async def extract(self, request: EtlRequest) -> EtlResult: category = classify_file(request.filename) + start = time.perf_counter() + status = "success" + result: EtlResult | None = None + with ot.etl_extract_span( + content_type=category.value, + file_extension=_file_extension(request.filename), + processing_mode=request.processing_mode.value, + ) as sp: + try: + if category == FileCategory.UNSUPPORTED: + raise EtlUnsupportedFileError( + f"File type not supported for parsing: {request.filename}" + ) - if category == FileCategory.UNSUPPORTED: - raise EtlUnsupportedFileError( - f"File type not supported for parsing: {request.filename}" - ) + if category == FileCategory.PLAINTEXT: + content = read_plaintext(request.file_path) + result = EtlResult( + markdown_content=content, + etl_service="PLAINTEXT", + content_type="plaintext", + ) + return result - if category == FileCategory.PLAINTEXT: - content = read_plaintext(request.file_path) - return EtlResult( - markdown_content=content, - etl_service="PLAINTEXT", - content_type="plaintext", - ) + if category == FileCategory.DIRECT_CONVERT: + content = convert_file_directly(request.file_path, request.filename) + result = EtlResult( + markdown_content=content, + etl_service="DIRECT_CONVERT", + content_type="direct_convert", + ) + return result - if category == FileCategory.DIRECT_CONVERT: - content = convert_file_directly(request.file_path, request.filename) - return EtlResult( - markdown_content=content, - etl_service="DIRECT_CONVERT", - content_type="direct_convert", - ) + if category == FileCategory.AUDIO: + content = await transcribe_audio(request.file_path, request.filename) + result = EtlResult( + markdown_content=content, + etl_service="AUDIO", + content_type="audio", + ) + return result - if category == FileCategory.AUDIO: - content = await transcribe_audio(request.file_path, request.filename) - return EtlResult( - markdown_content=content, - etl_service="AUDIO", - content_type="audio", - ) + if category == FileCategory.IMAGE: + result = await self._extract_image(request) + return result - if category == FileCategory.IMAGE: - return await self._extract_image(request) - - return await self._extract_document(request) + result = await self._extract_document(request) + return result + except Exception: + status = "error" + raise + finally: + with contextlib.suppress(Exception): + if result is not None: + sp.set_attribute("etl.service", result.etl_service) + sp.set_attribute("content.type", result.content_type) + sp.set_attribute("etl.status", status) + ot_metrics.record_etl_extract_duration( + time.perf_counter() - start, + etl_service=result.etl_service if result else None, + content_type=result.content_type if result else category.value, + status=status, + ) + ot_metrics.record_etl_extract_outcome( + etl_service=result.etl_service if result else None, + content_type=result.content_type if result else category.value, + status=status, + ) async def _extract_image(self, request: EtlRequest) -> EtlResult: if self._vision_llm: try: from app.etl_pipeline.parsers.vision_llm import parse_with_vision_llm - content = await parse_with_vision_llm( - request.file_path, request.filename, self._vision_llm - ) + with ot.etl_parse_span( + etl_service="VISION_LLM", + content_type="image", + file_extension=_file_extension(request.filename), + ) as sp: + content = await parse_with_vision_llm( + request.file_path, request.filename, self._vision_llm + ) + sp.set_attribute("etl.status", "success") return EtlResult( markdown_content=content, etl_service="VISION_LLM", @@ -94,7 +141,11 @@ class EtlPipelineService: ) try: - return await self._extract_document(request) + with ot.etl_ocr_span( + etl_service=app_config.ETL_SERVICE, + file_extension=_file_extension(request.filename), + ): + return await self._extract_document(request) except (EtlUnsupportedFileError, EtlServiceUnavailableError): raise EtlUnsupportedFileError( f"Cannot process image {request.filename}: vision LLM " @@ -121,18 +172,27 @@ class EtlPipelineService: f"File type {ext} is not supported by {etl_service}" ) - if etl_service == "DOCLING": - from app.etl_pipeline.parsers.docling import parse_with_docling + with ot.etl_parse_span( + etl_service=etl_service, + content_type="document", + file_extension=ext, + processing_mode=request.processing_mode.value, + ) as sp: + if etl_service == "DOCLING": + from app.etl_pipeline.parsers.docling import parse_with_docling - content = await parse_with_docling(request.file_path, request.filename) - elif etl_service == "UNSTRUCTURED": - from app.etl_pipeline.parsers.unstructured import parse_with_unstructured + content = await parse_with_docling(request.file_path, request.filename) + elif etl_service == "UNSTRUCTURED": + from app.etl_pipeline.parsers.unstructured import ( + parse_with_unstructured, + ) - content = await parse_with_unstructured(request.file_path) - elif etl_service == "LLAMACLOUD": - content = await self._extract_with_llamacloud(request) - else: - raise EtlServiceUnavailableError(f"Unknown ETL_SERVICE: {etl_service}") + content = await parse_with_unstructured(request.file_path) + elif etl_service == "LLAMACLOUD": + content = await self._extract_with_llamacloud(request) + else: + raise EtlServiceUnavailableError(f"Unknown ETL_SERVICE: {etl_service}") + sp.set_attribute("etl.status", "success") # When the operator opts into vision-LLM at ingest, walk the # original file's embedded images and append a structured @@ -171,9 +231,14 @@ class EtlPipelineService: async def _ocr_image(image_path: str, image_name: str) -> str: try: sub = EtlPipelineService(vision_llm=None) - ocr_result = await sub.extract( - EtlRequest(file_path=image_path, filename=image_name) - ) + with ot.etl_picture_ocr_span( + file_extension=_file_extension(image_name) + ) as sp: + ocr_result = await sub.extract( + EtlRequest(file_path=image_path, filename=image_name) + ) + sp.set_attribute("etl.service", ocr_result.etl_service) + sp.set_attribute("etl.status", "success") except ( EtlUnsupportedFileError, EtlServiceUnavailableError, @@ -186,12 +251,19 @@ class EtlPipelineService: return ocr_result.markdown_content try: - result = await describe_pictures( - request.file_path, - request.filename, - self._vision_llm, - ocr_runner=_ocr_image, - ) + with ot.etl_picture_describe_span() as sp: + result = await describe_pictures( + request.file_path, + request.filename, + self._vision_llm, + ocr_runner=_ocr_image, + ) + sp.set_attribute("image.described.count", len(result.descriptions)) + sp.set_attribute("image.failed.count", result.failed) + sp.set_attribute("image.skipped.too_small", result.skipped_too_small) + sp.set_attribute("image.skipped.too_large", result.skipped_too_large) + sp.set_attribute("image.skipped.duplicate", result.skipped_duplicate) + sp.set_attribute("etl.status", "success") except Exception: # Picture description is additive; never let it fail an # otherwise-successful document extraction. From 7c07c220fc3dc7b0935cf080b5dbd9a2310a561f Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 22 May 2026 13:49:59 +0530 Subject: [PATCH 019/198] feat(connectors): add connector sync spans --- .../app/tasks/celery_tasks/connector_tasks.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/surfsense_backend/app/tasks/celery_tasks/connector_tasks.py b/surfsense_backend/app/tasks/celery_tasks/connector_tasks.py index 86296c3d9..e0f0f09c9 100644 --- a/surfsense_backend/app/tasks/celery_tasks/connector_tasks.py +++ b/surfsense_backend/app/tasks/celery_tasks/connector_tasks.py @@ -8,7 +8,7 @@ from collections.abc import Awaitable, Callable from celery import current_task from app.celery_app import celery_app -from app.observability import metrics as ot_metrics +from app.observability import metrics as ot_metrics, otel as ot from app.tasks.celery_tasks import ( get_celery_session_maker, run_async_celery_task as _run_async_celery_task, @@ -23,9 +23,14 @@ def run_async_celery_task[T](coro_factory: Callable[[], Awaitable[T]]) -> T: t0 = time.perf_counter() status = "failed" try: - result = _run_async_celery_task(coro_factory) + with ot.connector_sync_span(connector_type=task_name) as sp: + result = _run_async_celery_task(coro_factory) + sp.set_attribute("connector.status", "success") status = "success" return result + except Exception: + status = "failed" + raise finally: elapsed_s = time.perf_counter() - t0 ot_metrics.record_connector_sync_duration( From 87a4dcfd054d8f0d83d04bde156e047aceba6e46 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 22 May 2026 13:50:32 +0530 Subject: [PATCH 020/198] feat(tasks): record indexing heartbeat metrics --- .../app/routes/search_source_connectors_routes.py | 15 +++++++++++++++ .../app/tasks/celery_tasks/document_tasks.py | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/surfsense_backend/app/routes/search_source_connectors_routes.py b/surfsense_backend/app/routes/search_source_connectors_routes.py index 1338fe16b..4d7e6b2ef 100644 --- a/surfsense_backend/app/routes/search_source_connectors_routes.py +++ b/surfsense_backend/app/routes/search_source_connectors_routes.py @@ -43,6 +43,7 @@ from app.db import ( async_session_maker, get_async_session, ) +from app.observability import metrics as ot_metrics from app.schemas import ( GoogleDriveIndexRequest, MCPConnectorCreate, @@ -104,7 +105,9 @@ async def _run_indexing_heartbeat_loop(notification_id: int) -> None: await asyncio.sleep(HEARTBEAT_REFRESH_INTERVAL) try: get_heartbeat_redis_client().setex(key, HEARTBEAT_TTL_SECONDS, "alive") + ot_metrics.record_celery_heartbeat_refresh(heartbeat_type="connector") except Exception as e: + ot_metrics.record_celery_heartbeat_failure(heartbeat_type="connector") logger.warning( f"Failed to refresh Redis heartbeat for notification " f"{notification_id}: {e}" @@ -1338,7 +1341,13 @@ async def _run_indexing_with_notifications( get_heartbeat_redis_client().setex( heartbeat_key, HEARTBEAT_TTL_SECONDS, "0" ) + ot_metrics.record_celery_heartbeat_refresh( + heartbeat_type="connector" + ) except Exception as e: + ot_metrics.record_celery_heartbeat_failure( + heartbeat_type="connector" + ) logger.warning(f"Failed to set initial Redis heartbeat: {e}") # Start a background coroutine that refreshes the @@ -1397,8 +1406,14 @@ async def _run_indexing_with_notifications( get_heartbeat_redis_client().setex( heartbeat_key, HEARTBEAT_TTL_SECONDS, str(indexed_count) ) + ot_metrics.record_celery_heartbeat_refresh( + heartbeat_type="connector" + ) except Exception as e: # Don't let Redis errors break the indexing + ot_metrics.record_celery_heartbeat_failure( + heartbeat_type="connector" + ) logger.warning(f"Failed to set Redis heartbeat: {e}") try: diff --git a/surfsense_backend/app/tasks/celery_tasks/document_tasks.py b/surfsense_backend/app/tasks/celery_tasks/document_tasks.py index c78e376bd..1f9609968 100644 --- a/surfsense_backend/app/tasks/celery_tasks/document_tasks.py +++ b/surfsense_backend/app/tasks/celery_tasks/document_tasks.py @@ -9,6 +9,7 @@ from uuid import UUID from app.celery_app import celery_app from app.config import config +from app.observability import metrics as ot_metrics from app.services.notification_service import NotificationService from app.services.task_logging_service import TaskLoggingService from app.tasks.celery_tasks import get_celery_session_maker, run_async_celery_task @@ -59,7 +60,9 @@ def _start_heartbeat(notification_id: int) -> None: try: key = _get_heartbeat_key(notification_id) _get_doc_heartbeat_redis().setex(key, HEARTBEAT_TTL_SECONDS, "started") + ot_metrics.record_celery_heartbeat_refresh(heartbeat_type="document") except Exception as e: + ot_metrics.record_celery_heartbeat_failure(heartbeat_type="document") logger.warning( f"Failed to set initial heartbeat for notification {notification_id}: {e}" ) @@ -87,7 +90,9 @@ async def _run_heartbeat_loop(notification_id: int): await asyncio.sleep(HEARTBEAT_REFRESH_INTERVAL) try: _get_doc_heartbeat_redis().setex(key, HEARTBEAT_TTL_SECONDS, "alive") + ot_metrics.record_celery_heartbeat_refresh(heartbeat_type="document") except Exception as e: + ot_metrics.record_celery_heartbeat_failure(heartbeat_type="document") logger.warning( f"Failed to refresh heartbeat for notification {notification_id}: {e}" ) From 0fe2bba5a8f41459ada4e3648b811d20e8652a13 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 22 May 2026 17:46:35 +0530 Subject: [PATCH 021/198] refactor(chat): replace TooltipIconButton with Button for scroll functionality --- .../components/assistant-ui/chat-viewport.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/surfsense_web/components/assistant-ui/chat-viewport.tsx b/surfsense_web/components/assistant-ui/chat-viewport.tsx index cb0b484ef..cb0fd2005 100644 --- a/surfsense_web/components/assistant-ui/chat-viewport.tsx +++ b/surfsense_web/components/assistant-ui/chat-viewport.tsx @@ -3,16 +3,19 @@ import { ThreadPrimitive } from "@assistant-ui/react"; import { ArrowDownIcon } from "lucide-react"; import type { FC, ReactNode } from "react"; -import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import { Button } from "@/components/ui/button"; const ChatScrollToBottom: FC = () => ( - - + ); From dbb652d4f87f502e3224cc40ee50e2d80ea4ec2b Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 22 May 2026 17:48:01 +0530 Subject: [PATCH 022/198] feat(observability): add telemetry error and event helpers --- .../app/observability/metrics.py | 104 ++++++++++++++++-- surfsense_backend/app/observability/otel.py | 44 ++++++++ .../tests/unit/observability/test_helpers.py | 101 +++++++++++++++++ 3 files changed, 241 insertions(+), 8 deletions(-) create mode 100644 surfsense_backend/tests/unit/observability/test_helpers.py diff --git a/surfsense_backend/app/observability/metrics.py b/surfsense_backend/app/observability/metrics.py index 53beb959d..798a6e2f7 100644 --- a/surfsense_backend/app/observability/metrics.py +++ b/surfsense_backend/app/observability/metrics.py @@ -20,6 +20,19 @@ logger = logging.getLogger(__name__) _INSTRUMENTATION_NAME = "surfsense.platform" _OBSERVABLES_REGISTERED = False +_ERROR_CATEGORY_UNKNOWN = "unknown" + +_ERROR_CATEGORY_HINTS: tuple[tuple[str, tuple[str, ...]], ...] = ( + ("rate_limited", ("ratelimit", "rate_limit", "toomanyrequests", "429")), + ("auth_failed", ("authentication", "auth", "unauthorized", "forbidden")), + ("quota_exhausted", ("quota", "insufficient", "credit", "billing")), + ("timeout", ("timeout", "timedout", "deadline")), + ("network_failed", ("connection", "connect", "network", "dns", "socket")), + ("server_error", ("internalserver", "serviceunavailable", "badgateway", "gateway")), + ("lock_contention", ("lock", "busy", "contention", "alreadyrunning")), + ("unsupported_format", ("unsupported", "format", "filetype")), + ("provider_error", ("provider", "apierror", "apistatus", "badrequest")), +) def _package_version() -> str: @@ -47,6 +60,36 @@ def _clean_attrs(attrs: dict[str, Any]) -> dict[str, str | int | float | bool]: return cleaned +def _attrs_with_optional_error_category( + attrs: dict[str, Any], error_category: str | None +) -> dict[str, Any]: + if error_category: + return {**attrs, "error.category": error_category} + return attrs + + +def categorize_exception(exc: BaseException | None) -> str: + """Return a low-cardinality category for an exception.""" + if exc is None: + return _ERROR_CATEGORY_UNKNOWN + haystack = " ".join( + cls.__name__.replace("-", "").replace("_", "").lower() + for cls in type(exc).__mro__ + ) + for category, hints in _ERROR_CATEGORY_HINTS: + if any(hint in haystack for hint in hints): + return category + return _ERROR_CATEGORY_UNKNOWN + + +def parse_celery_task_label(task_name: str | None) -> str: + """Return the operation token from a Celery task name.""" + if not task_name: + return "unknown" + operation = str(task_name).split("_", 1)[0].strip() + return operation or "unknown" + + def _record(callable_obj: Any, value: int | float, attrs: dict[str, Any]) -> None: if not _is_enabled(): return @@ -262,6 +305,15 @@ def _celery_heartbeat_failures(): ) +@lru_cache(maxsize=1) +def _celery_queue_latency(): + return _get_meter().create_histogram( + "surfsense.celery.queue.latency", + unit="s", + description="Time SurfSense Celery tasks spend waiting in queue.", + ) + + def record_model_call_duration( duration_ms: float, *, model: str | None, provider: str | None ) -> None: @@ -359,11 +411,16 @@ def record_connector_sync_duration( ) -def record_connector_sync_outcome(*, connector_type: str | None, status: str) -> None: +def record_connector_sync_outcome( + *, connector_type: str | None, status: str, error_category: str | None = None +) -> None: _add( _connector_sync_outcome(), 1, - {"connector.type": connector_type or "unknown", "status": status}, + _attrs_with_optional_error_category( + {"connector.type": connector_type or "unknown", "status": status}, + error_category, + ), ) @@ -398,11 +455,15 @@ def record_chat_request_outcome( flow: str, outcome: str, agent_mode: str | None = None, + error_category: str | None = None, ) -> None: _add( _chat_request_outcome(), 1, - {"chat.flow": flow, "outcome": outcome, "agent.mode": agent_mode}, + _attrs_with_optional_error_category( + {"chat.flow": flow, "outcome": outcome, "agent.mode": agent_mode}, + error_category, + ), ) @@ -464,15 +525,19 @@ def record_etl_extract_outcome( etl_service: str | None, content_type: str | None, status: str, + error_category: str | None = None, ) -> None: _add( _etl_extract_outcome(), 1, - { - "etl.service": etl_service or "unknown", - "content.type": content_type or "unknown", - "status": status, - }, + _attrs_with_optional_error_category( + { + "etl.service": etl_service or "unknown", + "content.type": content_type or "unknown", + "status": status, + }, + error_category, + ), ) @@ -484,6 +549,26 @@ def record_celery_heartbeat_failure(*, heartbeat_type: str) -> None: _add(_celery_heartbeat_failures(), 1, {"heartbeat.type": heartbeat_type}) +def record_celery_queue_latency( + duration_s: float, + *, + task_name: str | None, + queue: str | None, + scheduled: bool, + operation: str | None, +) -> None: + _record( + _celery_queue_latency(), + duration_s, + { + "task.name": task_name or "unknown", + "task.queue": queue or "unknown", + "task.scheduled": bool(scheduled), + "operation": operation or "unknown", + }, + ) + + def _runtime_snapshot_value(key: str, transform: Any = None) -> list[Any]: from opentelemetry.metrics import Observation @@ -569,9 +654,12 @@ def register_runtime_observables() -> None: __all__ = [ + "categorize_exception", + "parse_celery_task_label", "record_auth_failure", "record_celery_heartbeat_failure", "record_celery_heartbeat_refresh", + "record_celery_queue_latency", "record_chat_request_duration", "record_chat_request_outcome", "record_compaction_run", diff --git a/surfsense_backend/app/observability/otel.py b/surfsense_backend/app/observability/otel.py index e4d4a1fd9..ad2178f39 100644 --- a/surfsense_backend/app/observability/otel.py +++ b/surfsense_backend/app/observability/otel.py @@ -92,6 +92,48 @@ def is_enabled() -> bool: return _ENABLED +def _clean_event_attrs(attrs: dict[str, Any]) -> dict[str, str | int | float | bool]: + """Coerce event attributes to OTel-safe scalar values.""" + cleaned: dict[str, str | int | float | bool] = {} + for key, value in attrs.items(): + if value is None: + continue + if isinstance(value, bool | int | float): + cleaned[key] = value + continue + text = str(value) + if text: + cleaned[key] = text + return cleaned + + +def add_event(name: str, attributes: dict[str, Any] | None = None) -> None: + """Attach an event to the current active span. + + This is intentionally no-op and exception-safe when OTel is disabled, + unavailable, or no span is currently recording. + """ + if not _ENABLED or _ot_trace is None: + return + with contextlib.suppress(Exception): + sp = _ot_trace.get_current_span() + if sp is None or not sp.is_recording(): + return + sp.add_event( + name, + attributes=_clean_event_attrs(attributes) if attributes else None, + ) + + +def record_error(span_obj: Any, exc: BaseException) -> None: + """Record an exception and mark a span as errored without re-raising.""" + if not _ENABLED: + return + with contextlib.suppress(Exception): + span_obj.record_exception(exc) + span_obj.set_status(_OtStatus(_OtStatusCode.ERROR, str(exc))) + + def _get_tracer(): if not _OTEL_AVAILABLE: return None @@ -452,6 +494,7 @@ def reload_for_tests() -> bool: __all__ = [ + "add_event", "chat_request_span", "compaction_span", "connector_sync_span", @@ -466,6 +509,7 @@ __all__ = [ "kb_search_span", "model_call_span", "permission_asked_span", + "record_error", "reload_for_tests", "span", "subagent_invoke_span", diff --git a/surfsense_backend/tests/unit/observability/test_helpers.py b/surfsense_backend/tests/unit/observability/test_helpers.py new file mode 100644 index 000000000..ae60c1939 --- /dev/null +++ b/surfsense_backend/tests/unit/observability/test_helpers.py @@ -0,0 +1,101 @@ +"""Tests for pure observability helper functions.""" + +from __future__ import annotations + +import pytest + +from app.observability import metrics as ot_metrics, otel as ot + +pytestmark = pytest.mark.unit + + +@pytest.fixture(autouse=True) +def _disable_otel(monkeypatch: pytest.MonkeyPatch): + monkeypatch.delenv("OTEL_EXPORTER_OTLP_ENDPOINT", raising=False) + monkeypatch.setenv("SURFSENSE_DISABLE_OTEL", "true") + ot.reload_for_tests() + yield + ot.reload_for_tests() + + +@pytest.mark.parametrize( + ("task_name", "expected"), + [ + ("reindex_document", "reindex"), + ("delete_document_background", "delete"), + ("delete_folder_documents_background", "delete"), + ("delete_search_space_background", "delete"), + ("process_extension_document", "process"), + ("process_youtube_video", "process"), + ("process_file_upload", "process"), + ("process_file_upload_with_document", "process"), + ("process_circleback_meeting", "process"), + ("generate_video_presentation", "generate"), + ("generate_content_podcast", "generate"), + ("cleanup_stale_indexing_notifications", "cleanup"), + ("reconcile_pending_stripe_page_purchases", "reconcile"), + ("reconcile_pending_stripe_token_purchases", "reconcile"), + ("check_periodic_schedules", "check"), + ("ai_sort_search_space", "ai"), + ("index_notion_pages", "index"), + ("index_github_repos", "index"), + ("index_google_drive_files", "index"), + ("index_composio_connector", "index"), + ("index_obsidian_attachment", "index"), + ("index_local_folder", "index"), + ("index_uploaded_folder_files", "index"), + ("noseparator", "noseparator"), + ("", "unknown"), + ], +) +def test_parse_celery_task_label(task_name: str, expected: str) -> None: + assert ot_metrics.parse_celery_task_label(task_name) == expected + + +def test_parse_celery_task_label_handles_none() -> None: + assert ot_metrics.parse_celery_task_label(None) == "unknown" + + +@pytest.mark.parametrize( + ("exc", "expected"), + [ + (type("RateLimitError", (Exception,), {})(), "rate_limited"), + (type("AuthenticationError", (Exception,), {})(), "auth_failed"), + (type("QuotaInsufficientError", (Exception,), {})(), "quota_exhausted"), + (TimeoutError(), "timeout"), + (type("APIConnectionError", (Exception,), {})(), "network_failed"), + (type("ServiceUnavailableError", (Exception,), {})(), "server_error"), + (type("LockContentionError", (Exception,), {})(), "lock_contention"), + (type("UnsupportedFormatError", (Exception,), {})(), "unsupported_format"), + (type("ProviderError", (Exception,), {})(), "provider_error"), + (RuntimeError("plain"), "unknown"), + ], +) +def test_categorize_exception(exc: BaseException, expected: str) -> None: + assert ot_metrics.categorize_exception(exc) == expected + + +def test_record_celery_queue_latency_noops_when_disabled() -> None: + ot_metrics.record_celery_queue_latency( + 0.5, + task_name="index_notion_pages", + queue="surfsense.connectors", + scheduled=False, + operation="index", + ) + + +def test_add_event_noops_when_disabled() -> None: + ot.add_event("test.event", {"value": 1}) + + +def test_add_event_noops_without_current_span(monkeypatch: pytest.MonkeyPatch) -> None: + class FakeTrace: + @staticmethod + def get_current_span(): + return None + + monkeypatch.setattr(ot, "_ENABLED", True) + monkeypatch.setattr(ot, "_ot_trace", FakeTrace()) + + ot.add_event("test.event", {"value": 1}) From dc893281ba8ed27e55fbb0a89b88a9096b2e3722 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 22 May 2026 17:48:43 +0530 Subject: [PATCH 023/198] feat(chat): add model retry and stream lifecycle events --- .../agents/new_chat/middleware/retry_after.py | 20 ++++ .../middleware/scoped_model_fallback.py | 24 ++++- .../app/tasks/chat/stream_new_chat.py | 100 +++++++++++++++++- 3 files changed, 138 insertions(+), 6 deletions(-) diff --git a/surfsense_backend/app/agents/new_chat/middleware/retry_after.py b/surfsense_backend/app/agents/new_chat/middleware/retry_after.py index 0c3d3d017..321185dee 100644 --- a/surfsense_backend/app/agents/new_chat/middleware/retry_after.py +++ b/surfsense_backend/app/agents/new_chat/middleware/retry_after.py @@ -45,6 +45,8 @@ from langchain.agents.middleware.types import ( from langchain_core.callbacks import adispatch_custom_event, dispatch_custom_event from langchain_core.messages import AIMessage +from app.observability import metrics as ot_metrics, otel as ot + logger = logging.getLogger(__name__) # Names of exception classes for which a retry would not help — context @@ -198,6 +200,15 @@ class RetryAfterMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, Resp if not self._should_retry(exc) or attempt >= self.max_retries: raise delay = self._delay_for_attempt(attempt, exc) + ot.add_event( + "model.retry.scheduled", + { + "retry.attempt": attempt + 1, + "retry.max": self.max_retries, + "retry.delay_ms": int(delay * 1000), + "retry.reason": ot_metrics.categorize_exception(exc), + }, + ) try: dispatch_custom_event( "surfsense.retrying", @@ -231,6 +242,15 @@ class RetryAfterMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, Resp if not self._should_retry(exc) or attempt >= self.max_retries: raise delay = self._delay_for_attempt(attempt, exc) + ot.add_event( + "model.retry.scheduled", + { + "retry.attempt": attempt + 1, + "retry.max": self.max_retries, + "retry.delay_ms": int(delay * 1000), + "retry.reason": ot_metrics.categorize_exception(exc), + }, + ) try: await adispatch_custom_event( "surfsense.retrying", diff --git a/surfsense_backend/app/agents/new_chat/middleware/scoped_model_fallback.py b/surfsense_backend/app/agents/new_chat/middleware/scoped_model_fallback.py index 99eb2d74a..0294e2839 100644 --- a/surfsense_backend/app/agents/new_chat/middleware/scoped_model_fallback.py +++ b/surfsense_backend/app/agents/new_chat/middleware/scoped_model_fallback.py @@ -6,6 +6,8 @@ from typing import TYPE_CHECKING, Any from langchain.agents.middleware import ModelFallbackMiddleware +from app.observability import metrics as ot_metrics, otel as ot + if TYPE_CHECKING: from collections.abc import Awaitable, Callable @@ -55,7 +57,16 @@ class ScopedModelFallbackMiddleware(ModelFallbackMiddleware): raise last_exception = e - for fallback_model in self.models: + for attempt, fallback_model in enumerate(self.models, start=1): + ot.add_event( + "model.fallback", + { + "fallback.attempt": attempt, + "fallback.from": attempt - 1, + "fallback.to": attempt, + "fallback.reason": ot_metrics.categorize_exception(last_exception), + }, + ) try: return handler(request.override(model=fallback_model)) except Exception as e: @@ -79,7 +90,16 @@ class ScopedModelFallbackMiddleware(ModelFallbackMiddleware): raise last_exception = e - for fallback_model in self.models: + for attempt, fallback_model in enumerate(self.models, start=1): + ot.add_event( + "model.fallback", + { + "fallback.attempt": attempt, + "fallback.from": attempt - 1, + "fallback.to": attempt, + "fallback.reason": ot_metrics.categorize_exception(last_exception), + }, + ) try: return await handler(request.override(model=fallback_model)) except Exception as e: diff --git a/surfsense_backend/app/tasks/chat/stream_new_chat.py b/surfsense_backend/app/tasks/chat/stream_new_chat.py index b961733b4..fee50d72d 100644 --- a/surfsense_backend/app/tasks/chat/stream_new_chat.py +++ b/surfsense_backend/app/tasks/chat/stream_new_chat.py @@ -14,6 +14,7 @@ import contextlib import gc import json import logging +import sys import time from collections.abc import AsyncGenerator from dataclasses import dataclass, field @@ -886,6 +887,7 @@ async def stream_new_chat( stream_result.client_platform = fs_platform chat_agent_mode = "unknown" chat_outcome = "success" + chat_error_category: str | None = None chat_span_cm = ot.chat_request_span( chat_id=chat_id, search_space_id=search_space_id, @@ -985,6 +987,14 @@ async def stream_new_chat( requires_image_input=_requires_image_input, ) ).resolved_llm_config_id + ot.add_event( + "model.pin.resolved", + { + "pin.requested_id": requested_llm_config_id, + "pin.resolved_id": llm_config_id, + "pin.requires_image_input": _requires_image_input, + }, + ) except ValueError as pin_error: # Auto-pin's "no vision-capable cfg" path raises a ValueError # whose message we map to the friendly image-input SSE error @@ -1001,6 +1011,13 @@ async def stream_new_chat( if error_code == "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT" else "server_error" ) + if error_code == "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT": + ot.add_event( + "quota.denied", + { + "quota.code": error_code, + }, + ) yield _emit_stream_error( message=str(pin_error), error_kind=error_kind, @@ -1055,6 +1072,12 @@ async def stream_new_chat( model_label = ( agent_config.config_name or agent_config.model_name or "model" ) + ot.add_event( + "quota.denied", + { + "quota.code": "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT", + }, + ) yield _emit_stream_error( message=( f"The selected model ({model_label}) does not support " @@ -1098,6 +1121,12 @@ async def stream_new_chat( ) _premium_reserved_micros = reserve_amount_micros if not quota_result.allowed: + ot.add_event( + "quota.denied", + { + "quota.code": "PREMIUM_QUOTA_EXHAUSTED", + }, + ) if requested_llm_config_id == 0: try: llm_config_id = ( @@ -1111,6 +1140,13 @@ async def stream_new_chat( requires_image_input=_requires_image_input, ) ).resolved_llm_config_id + ot.add_event( + "model.repin", + { + "repin.reason": "premium_quota_exhausted", + "repin.to_config_id": llm_config_id, + }, + ) except ValueError as pin_error: yield _emit_stream_error( message=str(pin_error), @@ -1880,6 +1916,14 @@ async def stream_new_chat( llm_config_id, time.perf_counter() - _t0, ) + ot.add_event( + "chat.rate_limit.recovered", + { + "recovery.reason": "provider_rate_limited", + "recovery.previous_config_id": previous_config_id, + "recovery.fallback_config_id": llm_config_id, + }, + ) _log_chat_stream_error( flow=flow, error_kind="rate_limited", @@ -1910,6 +1954,12 @@ async def stream_new_chat( log_system_snapshot("stream_new_chat_END") if stream_result.is_interrupted: + ot.add_event( + "chat.interrupted", + { + "chat.flow": flow, + }, + ) if title_task is not None and not title_task.done(): title_task.cancel() @@ -2029,9 +2079,11 @@ async def stream_new_chat( error_extra, ) = _classify_stream_exception(e, flow_label="chat") chat_outcome = error_code or error_kind or "error" + chat_error_category = ot_metrics.categorize_exception(e) with contextlib.suppress(Exception): chat_span.set_attribute("chat.outcome", chat_outcome) - chat_span.record_exception(e) + chat_span.set_attribute("error.category", chat_error_category) + ot.record_error(chat_span, e) error_message = f"Error during chat: {e!s}" print(f"[stream_new_chat] {error_message}") print(f"[stream_new_chat] Exception type: {type(e).__name__}") @@ -2234,8 +2286,9 @@ async def stream_new_chat( flow=flow, outcome=chat_outcome, agent_mode=chat_agent_mode, + error_category=chat_error_category, ) - chat_span_cm.__exit__(None, None, None) + chat_span_cm.__exit__(*sys.exc_info()) async def stream_resume_chat( @@ -2262,6 +2315,7 @@ async def stream_resume_chat( stream_result.client_platform = fs_platform chat_agent_mode = "unknown" chat_outcome = "success" + chat_error_category: str | None = None chat_span_cm = ot.chat_request_span( chat_id=chat_id, search_space_id=search_space_id, @@ -2345,6 +2399,14 @@ async def stream_resume_chat( selected_llm_config_id=llm_config_id, ) ).resolved_llm_config_id + ot.add_event( + "model.pin.resolved", + { + "pin.requested_id": requested_llm_config_id, + "pin.resolved_id": llm_config_id, + "pin.requires_image_input": False, + }, + ) except ValueError as pin_error: yield _emit_stream_error( message=str(pin_error), @@ -2401,6 +2463,12 @@ async def stream_resume_chat( ) _resume_premium_reserved_micros = reserve_amount_micros if not quota_result.allowed: + ot.add_event( + "quota.denied", + { + "quota.code": "PREMIUM_QUOTA_EXHAUSTED", + }, + ) if requested_llm_config_id == 0: try: llm_config_id = ( @@ -2413,6 +2481,13 @@ async def stream_resume_chat( force_repin_free=True, ) ).resolved_llm_config_id + ot.add_event( + "model.repin", + { + "repin.reason": "premium_quota_exhausted", + "repin.to_config_id": llm_config_id, + }, + ) except ValueError as pin_error: yield _emit_stream_error( message=str(pin_error), @@ -2748,6 +2823,14 @@ async def stream_resume_chat( llm_config_id, time.perf_counter() - _t0, ) + ot.add_event( + "chat.rate_limit.recovered", + { + "recovery.reason": "provider_rate_limited", + "recovery.previous_config_id": previous_config_id, + "recovery.fallback_config_id": llm_config_id, + }, + ) _log_chat_stream_error( flow="resume", error_kind="rate_limited", @@ -2775,6 +2858,12 @@ async def stream_resume_chat( chat_id, ) if stream_result.is_interrupted: + ot.add_event( + "chat.interrupted", + { + "chat.flow": "resume", + }, + ) usage_summary = accumulator.per_message_summary() _perf_log.info( "[token_usage] interrupted resume_chat: calls=%d total=%d cost_micros=%d summary=%s", @@ -2869,9 +2958,11 @@ async def stream_resume_chat( error_extra, ) = _classify_stream_exception(e, flow_label="resume") chat_outcome = error_code or error_kind or "error" + chat_error_category = ot_metrics.categorize_exception(e) with contextlib.suppress(Exception): chat_span.set_attribute("chat.outcome", chat_outcome) - chat_span.record_exception(e) + chat_span.set_attribute("error.category", chat_error_category) + ot.record_error(chat_span, e) error_message = f"Error during resume: {e!s}" print(f"[stream_resume_chat] {error_message}") print(f"[stream_resume_chat] Traceback:\n{traceback.format_exc()}") @@ -3033,5 +3124,6 @@ async def stream_resume_chat( flow="resume", outcome=chat_outcome, agent_mode=chat_agent_mode, + error_category=chat_error_category, ) - chat_span_cm.__exit__(None, None, None) + chat_span_cm.__exit__(*sys.exc_info()) From 6e03ab044a7ad10ead96a22ced444bfa5672bf0c Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 22 May 2026 17:49:02 +0530 Subject: [PATCH 024/198] feat(tasks): measure Celery queue latency --- surfsense_backend/app/celery_app.py | 61 ++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/surfsense_backend/app/celery_app.py b/surfsense_backend/app/celery_app.py index cfb24731d..39406fb6b 100644 --- a/surfsense_backend/app/celery_app.py +++ b/surfsense_backend/app/celery_app.py @@ -1,16 +1,75 @@ """Celery application configuration and setup.""" +import contextlib import os +import time from celery import Celery from celery.schedules import crontab -from celery.signals import worker_process_init +from celery.signals import before_task_publish, task_prerun, worker_process_init from dotenv import load_dotenv +try: + from opentelemetry import trace +except ImportError: # pragma: no cover - optional OTel dependency + trace = None # type: ignore[assignment] + # Load environment variables load_dotenv() +@before_task_publish.connect +def _stamp_enqueue_time(headers=None, **_kwargs): + """Stamp enqueue time so workers can measure queue wait.""" + if headers is None: + return + with contextlib.suppress(Exception): + headers["surfsense.enqueued_at_ns"] = str(time.monotonic_ns()) + + +@task_prerun.connect +def _record_queue_latency(task=None, **_kwargs): + """Record queue latency and attach the generic operation to task spans.""" + if task is None: + return + try: + from app.observability import metrics as ot_metrics + + task_name = getattr(task, "name", None) or "unknown" + operation = ot_metrics.parse_celery_task_label(task_name) + request = getattr(task, "request", None) + delivery_info = getattr(request, "delivery_info", None) or {} + queue = delivery_info.get("routing_key") or "unknown" + scheduled = bool( + getattr(request, "eta", None) or getattr(request, "expires", None) + ) + + if trace is not None: + with contextlib.suppress(Exception): + span = trace.get_current_span() + span.set_attribute("celery.task.operation", operation) + + headers = getattr(request, "headers", None) or {} + enqueued_ns = headers.get("surfsense.enqueued_at_ns") + if enqueued_ns is None: + return + + elapsed_s = (time.monotonic_ns() - int(enqueued_ns)) / 1e9 + ot_metrics.record_celery_queue_latency( + elapsed_s, + task_name=task_name, + queue=queue, + scheduled=scheduled, + operation=operation, + ) + if trace is not None: + with contextlib.suppress(Exception): + span = trace.get_current_span() + span.set_attribute("celery.queue.latency_ms", elapsed_s * 1000) + except Exception: + pass + + @worker_process_init.connect def init_worker(**kwargs): """Initialize the LLM Router and Image Gen Router when a Celery worker process starts. From c4abbd6e2007b3400e3be1de504f57407cce86a6 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 22 May 2026 17:49:46 +0530 Subject: [PATCH 025/198] feat(pipeline): enrich ETL and indexing failure telemetry --- .../app/etl_pipeline/etl_pipeline_service.py | 48 +++++++++++++++++-- .../indexing_pipeline_service.py | 8 +++- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py b/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py index 4cdd387b0..496c6d0c3 100644 --- a/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py +++ b/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py @@ -30,6 +30,7 @@ class EtlPipelineService: category = classify_file(request.filename) start = time.perf_counter() status = "success" + error_category: str | None = None result: EtlResult | None = None with ot.etl_extract_span( content_type=category.value, @@ -75,8 +76,9 @@ class EtlPipelineService: result = await self._extract_document(request) return result - except Exception: + except Exception as exc: status = "error" + error_category = ot_metrics.categorize_exception(exc) raise finally: with contextlib.suppress(Exception): @@ -94,6 +96,7 @@ class EtlPipelineService: etl_service=result.etl_service if result else None, content_type=result.content_type if result else category.value, status=status, + error_category=error_category, ) async def _extract_image(self, request: EtlRequest) -> EtlResult: @@ -134,11 +137,27 @@ class EtlPipelineService: request.filename, exc_info=True, ) + ot.add_event( + "etl.fallback", + { + "fallback.from": "vision_llm", + "fallback.to": "document_parser", + "fallback.reason": ot_metrics.categorize_exception(exc), + }, + ) else: logging.info( "No vision LLM provided, falling back to document parser for %s", request.filename, ) + ot.add_event( + "etl.fallback", + { + "fallback.from": "vision_llm", + "fallback.to": "document_parser", + "fallback.reason": "not_configured", + }, + ) try: with ot.etl_ocr_span( @@ -246,6 +265,13 @@ class EtlPipelineService: # Common case: the configured ETL service can't OCR # this image format (or no service is configured at # all). Don't spam warnings -- just no OCR for it. + ot.add_event( + "etl.ocr.skipped", + { + "skip.reason": "unsupported_format", + "error.category": ot_metrics.categorize_exception(exc), + }, + ) logging.debug("Skipping per-image OCR for %s: %s", image_name, exc) return "" return ocr_result.markdown_content @@ -264,9 +290,17 @@ class EtlPipelineService: sp.set_attribute("image.skipped.too_large", result.skipped_too_large) sp.set_attribute("image.skipped.duplicate", result.skipped_duplicate) sp.set_attribute("etl.status", "success") - except Exception: + except Exception as exc: # Picture description is additive; never let it fail an # otherwise-successful document extraction. + ot.add_event( + "etl.degraded", + { + "degraded.reason": "picture_describe_failed", + "degraded.action": "return_parser_output", + "error.category": ot_metrics.categorize_exception(exc), + }, + ) logging.warning( "Picture description failed for %s, returning parser output unchanged", request.filename, @@ -319,7 +353,15 @@ class EtlPipelineService: return await parse_with_azure_doc_intelligence( request.file_path, processing_mode=mode_value ) - except Exception: + except Exception as exc: + ot.add_event( + "etl.fallback", + { + "fallback.from": "azure_di", + "fallback.to": "llamacloud", + "fallback.reason": ot_metrics.categorize_exception(exc), + }, + ) logging.warning( "Azure Document Intelligence failed for %s, " "falling back to LlamaCloud", diff --git a/surfsense_backend/app/indexing_pipeline/indexing_pipeline_service.py b/surfsense_backend/app/indexing_pipeline/indexing_pipeline_service.py index 2aa92bd9b..282bd6034 100644 --- a/surfsense_backend/app/indexing_pipeline/indexing_pipeline_service.py +++ b/surfsense_backend/app/indexing_pipeline/indexing_pipeline_service.py @@ -2,6 +2,7 @@ import asyncio import contextlib import hashlib import logging +import sys import time from collections.abc import Awaitable, Callable from dataclasses import dataclass, field @@ -445,6 +446,7 @@ class IndexingPipelineService: await self._enqueue_ai_sort_if_enabled(document) except RETRYABLE_LLM_ERRORS as e: + ot.record_error(persist_span, e) log_retryable_llm_error(ctx, e) outcome_status = "requeued" await rollback_and_persist_failure( @@ -452,24 +454,28 @@ class IndexingPipelineService: ) except PERMANENT_LLM_ERRORS as e: + ot.record_error(persist_span, e) log_permanent_llm_error(ctx, e) await rollback_and_persist_failure( self.session, document, llm_permanent_message(e) ) except RecursionError as e: + ot.record_error(persist_span, e) log_chunking_overflow(ctx, e) await rollback_and_persist_failure( self.session, document, PipelineMessages.CHUNKING_OVERFLOW ) except EMBEDDING_ERRORS as e: + ot.record_error(persist_span, e) log_embedding_error(ctx, e) await rollback_and_persist_failure( self.session, document, embedding_message(e) ) except Exception as e: + ot.record_error(persist_span, e) log_unexpected_error(ctx, e) await rollback_and_persist_failure( self.session, document, safe_exception_message(e) @@ -488,7 +494,7 @@ class IndexingPipelineService: document_type=document_type, status=outcome_status, ) - persist_span_cm.__exit__(None, None, None) + persist_span_cm.__exit__(*sys.exc_info()) return document async def _enqueue_ai_sort_if_enabled(self, document: Document) -> None: From 7a3b278b7567041f48c06cfe09e23ca7af9a69f5 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 22 May 2026 17:50:02 +0530 Subject: [PATCH 026/198] feat(connectors): add retry and auth telemetry events --- .../routes/search_source_connectors_routes.py | 24 ++++++++++++++++++- .../app/tasks/celery_tasks/connector_tasks.py | 14 +++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/surfsense_backend/app/routes/search_source_connectors_routes.py b/surfsense_backend/app/routes/search_source_connectors_routes.py index 4d7e6b2ef..3060fdf4a 100644 --- a/surfsense_backend/app/routes/search_source_connectors_routes.py +++ b/surfsense_backend/app/routes/search_source_connectors_routes.py @@ -43,7 +43,7 @@ from app.db import ( async_session_maker, get_async_session, ) -from app.observability import metrics as ot_metrics +from app.observability import metrics as ot_metrics, otel as ot from app.schemas import ( GoogleDriveIndexRequest, MCPConnectorCreate, @@ -1246,6 +1246,12 @@ async def _persist_auth_expired(session: AsyncSession, connector_id: int) -> Non """Flag a connector as auth_expired so the frontend shows a re-auth prompt.""" from sqlalchemy.orm.attributes import flag_modified + ot.add_event( + "connector.auth.expired", + { + "error.category": "auth_failed", + }, + ) try: result = await session.execute( select(SearchSourceConnector).where( @@ -1305,6 +1311,13 @@ async def _run_indexing_with_notifications( try: connector_lock_acquired = acquire_connector_indexing_lock(connector_id) if not connector_lock_acquired: + ot.add_event( + "connector.sync.skipped", + { + "skip.reason": "lock_contention", + "error.category": "lock_contention", + }, + ) logger.info( f"Skipping indexing for connector {connector_id} " "(another worker already holds Redis connector lock)" @@ -1375,6 +1388,15 @@ async def _run_indexing_with_notifications( ) -> None: """Callback to update notification during API retries (rate limits, etc.)""" nonlocal notification + ot.add_event( + "connector.retry.scheduled", + { + "retry.reason": retry_reason, + "retry.attempt": attempt, + "retry.max": max_attempts, + "retry.delay_ms": int(wait_seconds * 1000), + }, + ) if notification: try: await session.refresh(notification) diff --git a/surfsense_backend/app/tasks/celery_tasks/connector_tasks.py b/surfsense_backend/app/tasks/celery_tasks/connector_tasks.py index e0f0f09c9..50f757473 100644 --- a/surfsense_backend/app/tasks/celery_tasks/connector_tasks.py +++ b/surfsense_backend/app/tasks/celery_tasks/connector_tasks.py @@ -22,15 +22,18 @@ def run_async_celery_task[T](coro_factory: Callable[[], Awaitable[T]]) -> T: task_name = getattr(current_task, "name", None) or "unknown" t0 = time.perf_counter() status = "failed" + error_category: str | None = None try: with ot.connector_sync_span(connector_type=task_name) as sp: - result = _run_async_celery_task(coro_factory) - sp.set_attribute("connector.status", "success") + try: + result = _run_async_celery_task(coro_factory) + sp.set_attribute("connector.status", "success") + except Exception as exc: + error_category = ot_metrics.categorize_exception(exc) + sp.set_attribute("connector.error.category", error_category) + raise status = "success" return result - except Exception: - status = "failed" - raise finally: elapsed_s = time.perf_counter() - t0 ot_metrics.record_connector_sync_duration( @@ -40,6 +43,7 @@ def run_async_celery_task[T](coro_factory: Callable[[], Awaitable[T]]) -> T: ot_metrics.record_connector_sync_outcome( connector_type=task_name, status=status, + error_category=error_category, ) From 51e4d8b489dc2972c43aa63fd0ea174a36e5057c Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Fri, 22 May 2026 18:19:38 +0530 Subject: [PATCH 027/198] feat(tasks): enhance Celery task telemetry with queue metadata and latency tracking --- surfsense_backend/app/celery_app.py | 48 +++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/surfsense_backend/app/celery_app.py b/surfsense_backend/app/celery_app.py index 39406fb6b..5b45baca1 100644 --- a/surfsense_backend/app/celery_app.py +++ b/surfsense_backend/app/celery_app.py @@ -6,7 +6,12 @@ import time from celery import Celery from celery.schedules import crontab -from celery.signals import before_task_publish, task_prerun, worker_process_init +from celery.signals import ( + before_task_publish, + task_postrun, + task_prerun, + worker_process_init, +) from dotenv import load_dotenv try: @@ -29,7 +34,7 @@ def _stamp_enqueue_time(headers=None, **_kwargs): @task_prerun.connect def _record_queue_latency(task=None, **_kwargs): - """Record queue latency and attach the generic operation to task spans.""" + """Record queue latency and stash task metadata for span enrichment.""" if task is None: return try: @@ -44,10 +49,10 @@ def _record_queue_latency(task=None, **_kwargs): getattr(request, "eta", None) or getattr(request, "expires", None) ) - if trace is not None: - with contextlib.suppress(Exception): - span = trace.get_current_span() - span.set_attribute("celery.task.operation", operation) + with contextlib.suppress(Exception): + request.surfsense_operation = operation + request.surfsense_queue = queue + request.surfsense_scheduled = scheduled headers = getattr(request, "headers", None) or {} enqueued_ns = headers.get("surfsense.enqueued_at_ns") @@ -55,6 +60,9 @@ def _record_queue_latency(task=None, **_kwargs): return elapsed_s = (time.monotonic_ns() - int(enqueued_ns)) / 1e9 + with contextlib.suppress(Exception): + request.surfsense_queue_latency_ms = elapsed_s * 1000 + ot_metrics.record_celery_queue_latency( elapsed_s, task_name=task_name, @@ -62,10 +70,30 @@ def _record_queue_latency(task=None, **_kwargs): scheduled=scheduled, operation=operation, ) - if trace is not None: - with contextlib.suppress(Exception): - span = trace.get_current_span() - span.set_attribute("celery.queue.latency_ms", elapsed_s * 1000) + except Exception: + pass + + +@task_postrun.connect +def _set_celery_span_attributes(task=None, **_kwargs): + """Attach derived queue metadata to the active Celery run span.""" + if task is None or trace is None: + return + + try: + request = getattr(task, "request", None) + if request is None: + return + + span = trace.get_current_span() + + operation = getattr(request, "surfsense_operation", None) + if operation: + span.set_attribute("celery.task.operation", operation) + + latency_ms = getattr(request, "surfsense_queue_latency_ms", None) + if latency_ms is not None: + span.set_attribute("celery.queue.latency_ms", latency_ms) except Exception: pass From d97b2830c51774ee1864665cd4fb652dea381aee Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Fri, 22 May 2026 17:24:57 +0200 Subject: [PATCH 028/198] fix: resolve desktop KB prompt self-contradiction on chunk_ids The citations fix (cacb27e0) added a "Chunk citations in your prose" section to system_prompt_desktop.md telling the KB subagent to always leave `evidence.chunk_ids` null and emit no `[citation:...]` markers in desktop mode, but left the pre-existing line declaring that `chunk_ids` apply to `` hits. The two rules contradicted each other; the model picked one per turn. Strike the stale conditional clause and point at the dedicated section as the single source of truth. Matches the parallel line in system_prompt_cloud.md and the already-consistent system_prompt_readonly_desktop.md. --- .../subagents/builtins/knowledge_base/system_prompt_desktop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md index 4e5465aaf..b0f2dacb2 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md @@ -33,7 +33,7 @@ Map outcomes to your `status`: - Any other `"Error: …"` → `status=error` and relay the tool's message verbatim as `next_step`. - HITL rejection → `status=blocked` with `next_step="User declined this filesystem action. Do not retry."`. -You construct the structured `evidence` fields from your own knowledge of what you called and what you observed — the tools do not return them. `chunk_ids` apply only to `` hits; for local-file operations leave them `null`. Never report values you did not actually see. +You construct the structured `evidence` fields from your own knowledge of what you called and what you observed — the tools do not return them. Never report values you did not actually see. (`chunk_ids` is always `null` in desktop mode — see "Chunk citations in your prose" below.) ## Chunk citations in your prose From 6ee7c04d0283008e1832f21453c9b0391f3006e1 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Fri, 22 May 2026 18:39:22 +0200 Subject: [PATCH 029/198] fix(desktop): recover deep-link URL from argv on win/linux cold start setupDeepLinks() only listened for second-instance and open-url events. On Windows/Linux a fresh launch via `surfsense://` delivers the URL in argv of the first instance, where it was silently dropped. Scan argv on setup so the existing handlePendingDeepLink() pass picks it up. --- surfsense_desktop/src/modules/deep-links.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/surfsense_desktop/src/modules/deep-links.ts b/surfsense_desktop/src/modules/deep-links.ts index 11b7bfcff..7d5e429bd 100644 --- a/surfsense_desktop/src/modules/deep-links.ts +++ b/surfsense_desktop/src/modules/deep-links.ts @@ -60,6 +60,11 @@ export function setupDeepLinks(): boolean { app.setAsDefaultProtocolClient(PROTOCOL); } + // Cold-start on Windows/Linux: protocol URL arrives via argv of the + // first instance, not via `second-instance` or `open-url`. + const cold = process.argv.find((arg) => arg.startsWith(`${PROTOCOL}://`)); + if (cold) handleDeepLink(cold); + return true; } From fe98c17b1d52535450a3b3de8602f2b03a5eeef5 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Fri, 22 May 2026 18:39:27 +0200 Subject: [PATCH 030/198] fix(desktop): declare surfsense:// scheme in macOS Info.plist Linux registered the scheme via desktop-file MIME, but mac.extendInfo never declared CFBundleURLTypes, leaving install-time LaunchServices unaware of the protocol. The runtime app.setAsDefaultProtocolClient call still runs as a fallback. --- surfsense_desktop/electron-builder.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/surfsense_desktop/electron-builder.yml b/surfsense_desktop/electron-builder.yml index e4e7670ec..90568a637 100644 --- a/surfsense_desktop/electron-builder.yml +++ b/surfsense_desktop/electron-builder.yml @@ -55,6 +55,11 @@ mac: NSAccessibilityUsageDescription: "SurfSense uses accessibility features to bring the app to the foreground and interact with the active application when you use desktop assists." NSScreenCaptureUsageDescription: "SurfSense uses screen capture so you can attach a selected region to chat (Screenshot Assist) or capture the full screen from the composer." NSAppleEventsUsageDescription: "SurfSense uses Apple Events to interact with the active application." + # `surfsense://` scheme — install-time registration for LaunchServices. + CFBundleURLTypes: + - CFBundleURLName: com.surfsense.desktop + CFBundleURLSchemes: + - surfsense target: - target: dmg arch: [x64, arm64] From 1b6c238c682bf3629906a688372b504b40fd5163 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Fri, 22 May 2026 18:39:47 +0200 Subject: [PATCH 031/198] refactor(desktop): harden OAuth redirect rewrite for host variants and self-hosters The interceptor previously matched a strict `${HOSTED_FRONTEND_URL}/*` prefix and did a naive String.replace, which broke whenever the backend NEXT_FRONTEND_URL differed at all (apex vs www, http vs https, or a self-hosted domain). Now: - Match by host: apex + www. sibling, both http and https. - Rewrite via URL parsing so only protocol/host change; query strings containing the host as a value are left intact. - Read HOSTED_FRONTEND_URL through getHostedFrontendUrl() which honors a SURFSENSE_HOSTED_FRONTEND_URL_OVERRIDE env var, letting self-hosters point their builds at their own frontend without rebuilding. Default behavior is identical when override is unset and backend host matches the baked-in value. --- surfsense_desktop/.env.example | 5 ++++ surfsense_desktop/src/modules/window.ts | 40 +++++++++++++++++++++---- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/surfsense_desktop/.env.example b/surfsense_desktop/.env.example index e127b99e0..2d9de7561 100644 --- a/surfsense_desktop/.env.example +++ b/surfsense_desktop/.env.example @@ -5,6 +5,11 @@ # inside the desktop app. Set to your production frontend domain. HOSTED_FRONTEND_URL=https://surfsense.net +# Runtime override for the above (read at app start, no rebuild required). +# Useful for self-hosters whose backend NEXT_FRONTEND_URL differs from the +# value baked into the official desktop builds. Leave empty to use HOSTED_FRONTEND_URL. +# SURFSENSE_HOSTED_FRONTEND_URL_OVERRIDE= + # PostHog analytics (leave empty to disable) POSTHOG_KEY= POSTHOG_HOST=https://assets.surfsense.com diff --git a/surfsense_desktop/src/modules/window.ts b/surfsense_desktop/src/modules/window.ts index 5317005d5..8d60e05f5 100644 --- a/surfsense_desktop/src/modules/window.ts +++ b/surfsense_desktop/src/modules/window.ts @@ -6,9 +6,26 @@ import { getServerPort } from './server'; import { setActiveSearchSpaceId } from './active-search-space'; const isDev = !app.isPackaged; -const HOSTED_FRONTEND_URL = process.env.HOSTED_FRONTEND_URL as string; const isMac = process.platform === 'darwin'; +function getHostedFrontendUrl(): string { + return ( + process.env.SURFSENSE_HOSTED_FRONTEND_URL_OVERRIDE || + process.env.HOSTED_FRONTEND_URL || + 'https://surfsense.net' + ); +} + +function getHostedFrontendHosts(): string[] { + try { + const host = new URL(getHostedFrontendUrl()).host; + const sibling = host.startsWith('www.') ? host.slice(4) : `www.${host}`; + return Array.from(new Set([host, sibling])); + } catch { + return []; + } +} + let mainWindow: BrowserWindow | null = null; let isQuitting = false; @@ -58,11 +75,22 @@ export function createMainWindow(initialPath = '/dashboard'): BrowserWindow { return { action: 'deny' }; }); - const filter = { urls: [`${HOSTED_FRONTEND_URL}/*`] }; - session.defaultSession.webRequest.onBeforeRequest(filter, (details, callback) => { - const rewritten = details.url.replace(HOSTED_FRONTEND_URL, `http://localhost:${getServerPort()}`); - callback({ redirectURL: rewritten }); - }); + const hostedHosts = getHostedFrontendHosts(); + const rewriteFilter = { + urls: hostedHosts.flatMap((h) => [`http://${h}/*`, `https://${h}/*`]), + }; + if (rewriteFilter.urls.length > 0) { + session.defaultSession.webRequest.onBeforeRequest(rewriteFilter, (details, callback) => { + try { + const u = new URL(details.url); + u.protocol = 'http:'; + u.host = `localhost:${getServerPort()}`; + callback({ redirectURL: u.toString() }); + } catch { + callback({}); + } + }); + } mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL) => { console.error(`Failed to load ${validatedURL}: ${errorDescription} (${errorCode})`); From b9403b1720756d65a431767c2ac02c3efd49b4d3 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Fri, 22 May 2026 18:40:06 +0200 Subject: [PATCH 032/198] feat(desktop): emit PostHog events for OAuth redirect intercept and miss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two diagnostic events to surface OAuth-redirect failures we can't reproduce on Linux: - desktop_oauth_redirect_intercepted fires from inside onBeforeRequest with the original host, path, and target port — confirms the rewrite actually ran. - desktop_oauth_redirect_missed fires from a read-only onCompleted listener when a /dashboard/*/connectors/callback URL lands off-localhost, meaning the rewrite filter didn't catch it. This is the smoking-gun event for "connector OAuth dies on mac/win" reports. Read-only; no behavior change. --- surfsense_desktop/src/modules/window.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/surfsense_desktop/src/modules/window.ts b/surfsense_desktop/src/modules/window.ts index 8d60e05f5..003241ef3 100644 --- a/surfsense_desktop/src/modules/window.ts +++ b/surfsense_desktop/src/modules/window.ts @@ -83,8 +83,14 @@ export function createMainWindow(initialPath = '/dashboard'): BrowserWindow { session.defaultSession.webRequest.onBeforeRequest(rewriteFilter, (details, callback) => { try { const u = new URL(details.url); + const originalHost = u.host; u.protocol = 'http:'; u.host = `localhost:${getServerPort()}`; + trackEvent('desktop_oauth_redirect_intercepted', { + host: originalHost, + path: u.pathname, + rewritten_to_port: getServerPort(), + }); callback({ redirectURL: u.toString() }); } catch { callback({}); @@ -92,6 +98,25 @@ export function createMainWindow(initialPath = '/dashboard'): BrowserWindow { }); } + // Diagnostic: connector callback landing somewhere other than localhost + // means the rewrite missed and the user is stranded off-app. + session.defaultSession.webRequest.onCompleted( + { urls: ['*://*/dashboard/*/connectors/callback*'] }, + (details) => { + try { + const u = new URL(details.url); + if (u.hostname === 'localhost' || u.hostname === '127.0.0.1') return; + trackEvent('desktop_oauth_redirect_missed', { + host: u.host, + path: u.pathname, + status_code: details.statusCode, + }); + } catch { + // ignore malformed URLs + } + } + ); + mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL) => { console.error(`Failed to load ${validatedURL}: ${errorDescription} (${errorCode})`); if (errorCode === -3) return; From d5284b307687704ef8731e68721670ddddeca849 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Fri, 22 May 2026 19:37:19 +0200 Subject: [PATCH 033/198] fix(desktop): bind bundled Next.js on localhost to keep window origin stable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Setting HOSTNAME=0.0.0.0 made Next.js standalone canonicalize request.url to http://0.0.0.0:PORT. The connector OAuth callback's NextResponse.redirect built its Location from that URL, so navigating it flipped window.location.origin from http://localhost:PORT to http://0.0.0.0:PORT. The backend CORS allowlist matches localhost/127.0.0.1 only, blocking every subsequent API call until app restart — producing the "no internet" / app-down state after connecting any connector. --- surfsense_desktop/src/modules/server.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/surfsense_desktop/src/modules/server.ts b/surfsense_desktop/src/modules/server.ts index e2f078a8c..17fcfb445 100644 --- a/surfsense_desktop/src/modules/server.ts +++ b/surfsense_desktop/src/modules/server.ts @@ -39,7 +39,8 @@ export async function startNextServer(): Promise { const serverScript = path.join(standalonePath, 'server.js'); process.env.PORT = String(serverPort); - process.env.HOSTNAME = '0.0.0.0'; + // Loopback bind: 0.0.0.0 leaks into request.url and flips window origin via NextResponse.redirect. + process.env.HOSTNAME = 'localhost'; process.env.NODE_ENV = 'production'; process.chdir(standalonePath); From fa75d7c54cf6aa576f247f78199d070d6c5b714c Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Fri, 22 May 2026 20:04:21 +0200 Subject: [PATCH 034/198] fix(desktop): ship multi-size icons so linux launchers can find them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit electron-builder was given a single 2048x2048 icon.png and dumped it into hicolor/2048x2048/apps/, a bucket no Linux desktop environment indexes — launchers and taskbar fell back to a generic placeholder. Pre-render the standard sizes (16, 32, 48, 64, 128, 256, 512, 1024) and point linux.icon at the directory so each PNG lands in the matching hicolor/NxN/apps/ slot. Tray icon was unaffected (loaded at runtime via Electron's Tray API from bundled resources, no theme lookup). --- surfsense_desktop/assets/icons/1024x1024.png | Bin 0 -> 1549970 bytes surfsense_desktop/assets/icons/128x128.png | Bin 0 -> 29976 bytes surfsense_desktop/assets/icons/16x16.png | Bin 0 -> 841 bytes surfsense_desktop/assets/icons/256x256.png | Bin 0 -> 101502 bytes surfsense_desktop/assets/icons/32x32.png | Bin 0 -> 2717 bytes surfsense_desktop/assets/icons/48x48.png | Bin 0 -> 5552 bytes surfsense_desktop/assets/icons/512x512.png | Bin 0 -> 380092 bytes surfsense_desktop/assets/icons/64x64.png | Bin 0 -> 9263 bytes surfsense_desktop/electron-builder.yml | 2 +- 9 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 surfsense_desktop/assets/icons/1024x1024.png create mode 100644 surfsense_desktop/assets/icons/128x128.png create mode 100644 surfsense_desktop/assets/icons/16x16.png create mode 100644 surfsense_desktop/assets/icons/256x256.png create mode 100644 surfsense_desktop/assets/icons/32x32.png create mode 100644 surfsense_desktop/assets/icons/48x48.png create mode 100644 surfsense_desktop/assets/icons/512x512.png create mode 100644 surfsense_desktop/assets/icons/64x64.png diff --git a/surfsense_desktop/assets/icons/1024x1024.png b/surfsense_desktop/assets/icons/1024x1024.png new file mode 100644 index 0000000000000000000000000000000000000000..853201c5ee93fee2c101d7bb40117226bea4dd64 GIT binary patch literal 1549970 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

u>|$m2!*Kl}qc45fo5=*Tm^^fTUgQLE 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�c>Gd89R|oOtE; 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%0Dr%Lci~HccF)ej_j`bJh9h_KcDm+bUAF6)~;m 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

    g?g_PTJ>aZA>m$3uGN^ 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

    4^6mi0KM0~vcCz% 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
    -YcFk7=a4{4!b~y?J#(p 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
    I!pVAyd77~zH@@ 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 z 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 {rk!(^ZD^jy35WRzYV{u0*;yq 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%0DheLfUV6va)o@e{K>r}8V&GLy| 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%0D8Cp4RGvxE3{(!TgyLE8rpIRV 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

    n)evnPvHE}+Gg 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

    w&Iqr} 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 z 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

    t4C1;2vAcgo(>-2mj~ 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 literal 0 HcmV?d00001 diff --git a/surfsense_desktop/assets/icons/64x64.png b/surfsense_desktop/assets/icons/64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..a218a4ee2bafd4706f3ded019acaa34b10af1927 GIT binary patch literal 9263 zcmV+~B+%Q5P))f5v*FmW(WOgA{O4aOK;Ag>K_i*a0tu}w^3 zY-}(X0vG|obc6~4HB?d8YF8`muD0*qerL{k|2Q*quLvIytG#pQob#Ng{GR9eJ))^& zrPdk%G#ViQtpR}+0)YS&pbU50GP@K4gpk<&jz9=Ra5jITU@O5! z2=`q1O#27sBfkqpqJy>lO@)W|pB0#O{S8(QFZ?$Ip9x8(FVfN+SkbHwRGirbmh{_Gs<5{_+n60)=%WM#ag>Ed1LrCZZ|=4uxGU zDph$g=0M>_g%O|-W(_R_LP!Lbzpe;xCj3*F-v^f?WT3X3q-BEY6DafkiukBj0#T*I zT9~8T#^!XzenfESiXsac+z@DAg@rI;5;}Ncl^JLSLLzMotrU1hc-WRvBv*mf2&qG1 z2@P2O8cKl%X;sWZS6_Ijt-iWz4VM3amVk{Au7AnW?5Elkbof^DHxa5`)pMd^QbLH( zY$e)Pec!@_MvA~l%w~iTNK4>OOyFj+Xxl?6MzF$nkoeBmUrz{-D5ELbn6It1hY%KlAVlD{wfUmf z)%(;HFRpzX7SeZQ+P@ehIX9D|>HM>3zy9xt&uIlqQs_KJ_oL4<@Y)8fWGsv%5`~w~ z(|F-owBK|U$vJI!WtURtF}j|7nf{m8;KXAfeP4h8^${nnB})04B>-(9P!;V)AhZts zl{StCE7{P#s@jsmD2M>*y9jMUp)dyQhb7@3R{Nj(+JZ(Y#lrjUVE%nyp)@kW!0Q_r z+p&-OxigsmrRz~bGq`Ru(up91Kotti{K^e1`SIP9hll8UaShphoz%@~W9ApGA>P!$ zz&jiL+qOVUgjW8=`k^_v0AcP}g!?w?sdeBy0`oC@wJY+egoe-~Y0r0Fit2wv;EjA~ z31NgKZHYTR!R&i(qwR*P*m?K;bU(Qo=@~8ReDEci&28-ywQmUW)))EJLRbhX(d9geA^ zS@OewVdtu!(e=nv#G4wC7Rb1R6^k&iW)lr77SgbC89mRw0v4Qd|JTSL?q=tm_v6&o zAY%^Fv9S|L2H)F8ek{Y>J8xlR{bt6t?=tHPVKfMB!mQ;V%y$Xe(t+vwm!^%zij!l4 zt+g_qv7#-$Myc?ojju7PFQw6SAtcImar0T??d>$2dK#7!q3Oa47~gf2qmMm9yrC8^ zGeLA}D;f`XWEd$ex>vu!;_ux~q^Ta|X=>-sV)u7`iL^|Vu+xno9g4@gi8iG<^82T0 z`@2su^Pj#*bowj=ijnO*$s9a@lZYcD5wurEDBre)@}rSJd&Vq+fUmVAbZiK0;{c7- z2;cSth^w_Qs-ku1T+C-Ss!%}0<1D=6E~Z}oNdqwpd5A^I z=~*~+nz>(^j}?uBmh`>)68nDrOWe#DG7|Isl=d%8`aZxMRv<7wQdKaA23v$-Ncf>q znW!L)*E9$*2w9d1SEV8%m1M=w9wa?y9{c~}7xb@Ni^@!3*Vi-sqQ9o&#+!&wpGDog z`SfjgmxGT!LNrx_o5_b^HGmxf+|{naN!4)p504O^I+aD=xR=qrdzpIvN)Gmvav ze02#Nhe>FJWrg@kxh3Y`bq~pzvv~LB>p1-AZz!KQj#tc+Ki(2`uBU#_`p;cMYU(tWeDhwER|p*(T7lFEE5Izin-?C^B6NgRcYcalQ^;NO?v+PG)gPHQW+(nNMY+93p1qQQhg11$|!7FzhpNALnaz)FEsF0-!vJejT|bU*btw&Nh&GEQwh zr~mw6noc{Nqc8m#+lk`kvZ#_^XxnB~Hj9up(g)wg{u7LE-2yIQULj3++OjzI?2{b)?ZXJI zkd_q|TeMQX6RendVBxm#OJ9|oUWZj7EfhjZq>z-0MHbw%iW#50lmowgoZi=8#vLBU zs!7vv^=Fy(nJbZwjpam4J;Anc^99PqG7ZZY(RSsvaSKIoT`VVp9ghM~&gGf+xxc4j{v6id{$0ugebh~B!yO&v z@MFKjN+ywsgsC4`5u#0#7+mu@M;?2E8UJ_q~6ydof+Gp@rk37Myd+%o8?bor=HE8$b zI-(Acpav_^*tW{xd_T?$QIU?2c!eC3F1Uzf$2_|K@LLc9FE>WpC$DDy?e}nW^)m=* za{Abm7FIs=9j0A)7RAxAO8)M7$f$!;6K81Sc6R*mKiPi&L!5Z`14Kzv=;>p0e;0UV ztXP!%zzBWsZ>N;akZftDY3Tx_lpKA1El$*_WT(Ji{pwLXugvJi4cN&V+lNm^9O?LvSpbamOdQsAgmY^6yoR8SVxNXsE}^eB<~G|9F$Mt5u{ zIjfDc|Kq#V&Y4MZcnr(2(ORQik4QR6d3b~mzWYlKKK25_b!j-`bP_GijPE~a@`~GU zVEU!!;55}6IGit&J=6u+Jh`E9X8yw`>3;bgMt1MVPQ@t?3{ZR8890+0C>%eEkPaw~ z_EUL*Kq$W^i0fB{MR16)wrE%KV|{YD)&Efni_HGLIH?rz$y3N4K7bXCGrnsFyMC~W ziGBM>&T8YFhkrn7W-H~<38Z7Al}3AtXnmT&_qVa>TMv*sa*P?*U4g7=BzI(h-nZYR zX6_6w_{H~0&z(habOJnZwZhF8sF~4-xbOPAx+ zOPbI5IAc3Mz-ej$ugLKipCdK5gLD7$UrDx2qdcBL+UD>|DdLTFbU*(Z8}EGpHJ+vR zv;{1F;2!cvjLq<|&5vb)sBFVp216uey{O ze}5@Wr!OT^pN6u_^83C=zEoi6s=Lvp5>Bdy!q^avXPu3cwrE&(I>Ya+3(I?&paY}* zv|ply#!57{8zdM+0qH|t%fib{(0suqbbRSf2DfZy>cy8*IMz+J>kwYiB{gpr=RW)c zlC9GyPh^metB(62)`N)GeBeh&mj5_I1X#@8k3zf0L$V3n>hZ z5v{MIf8G1M^Tls7wtGLSP{5nWlk4fF>(zHC4i3|L`GxeZd5>IAFM~U_()IL{lm`c} zB2kLN{WPAjk~w$W!|0BEOgaAoCb|xj*}n%X7WXfm{9p$wX8yZSoD zc5Gq$onK(+owpEChvLX6wF^5q|G$1nV%lU%6B&eU`5?o1`VSv`l&#-?2q&4On90+y zVhQo4I=1}ty>$QXIhxN|&Xn^{r#L*09gkD&?`P9}51>jOPD4G?cCaE2c1@CKLp^&R z`ENR(Tur)dD%x|&cO5|#OIVQzA|7Y@XKrBWkAFpO|6aD;aSH>RH!mhrAl1=I)8ct_|M@LqHFbz+jP}bez_KGKSK%b$bicfY z+|fRqL=07QD^*;j(5@m@Tg!nbUm!m)h_oYEkr>+bh%`2G<|BV%;ho>$_+MUT+r3|d ze4f5nUnDhSHnAzwP^F>|gmk6ijYepNO;w8%?RQy)LVF&OmMN6RM#vrB4^rZm%FO@b z4J^Iu7PJ62U%+xKw5yPoBod3T>D~u8_{2-Z(@Ba$BSfaO&~nZS4*mBl2x+60hf|xP z`P2odVhJfFT6qlZ*o(C7N*4*^n|$Pgh}aZP4C9WBA!8Ag>w#tAWpf;U90tr97lCNjKW00Jc^^`B05@r7s9R|=FbY=lHu0=JOo*y^W{5t~Rl zjYguq0#+mn0=#n3WQ49uv?+}fk22BKhj1cjt+9jjFzniDjZhl52FV2n%Tn;Yh4#6s@3cQ7eOTeeX5U6ZF5lnz3yg zu@eb|?H~YmXpnf@Or~A^kMzCu8t%{#PCAWD#+Y)+$2jrB|3dho+N8r;hpi;7G^c#& z8fJa!Qe-TOuq1B2NO5q~Y#5F5jMK8D;J{B0*b+qoOBl%@TezhPwa z9>zM4lbSW1NoOpjcg-du=^7*wH=9G3T@!{dfNAo| z(ZBu#r6Id#H;s!=W7@@kP4^!krTf_@D36XJBM#})Rxt0It0<38aQM-OaZ*W&gCi`s z<65k`TC!coiKJ@`_5o~92OU%kU3S^=vq#CF7-sf0SCE+8j1`R{Z3ittI>y@%Z{Ne7 z|9Fy~H{K05rZ3dVW*RR8C#&s9x_$K>^tu!*VD!Jd+x*?96-im%)9k2 zX54fmlPe=SeN?eTtf3ZFa>15Fr#3O| z>dP4aU?;0R?4Rw98?6(Wc&MFks6G}@BH8XItj#yIf1XE^ZK zpRtoBqjEEOq-EixVki;8oyg#MineP%!-Csx=J@Mxap>QFM`HRU+|hAR8Y>!MeA_;n zRxF}s-b`{w`*3q5j&ImXz(5AI^)H~tOVQ^;5pD;_0t^2QPajfRBssOZ`eX{U>G|QM->W;?c0NN9Mcz&7GAMLsH*7LCgmaO!=x zG3Ums5L)4mXV`S#gLtDEgskeK1_IE0BoPe#g22n?NiAGL!`bIi7#^Z_(W%rdI+aK= zMXvui<6AZ%m1cPB4vs$Y43jTfNxW?eBOiQ7YEC=u*aSM0BX_Ky)buG#yZK7;{R50` z-%nyn3zN@WhMUbJY}1Y_=L^&=?4b4Hm6QtwMh|r1j%JaTgI1o&)?I~G;0_Pb^ZYYp zj~*q`P*3y6ma^!pH*m`BH&D~jMtOLQL_-}re)$+Z|Ff1zDrM+5Rh?kbJo}uWN8m>v zpYq~ohiN@2wvVvTjA=FFsF@jRNA&ZnlM6%BZq9J*8{(b7ow zYirqX+xLk?A{8Q#)?rV`Z;l4#59QYnWtc|`34O1;Kp9Q`&_Qb2XAqg(O#a9r^4;CU zrcNh6JVNehCo+*n#vKONZDHp1S26D!Uts)$Jsf@FIoxuQ!S$O-wM}At>t3WI@y5nk z|IPck_>q4jl1xy_7r>TSwuLH|DHTejX16nc@f;|-xPCEJYmJOXaAGlJG-Bd^u}C44 zHT(djh_^IwV)G6*-t$u&%kudR3Mu?@zmGMnWMlg(?YEJIsE{7DZznQhGxotoGP`$> zXl$YX?N^94Hjtb(huWnpIOX%Vp}jJm@@QCgDz&p`lbAUTRV-0AXFk|5x}JTBfw$hJ z*waVvs~f0WIG;?{QKVy$>*=TegFQ^Y;7nrmY0CM6j}HV=3Ut}U%@xrl*C57N6e}Jx zHH%^yl`o)*MTAlarLlx0-dNA^weR!p?LUA_5fQchMv2d}(jq*om1t;NRn654>8sj@ zrCMMo(i8^|Xz8q1SC zaFj$#6UkX^WP6XHC$hvQPsXmRA$#~3(b{?@I=eZxZX*o~I;fl74z7pt8zKUMK4o-)?9B0RIUe}GMS+^ zw=Db?nxV-!L{f32Z85xMC!1G2$ic@~<3yvzu?88g78RzYsPLk!RMYfTrej%U22%QA z!k4fxAj!>40MK;m3Wm17Piod&+_5pnc5EfmP;Z)$K6S=Ur%AQ9(f96Jnoc{N%>Mn1 z?cYoD`R8FtoAUSsk-9Xwo}*Z?IMTAAT;}*2>*#)C1BIb6>|~5seHy1GiIt3D#bUk* zM6u!#w1DE^5WR1|$M&E8hTRW6POht$NKJ~M*=ZoP2z#9RWXdR_WzIP|WQzujFHvca z38_)qBr?*nC{K*idc~*7_H^O73hkE29omnG#w*;yQjy5iDI}-2Gq~n0(hE+ZcK$-T zo_`8Ek!149bCET*2-~6e&DU|qMzIqKoaPp4W_2)eU_Y6Ihp=mFNY0o-dggSJQzw~p zR|?!hj`GL^qx%jsv~v$?B8wG^;v|v?VQLOqXrBsH#VZyXt;9);X3;X|98EPPUWkxc z9WX?+uHx@n7OIeE#*Meqc*dD@{`O&pxBf3k2V57S!7JvOeEAiWMn@UnvJnMLyX10) zKiom#*fG3PiPX$lEd2T^WHLqXUtXkC$`eV%DUXdZvTFzKi9Uq1a0>-g$*pQ}c__Dx z%H^qfBh_s`iT~ibAdz@j}Uj*k6C(-il@WladNBKYd^iZuTG+CrT zNQo+zvEvDv&X`Ma_-eEzh&4_ErEv2(?Alu9-f}w=A8zM^tsAi{i_zUXX<2+4UEN(o zYHP_I>7+C~MBTjk#M|4+u78sg+qdGL7{E%zu~P}WVgV}}!;VH!_ar7yrGDvh60_%k6cootiA`&xJT``~ZDP|~NiSSX&FuL^k|`RN zpFweKgyDC0a^lUuP#zutTUH3(A+gIpbjT|fRdjq1&2!Gx72`CehkPtf`qv=Uq(0`Ipf5>Pt-Q*#=rLerP|X!4uRkTLDTju>Ngy z$wNk?Sg}aRatp^7gbwMlLhFzQ5Ee#tzz5SJBuZ(m4N~%aqOqu=qiW&H&V)KUU5AXt zIsW9rJNZ1?-v{yg~sP zjbbO_;FZaC9-=TXfUqnq8_I)y^t|v4$6tI7;dxBEq|Zy~+w)-e4Om(lau%TOw!q@-rf0_x^2 zpfohZz}5}eHEGHNgT!0gsavuPRVp#?-g?}TVeDke9|Q0Vr3Yb>rGVtEf*A-=p}XsV z1X{%)Z=QXQt{z|tcqO7r?L%s*tYV7?bQ7V$(}t_BT)bkAMEeYC7cE0bi|o;E@<%(# z9qXpHV*z!GmmnOQ!oVOX&53v4rgokQlckXn1~JGPPS zI)ojIo0PVy*+EFS^hq8etG#L_K^vh2b;h7pDVk@Wtxsx>23%WFnG2{ekO`^rh^pbK zu>Fi_sTK05l1psbG*WZs5~*)QD99c?f>$mv{k%&^FFuX2-P@Shw;Pd6F}!;_DxW7c zy&b2q9&8(LY@E?OyD0V@Lpl*tunh_xqB4;b5+8!$kI>^&ae&ooPw;nPoM2@PEnrm! zQuPx^Vd&-I_?y)JSdA&J8P>S(e3Uf8vjnPC#48k$(HPO@Mxs-u5{;#ZO=)G$Ki`3E zLFb?Tz|j5=iA3Y5VxGdpD8>GM3jMuMDk38>M8q-nt%6Q|pq*C80j#jRgZ~*)rD^SZ z6+!^MdH7v%n<`d#5FdpfM}rp&U+t-CD;pl3sth4104t=Q;30Tiy2ZQD2bz3%}DO z!jU^)*+Ys<@NPvFLE1v1P=Nr1-$vIJ_1D2{j-mpf0>*ju9FnY>@6w8LE(1uMcp@0) zF`ubmP{U`h1uk1uWRE_1Y)*)(;Vld$HqaQO)#7CKTVM)8U{?bm9k3~d@;j*-rK{TJ zRWVeGs!36;K7t-~I9F))BK+p553+KQu8!WrFW@yK-$oi=5{#wS^+mYm>USgqj|>D2HQwL zDxV(^yreP*Y&tX5nW6A~gYmYqibLajVm}S8RFbNowWfOht}_28t#E^3FEAi5_=h)- ziLgEy%p!+H%__N`v@c$`KWHT? zTnM|GH7Pza1zw##eq`Xg>N|m*ijzhSIB86k|1))_Lk$w>z(H#He`aEX!-bAph|r=c z{8wFN29*iO>K6sX4{2FOfWEfdfm;wKjf{$_TOl+9;g8fxe=%Ljl0Q0kTFD^-T<$YS z0&Y$aLACEYMAb-jFb8aAQA5{K<;g2)ZP3HeRfGPauL_1ewLW>mT~x+I{y%yh@0LMv REbssT002ovPDHLkV1hFs{?Gsb literal 0 HcmV?d00001 diff --git a/surfsense_desktop/electron-builder.yml b/surfsense_desktop/electron-builder.yml index 90568a637..0a7c48203 100644 --- a/surfsense_desktop/electron-builder.yml +++ b/surfsense_desktop/electron-builder.yml @@ -77,7 +77,7 @@ nsis: createDesktopShortcut: true createStartMenuShortcut: true linux: - icon: assets/icon.png + icon: assets/icons/ category: Utility artifactName: "${productName}-${version}-${arch}.${ext}" mimeTypes: From df698e021652f6472de09a1006094f415da09309 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Sat, 23 May 2026 00:17:23 +0530 Subject: [PATCH 035/198] feat(observability): integrate OpenTelemetry collector and configuration for enhanced telemetry --- docker/.env.example | 22 +++++ docker/docker-compose.yml | 23 ++++++ docker/otel-collector/config.yaml | 81 +++++++++++++++++++ surfsense_backend/.env.example | 7 +- .../app/observability/bootstrap.py | 7 +- .../tests/unit/observability/test_otel.py | 27 ++++++- 6 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 docker/otel-collector/config.yaml diff --git a/docker/.env.example b/docker/.env.example index 4de35a5e9..2ff15e71d 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -304,6 +304,28 @@ STT_SERVICE=local/base # LANGSMITH_API_KEY= # LANGSMITH_PROJECT=surfsense +# OpenTelemetry traces and metrics. +# Enable the collector with: docker compose --profile observability up -d +# SURFSENSE_ENABLE_OTEL=true +# OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 +# OTEL_EXPORTER_OTLP_PROTOCOL=grpc +# OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=production,service.namespace=surfsense +# +# Emergency kill switch. +# OTEL_SDK_DISABLED=true +# +# Grafana Cloud OTLP credentials. These are used only by the collector container. +# GRAFANA_CLOUD_OTLP_ENDPOINT=https://otlp-gateway-.grafana.net/otlp +# GRAFANA_CLOUD_INSTANCE_ID= +# GRAFANA_CLOUD_API_KEY= +# +# Optional host port overrides for the bundled OTel Collector. Only change +# these if the host already uses 4317/4318/13133; backend containers still use +# the internal Docker endpoint above. +# OTEL_GRPC_PORT=4317 +# OTEL_HTTP_PORT=4318 +# OTEL_HEALTH_PORT=13133 + # ------------------------------------------------------------------------------ # Advanced (optional) # ------------------------------------------------------------------------------ diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 82d77f826..06a3ac79a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -61,6 +61,29 @@ services: timeout: 5s retries: 5 + otel-collector: + image: otel/opentelemetry-collector-contrib:0.152.1 + profiles: + - observability + command: ["--config=/etc/otelcol/config.yaml"] + volumes: + - ./otel-collector/config.yaml:/etc/otelcol/config.yaml:ro + environment: + GRAFANA_CLOUD_OTLP_ENDPOINT: ${GRAFANA_CLOUD_OTLP_ENDPOINT:-} + GRAFANA_CLOUD_INSTANCE_ID: ${GRAFANA_CLOUD_INSTANCE_ID:-} + GRAFANA_CLOUD_API_KEY: ${GRAFANA_CLOUD_API_KEY:-} + ports: + - "${OTEL_GRPC_PORT:-4317}:4317" + - "${OTEL_HTTP_PORT:-4318}:4318" + - "${OTEL_HEALTH_PORT:-13133}:13133" + mem_limit: 2g + restart: unless-stopped + healthcheck: + test: ["CMD", "/otelcol-contrib", "--version"] + interval: 30s + timeout: 5s + retries: 3 + searxng: image: searxng/searxng:2026.3.13-3c1f68c59 volumes: diff --git a/docker/otel-collector/config.yaml b/docker/otel-collector/config.yaml new file mode 100644 index 000000000..f495eff9b --- /dev/null +++ b/docker/otel-collector/config.yaml @@ -0,0 +1,81 @@ +extensions: + health_check: + endpoint: 0.0.0.0:13133 + basicauth/grafana_cloud: + client_auth: + username: ${env:GRAFANA_CLOUD_INSTANCE_ID} + password: ${env:GRAFANA_CLOUD_API_KEY} + +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + +processors: + # Percentage limits are calculated against the collector container memory limit. + # Keep docker-compose.yml/Coolify memory limit set for predictability. + memory_limiter: + check_interval: 1s + limit_percentage: 80 + spike_limit_percentage: 25 + + attributes/scrub: + actions: + - key: http.request.header.authorization + action: delete + - key: http.request.header.cookie + action: delete + - key: db.statement + action: delete + + tail_sampling: + decision_wait: 10s + num_traces: 50000 + expected_new_traces_per_sec: 100 + policies: + - name: errors + type: status_code + status_code: + status_codes: [ERROR] + - name: slow-requests + type: latency + latency: + threshold_ms: 500 + - name: baseline + type: probabilistic + probabilistic: + sampling_percentage: 100 + + batch: + timeout: 5s + send_batch_size: 1024 + send_batch_max_size: 2048 + +exporters: + otlp_http/grafana_cloud: + endpoint: ${env:GRAFANA_CLOUD_OTLP_ENDPOINT} + auth: + authenticator: basicauth/grafana_cloud + sending_queue: + enabled: true + queue_size: 10000 + retry_on_failure: + enabled: true + initial_interval: 5s + max_interval: 30s + max_elapsed_time: 300s + +service: + extensions: [health_check, basicauth/grafana_cloud] + pipelines: + traces: + receivers: [otlp] + processors: [memory_limiter, attributes/scrub, tail_sampling, batch] + exporters: [otlp_http/grafana_cloud] + metrics: + receivers: [otlp] + processors: [memory_limiter, batch] + exporters: [otlp_http/grafana_cloud] diff --git a/surfsense_backend/.env.example b/surfsense_backend/.env.example index 908f4645d..91b03770e 100644 --- a/surfsense_backend/.env.example +++ b/surfsense_backend/.env.example @@ -310,10 +310,9 @@ LANGSMITH_PROJECT=surfsense # use http://otel-lgtm:4317 instead. # OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 # OTEL_EXPORTER_OTLP_PROTOCOL=grpc # or http/protobuf -# OTEL_SERVICE_NAME=surfsense-backend -# OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production -# OTEL_METRIC_EXPORT_INTERVAL=60000 # ms -# OTEL_SDK_DISABLED=false # spec kill-switch +# OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=production,service.namespace=surfsense +# OTEL_METRIC_EXPORT_INTERVAL=300000 # ms; 5 minutes +# OTEL_SDK_DISABLED=true # emergency kill-switch # Skills + subagents # SURFSENSE_ENABLE_SKILLS=false diff --git a/surfsense_backend/app/observability/bootstrap.py b/surfsense_backend/app/observability/bootstrap.py index ad1d02ea8..f9ed65e7b 100644 --- a/surfsense_backend/app/observability/bootstrap.py +++ b/surfsense_backend/app/observability/bootstrap.py @@ -71,7 +71,7 @@ def _build_resource(): "service.name": os.environ.get("OTEL_SERVICE_NAME", "surfsense-backend"), "service.version": _package_version(), "service.instance.id": socket.gethostname(), - "deployment.environment": _deployment_environment(), + "deployment.environment.name": _deployment_environment(), } ) @@ -336,7 +336,10 @@ def init_logs() -> None: def _run() -> None: from opentelemetry.instrumentation.logging import LoggingInstrumentor - LoggingInstrumentor().instrument() + # Required for stdlib LogRecords to receive otelTraceID/otelSpanID. + # logging.basicConfig is already installed by main.py, so this does not + # take over formatting in normal app startup. + LoggingInstrumentor().instrument(set_logging_format=True) if _safe_instrument("logging", _run): _LOGS_INITIALIZED = True diff --git a/surfsense_backend/tests/unit/observability/test_otel.py b/surfsense_backend/tests/unit/observability/test_otel.py index be40cccde..52ccba82f 100644 --- a/surfsense_backend/tests/unit/observability/test_otel.py +++ b/surfsense_backend/tests/unit/observability/test_otel.py @@ -113,12 +113,37 @@ class TestBootstrapConfig: resource = bootstrap._build_resource() attrs = dict(resource.attributes) assert attrs["service.name"] == "custom-backend" - assert attrs["deployment.environment"] == "test" + assert attrs["deployment.environment.name"] == "test" assert attrs["service.instance.id"] def test_shutdown_is_safe_without_providers(self) -> None: bootstrap.shutdown_otel() + def test_init_logs_enables_log_correlation( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + calls: list[dict[str, object]] = [] + + class FakeLoggingInstrumentor: + def instrument(self, **kwargs: object) -> None: + calls.append(kwargs) + + def fake_safe_instrument(name: str, callback): + assert name == "logging" + monkeypatch.setattr( + "opentelemetry.instrumentation.logging.LoggingInstrumentor", + FakeLoggingInstrumentor, + ) + callback() + return True + + monkeypatch.setattr(bootstrap, "_LOGS_INITIALIZED", False) + monkeypatch.setattr(bootstrap, "_safe_instrument", fake_safe_instrument) + + bootstrap.init_logs() + + assert calls == [{"set_logging_format": True}] + class TestMetricHelpers: def test_all_metric_helpers_noop_safely_when_disabled(self) -> None: From 4c8d47617da5619c8ddcdc1a6f52136435d2fe4d Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Sat, 23 May 2026 02:13:24 +0530 Subject: [PATCH 036/198] feat(env): add SURFSENSE_ENV variable for deployment environment and update observability resource attributes --- docker/.env.example | 5 ++++- surfsense_backend/.env.example | 5 ++++- surfsense_backend/app/observability/bootstrap.py | 13 ++++++------- .../tests/unit/observability/test_otel.py | 12 ++++++++++++ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/docker/.env.example b/docker/.env.example index 2ff15e71d..748f03048 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -7,6 +7,9 @@ # SurfSense version (use "latest" or a specific version like "0.0.14") SURFSENSE_VERSION=latest +# Deployment environment: dev or production +SURFSENSE_ENV=production + # ------------------------------------------------------------------------------ # Core Settings # ------------------------------------------------------------------------------ @@ -309,7 +312,7 @@ STT_SERVICE=local/base # SURFSENSE_ENABLE_OTEL=true # OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 # OTEL_EXPORTER_OTLP_PROTOCOL=grpc -# OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=production,service.namespace=surfsense +# OTEL_RESOURCE_ATTRIBUTES=service.namespace=surfsense # # Emergency kill switch. # OTEL_SDK_DISABLED=true diff --git a/surfsense_backend/.env.example b/surfsense_backend/.env.example index 91b03770e..b05369412 100644 --- a/surfsense_backend/.env.example +++ b/surfsense_backend/.env.example @@ -1,5 +1,8 @@ DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/surfsense +# Deployment environment: dev or production +SURFSENSE_ENV=dev + #Celery Config CELERY_BROKER_URL=redis://localhost:6379/0 CELERY_RESULT_BACKEND=redis://localhost:6379/0 @@ -310,7 +313,7 @@ LANGSMITH_PROJECT=surfsense # use http://otel-lgtm:4317 instead. # OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 # OTEL_EXPORTER_OTLP_PROTOCOL=grpc # or http/protobuf -# OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=production,service.namespace=surfsense +# OTEL_RESOURCE_ATTRIBUTES=service.namespace=surfsense # OTEL_METRIC_EXPORT_INTERVAL=300000 # ms; 5 minutes # OTEL_SDK_DISABLED=true # emergency kill-switch diff --git a/surfsense_backend/app/observability/bootstrap.py b/surfsense_backend/app/observability/bootstrap.py index f9ed65e7b..70008d43d 100644 --- a/surfsense_backend/app/observability/bootstrap.py +++ b/surfsense_backend/app/observability/bootstrap.py @@ -55,23 +55,22 @@ def _package_version() -> str: def _deployment_environment() -> str: - return ( - os.environ.get("SURFSENSE_ENV") - or os.environ.get("APP_ENV") - or os.environ.get("ENVIRONMENT") - or "dev" - ) + return os.environ.get("SURFSENSE_ENV", "dev") def _build_resource(): from opentelemetry.sdk.resources import Resource + deployment_environment = _deployment_environment() return Resource.create( { "service.name": os.environ.get("OTEL_SERVICE_NAME", "surfsense-backend"), "service.version": _package_version(), "service.instance.id": socket.gethostname(), - "deployment.environment.name": _deployment_environment(), + "deployment.environment.name": deployment_environment, + # Compatibility alias for Grafana onboarding checks that still use + # the older semantic-convention key. + "deployment.environment": deployment_environment, } ) diff --git a/surfsense_backend/tests/unit/observability/test_otel.py b/surfsense_backend/tests/unit/observability/test_otel.py index 52ccba82f..d3718e7b9 100644 --- a/surfsense_backend/tests/unit/observability/test_otel.py +++ b/surfsense_backend/tests/unit/observability/test_otel.py @@ -114,8 +114,20 @@ class TestBootstrapConfig: attrs = dict(resource.attributes) assert attrs["service.name"] == "custom-backend" assert attrs["deployment.environment.name"] == "test" + assert attrs["deployment.environment"] == "test" assert attrs["service.instance.id"] + def test_deployment_environment_uses_surfsense_env_only( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + monkeypatch.delenv("SURFSENSE_ENV", raising=False) + + assert bootstrap._deployment_environment() == "dev" + + monkeypatch.setenv("SURFSENSE_ENV", "production") + + assert bootstrap._deployment_environment() == "production" + def test_shutdown_is_safe_without_providers(self) -> None: bootstrap.shutdown_otel() From 6302939a72a19f7e58f76420306a0a6278390c80 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Sat, 23 May 2026 03:18:25 +0530 Subject: [PATCH 037/198] feat(docs): add observability documentation --- surfsense_web/content/docs/meta.json | 1 + surfsense_web/content/docs/observability.mdx | 167 +++++++++++++++++++ surfsense_web/lib/source.ts | 2 + 3 files changed, 170 insertions(+) create mode 100644 surfsense_web/content/docs/observability.mdx diff --git a/surfsense_web/content/docs/meta.json b/surfsense_web/content/docs/meta.json index a0b6f8a1b..13b599118 100644 --- a/surfsense_web/content/docs/meta.json +++ b/surfsense_web/content/docs/meta.json @@ -13,6 +13,7 @@ "how-to", "---Developers---", "testing", + "observability", "code-of-conduct" ] } diff --git a/surfsense_web/content/docs/observability.mdx b/surfsense_web/content/docs/observability.mdx new file mode 100644 index 000000000..c6dfb7e3a --- /dev/null +++ b/surfsense_web/content/docs/observability.mdx @@ -0,0 +1,167 @@ +--- +title: Observability +description: Configure backend traces and metrics for SurfSense +icon: Radar +--- + +SurfSense uses OpenTelemetry for backend traces and metrics. Application logs +include trace and span IDs so you can correlate logs with traces, but logs stay +on the normal container stderr path. + +## Enable Locally + +The development compose file reads backend settings from +`surfsense_backend/.env`. Add these values there: + +```dotenv +SURFSENSE_ENABLE_OTEL=true +SURFSENSE_ENV=dev +OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-lgtm:4317 +OTEL_EXPORTER_OTLP_PROTOCOL=grpc +OTEL_RESOURCE_ATTRIBUTES=service.namespace=surfsense +OTEL_METRIC_EXPORT_INTERVAL=300000 +``` + +Then start the development stack with the local LGTM backend: + +```bash +docker compose -f docker/docker-compose.dev.yml up --build +``` + +Grafana is exposed on `http://localhost:3001` by default. + +## Enable in Production Docker Compose + +Production Docker Compose reads backend and collector settings from +`docker/.env`. The API and Celery worker export telemetry to the bundled +collector at `otel-collector:4317`; the collector is the only service that uses +the Grafana Cloud credentials. + +Add these values to `docker/.env`: + +```dotenv +SURFSENSE_ENV=production +SURFSENSE_ENABLE_OTEL=true +OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 +OTEL_EXPORTER_OTLP_PROTOCOL=grpc +OTEL_RESOURCE_ATTRIBUTES=service.namespace=surfsense +OTEL_METRIC_EXPORT_INTERVAL=300000 + +GRAFANA_CLOUD_OTLP_ENDPOINT=https://otlp-gateway-.grafana.net/otlp +GRAFANA_CLOUD_INSTANCE_ID= +GRAFANA_CLOUD_API_KEY= +``` + +Then start the stack: + +```bash +docker compose -f docker/docker-compose.yml --profile observability up -d +``` + +The collector receives OTLP on `otel-collector:4317`, scrubs sensitive span +attributes, applies the configured tail-sampling policy, batches exports, +retries failures, and forwards traces and metrics to Grafana Cloud over OTLP +HTTP. + +When deploying `surfsense_backend/Dockerfile` directly instead of production +compose, use the same split: SurfSense containers export to a collector, and +the collector owns the Grafana Cloud credentials. + +## Automatic Traces + +When OpenTelemetry is enabled, the backend instruments: + +- FastAPI inbound requests. +- SQLAlchemy queries from the main async engine and Celery task engine. +- Raw psycopg calls used by the LangGraph checkpointer. +- Redis commands. +- HTTPX outbound requests. +- Celery producer and worker execution. + +## Manual Spans + +SurfSense keeps project-specific spans behind `app.observability.otel`: + +- `model.call` +- `tool.call` +- `chat.request` +- `kb.search` +- `kb.persist` +- `connector.sync` +- `subagent.invoke` +- `etl.extract` +- `etl.parse` +- `etl.ocr` +- `etl.picture.describe` +- `etl.picture.ocr` +- `compaction.run` +- `permission.asked` +- `interrupt.raised` + +Keep span names and attributes low-cardinality. Do not attach user content, +prompts, document titles, file paths, user-specific URLs, secrets, or raw +queries as span attributes. + +## Metrics + +The OpenTelemetry instrumentors provide HTTP, HTTPX, and Celery runtime +metrics. SurfSense adds these project metrics from `app.observability.metrics`: + +- `surfsense.model.call.duration` +- `gen_ai.client.token.usage` +- `surfsense.tool.call.duration` +- `surfsense.tool.call.errors` +- `surfsense.chat.request.duration` +- `surfsense.chat.request.outcome` +- `surfsense.kb.search.duration` +- `surfsense.compaction.runs` +- `surfsense.permission.asks` +- `surfsense.interrupt.raised` +- `surfsense.indexing.document.duration` +- `surfsense.indexing.document.outcome` +- `surfsense.connector.sync.duration` +- `surfsense.connector.sync.outcome` +- `surfsense.subagent.invoke.duration` +- `surfsense.subagent.invoke.outcome` +- `surfsense.etl.extract.duration` +- `surfsense.etl.extract.outcome` +- `surfsense.celery.heartbeat.refreshes` +- `surfsense.celery.heartbeat.failures` +- `surfsense.celery.queue.latency` +- `surfsense.auth.failures` +- `surfsense.rate_limit.rejections` +- `surfsense.perf.elapsed_ms` + +Runtime gauges include process RSS, CPU utilization, threads, open file +descriptors, asyncio tasks, and CPython GC counters. + +## Logs + +`LoggingInstrumentor().instrument()` injects `otelTraceID` and `otelSpanID` into +standard Python `LogRecord`s. The root log format writes them as +`trace_id=... span_id=...`. + +SurfSense intentionally does not create an OpenTelemetry `LoggerProvider`, +`LoggingHandler`, or `OTLPLogExporter`. Container stderr remains the log +transport. + +## Verification + +1. Hit a FastAPI endpoint and confirm an inbound server span appears in Grafana. +2. Run a chat request and confirm `model.call` and `tool.call` child spans. +3. Run a knowledge-base search and confirm `kb.search` spans and SQL child spans. +4. Run connector indexing and confirm Celery producer/worker spans share a trace + ID and connector sync metrics increment. +5. Confirm `gen_ai.client.token.usage`, model/tool durations, request duration, + Celery runtime, and runtime gauges appear within one export interval. +6. Confirm logs emitted inside a traced request show non-zero trace and span IDs. + +## Out Of Scope + +- Frontend/browser OpenTelemetry. +- OpenTelemetry log export. +- Profiling. +- Production backend selection. +- Tail-sampling collector configuration. +- Replacing LangSmith. +- Vendor SDKs. diff --git a/surfsense_web/lib/source.ts b/surfsense_web/lib/source.ts index b94f990ab..62fbb362b 100644 --- a/surfsense_web/lib/source.ts +++ b/surfsense_web/lib/source.ts @@ -7,6 +7,7 @@ import { Download, FlaskConical, Heart, + Radar, Unplug, Wrench, } from "lucide-react"; @@ -26,6 +27,7 @@ const DOCS_ICONS: Record = { Download, FlaskConical, Heart, + Radar, Unplug, Wrench, }; From aa86534a5247254a05e472a1187db00f42d327de Mon Sep 17 00:00:00 2001 From: suryo12 Date: Sat, 23 May 2026 23:22:18 +0700 Subject: [PATCH 038/198] refactor(web): centralize OAuth callback cookie contract (fixes #1362) Replace the duplicated `OAUTH_RESULT_COOKIE` constant and inline payload type across the callback route and connector dialog hook with a shared `contracts/types/oauth.types.ts` module that exports: - OAUTH_RESULT_COOKIE constant - oauthCallbackResultSchema Zod schema - OAuthCallbackResult type (inferred from the schema) - parseOAuthCallbackResult() helper that returns null on invalid JSON or shape mismatch The route handler now uses the shared type to constrain the cookie payload at compile time. The consumer hook validates the cookie value through the helper instead of an unchecked JSON.parse, removing the silent runtime risk when the cookie is tampered with or its shape drifts. --- .../connectors/callback/route.ts | 8 ++--- .../hooks/use-connector-dialog.ts | 18 +++-------- surfsense_web/contracts/types/oauth.types.ts | 31 +++++++++++++++++++ 3 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 surfsense_web/contracts/types/oauth.types.ts diff --git a/surfsense_web/app/dashboard/[search_space_id]/connectors/callback/route.ts b/surfsense_web/app/dashboard/[search_space_id]/connectors/callback/route.ts index 14573066d..304f33a33 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/connectors/callback/route.ts +++ b/surfsense_web/app/dashboard/[search_space_id]/connectors/callback/route.ts @@ -1,6 +1,5 @@ import { type NextRequest, NextResponse } from "next/server"; - -const OAUTH_RESULT_COOKIE = "connector_oauth_result"; +import { OAUTH_RESULT_COOKIE, type OAuthCallbackResult } from "@/contracts/types/oauth.types"; export async function GET( request: NextRequest, @@ -9,12 +8,13 @@ export async function GET( const { search_space_id } = await params; const searchParams = request.nextUrl.searchParams; - const result = JSON.stringify({ + const payload: OAuthCallbackResult = { success: searchParams.get("success"), error: searchParams.get("error"), connector: searchParams.get("connector"), connectorId: searchParams.get("connectorId"), - }); + }; + const result = JSON.stringify(payload); const redirectUrl = new URL(`/dashboard/${search_space_id}/new-chat`, request.url); diff --git a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts index d1d675ad1..25ab82e2e 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/hooks/use-connector-dialog.ts @@ -14,7 +14,9 @@ import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-quer import { EnumConnectorName } from "@/contracts/enums/connector"; import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import { searchSourceConnector } from "@/contracts/types/connector.types"; +import { OAUTH_RESULT_COOKIE, parseOAuthCallbackResult } from "@/contracts/types/oauth.types"; import { authenticatedFetch } from "@/lib/auth-utils"; +import { BACKEND_URL } from "@/lib/env-config"; import { trackConnectorConnected, trackConnectorDeleted, @@ -36,15 +38,12 @@ import { OAUTH_CONNECTORS, OTHER_CONNECTORS, } from "../constants/connector-constants"; - import { dateRangeSchema, frequencyMinutesSchema, parseOAuthAuthResponse, validateIndexingConfigState, } from "../constants/connector-popup.schemas"; -import { BACKEND_URL } from "@/lib/env-config"; -const OAUTH_RESULT_COOKIE = "connector_oauth_result"; function readOAuthResultCookie(): string | null { const match = document.cookie @@ -211,17 +210,8 @@ export const useConnectorDialog = () => { if (!raw || !searchSpaceId) return; clearOAuthResultCookie(); - let result: { - success: string | null; - error: string | null; - connector: string | null; - connectorId: string | null; - }; - try { - result = JSON.parse(raw); - } catch { - return; - } + const result = parseOAuthCallbackResult(raw); + if (!result) return; if (result.error) { const oauthConnector = result.connector diff --git a/surfsense_web/contracts/types/oauth.types.ts b/surfsense_web/contracts/types/oauth.types.ts new file mode 100644 index 000000000..8b3854cf8 --- /dev/null +++ b/surfsense_web/contracts/types/oauth.types.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; + +export const OAUTH_RESULT_COOKIE = "connector_oauth_result"; + +/** + * Schema for the payload written to the `connector_oauth_result` cookie by the + * OAuth callback route and read back by the connector dialog hook. + */ +export const oauthCallbackResultSchema = z.object({ + success: z.string().nullable(), + error: z.string().nullable(), + connector: z.string().nullable(), + connectorId: z.string().nullable(), +}); + +export type OAuthCallbackResult = z.infer; + +/** + * Safely decode and validate the OAuth callback cookie value. Returns `null` + * when the value is not valid JSON or does not match the expected shape. + */ +export function parseOAuthCallbackResult(raw: string): OAuthCallbackResult | null { + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch { + return null; + } + const result = oauthCallbackResultSchema.safeParse(parsed); + return result.success ? result.data : null; +} From f382cb296caec66b2a7e0907db95739743232a14 Mon Sep 17 00:00:00 2001 From: dekalouis Date: Sun, 24 May 2026 15:26:40 +0700 Subject: [PATCH 039/198] refactor: migrate PromptConfigManager save to updateSearchSpaceMutationAtom --- .../settings/prompt-config-manager.tsx | 48 +++++++------------ 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/surfsense_web/components/settings/prompt-config-manager.tsx b/surfsense_web/components/settings/prompt-config-manager.tsx index 871098bc9..71cfcb971 100644 --- a/surfsense_web/components/settings/prompt-config-manager.tsx +++ b/surfsense_web/components/settings/prompt-config-manager.tsx @@ -1,19 +1,19 @@ "use client"; import { useQuery } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; import { AlertTriangle, Info } from "lucide-react"; import { useEffect, useState } from "react"; import { toast } from "sonner"; +import { updateSearchSpaceMutationAtom } from "@/atoms/search-spaces/search-space-mutation.atoms"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Skeleton } from "@/components/ui/skeleton"; import { Textarea } from "@/components/ui/textarea"; import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service"; -import { authenticatedFetch } from "@/lib/auth-utils"; import { cacheKeys } from "@/lib/query-client/cache-keys"; import { Spinner } from "../ui/spinner"; -import { BACKEND_URL } from "@/lib/env-config"; interface PromptConfigManagerProps { searchSpaceId: number; @@ -23,15 +23,17 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps) const { data: searchSpace, isLoading: loading, - refetch: fetchSearchSpace, } = useQuery({ queryKey: cacheKeys.searchSpaces.detail(searchSpaceId.toString()), queryFn: () => searchSpacesApiService.getSearchSpace({ id: searchSpaceId }), enabled: !!searchSpaceId, }); + const { mutateAsync: updateSearchSpace, isPending: isSaving } = useAtomValue( + updateSearchSpaceMutationAtom + ); + const [customInstructions, setCustomInstructions] = useState(""); - const [saving, setSaving] = useState(false); const hasSearchSpace = !!searchSpace; const searchSpaceInstructions = searchSpace?.qna_custom_instructions; @@ -48,34 +50,16 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps) const handleSave = async () => { try { - setSaving(true); - - const payload = { - qna_custom_instructions: customInstructions.trim() || "", - }; - - const response = await authenticatedFetch( - `${BACKEND_URL}/api/v1/searchspaces/${searchSpaceId}`, - { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - } - ); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.detail || "Failed to save system instructions"); - } - + await updateSearchSpace({ + id: searchSpaceId, + data: { qna_custom_instructions: customInstructions.trim() || "" }, + }); toast.success("System instructions saved successfully"); - - await fetchSearchSpace(); } catch (error: unknown) { + const message = + error instanceof Error ? error.message : "Failed to save system instructions"; console.error("Error saving system instructions:", error); - toast.error(error instanceof Error ? error.message : "Failed to save system instructions"); - } finally { - setSaving(false); + toast.error(message); } }; @@ -184,11 +168,11 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps)

From ddae50663174fbcd1ab78cabbf012002a9af3054 Mon Sep 17 00:00:00 2001 From: suryo12 Date: Sun, 24 May 2026 16:41:47 +0700 Subject: [PATCH 040/198] refactor(web): replace slideout panel window event with jotai atom (fixes #1358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the `SLIDEOUT_PANEL_OPENED_EVENT` window event with a `slideoutOpenedTickAtom` jotai atom. The dispatcher in `SidebarSlideOutPanel` now bumps the tick via `useSetAtom`, and the listener in `Thread` reads it via `useAtomValue` and reacts on change behind a ref guard that skips the initial render — preserving the one-shot-per-open semantics of the previous event. This removes the implicit cross-module string contract, makes the signal traceable through React DevTools / jotai inspector, and lets TypeScript catch typos that the string-based event API silently swallowed. --- .../components/assistant-ui/thread.tsx | 21 +++++++++++-------- .../ui/sidebar/SidebarSlideOutPanel.tsx | 8 ++++--- surfsense_web/lib/layout-events.ts | 10 ++++++++- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 6876ce23e..24f070b4e 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -105,7 +105,7 @@ import { useMediaQuery } from "@/hooks/use-media-query"; import { useElectronAPI } from "@/hooks/use-platform"; import { captureDisplayToPngDataUrl } from "@/lib/chat/display-media-capture"; import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; -import { SLIDEOUT_PANEL_OPENED_EVENT } from "@/lib/layout-events"; +import { slideoutOpenedTickAtom } from "@/lib/layout-events"; import { cn } from "@/lib/utils"; const COMPOSER_PLACEHOLDER = "Ask anything, type / for prompts, type @ to mention docs"; @@ -478,15 +478,18 @@ const Composer: FC = () => { editorRef.current?.focus(); }, [isDesktop, showDocumentPopover, showPromptPicker, threadId]); - // Close document picker when a slide-out panel (inbox, etc.) opens. + // Close document picker when a sidebar slide-out panel (inbox, etc.) opens. + const slideoutOpenedTick = useAtomValue(slideoutOpenedTickAtom); + const isFirstSlideoutTickRef = useRef(true); useEffect(() => { - const handler = () => { - setShowDocumentPopover(false); - setMentionQuery(""); - }; - window.addEventListener(SLIDEOUT_PANEL_OPENED_EVENT, handler); - return () => window.removeEventListener(SLIDEOUT_PANEL_OPENED_EVENT, handler); - }, []); + void slideoutOpenedTick; + if (isFirstSlideoutTickRef.current) { + isFirstSlideoutTickRef.current = false; + return; + } + setShowDocumentPopover(false); + setMentionQuery(""); + }, [slideoutOpenedTick]); // Sync editor text into assistant-ui's composer and mirror the chip // atom from the editor's reported ``docs``. The editor is the diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarSlideOutPanel.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarSlideOutPanel.tsx index 3fa4dd5d3..52b2cf998 100644 --- a/surfsense_web/components/layout/ui/sidebar/SidebarSlideOutPanel.tsx +++ b/surfsense_web/components/layout/ui/sidebar/SidebarSlideOutPanel.tsx @@ -1,9 +1,10 @@ "use client"; +import { useSetAtom } from "jotai"; import { AnimatePresence, motion } from "motion/react"; import { useCallback, useEffect } from "react"; import { useIsMobile } from "@/hooks/use-mobile"; -import { SLIDEOUT_PANEL_OPENED_EVENT } from "@/lib/layout-events"; +import { slideoutOpenedTickAtom } from "@/lib/layout-events"; interface SidebarSlideOutPanelProps { open: boolean; @@ -29,12 +30,13 @@ export function SidebarSlideOutPanel({ children, }: SidebarSlideOutPanelProps) { const isMobile = useIsMobile(); + const bumpSlideoutOpenedTick = useSetAtom(slideoutOpenedTickAtom); useEffect(() => { if (open) { - window.dispatchEvent(new Event(SLIDEOUT_PANEL_OPENED_EVENT)); + bumpSlideoutOpenedTick((tick) => tick + 1); } - }, [open]); + }, [open, bumpSlideoutOpenedTick]); const handleEscape = useCallback( (e: KeyboardEvent) => { diff --git a/surfsense_web/lib/layout-events.ts b/surfsense_web/lib/layout-events.ts index 45c52f7a4..27ea8de39 100644 --- a/surfsense_web/lib/layout-events.ts +++ b/surfsense_web/lib/layout-events.ts @@ -1 +1,9 @@ -export const SLIDEOUT_PANEL_OPENED_EVENT = "slideout-panel-opened"; +import { atom } from "jotai"; + +/** + * Tick counter that increments each time a sidebar slide-out panel opens. + * Consumers read this with `useAtomValue` and react to it changing — guard + * the initial render with a ref so the effect only fires on subsequent + * opens, matching the one-shot semantics of the previous window event. + */ +export const slideoutOpenedTickAtom = atom(0); From 29d5ee54659effd363bbceac06198a6cb478f12c Mon Sep 17 00:00:00 2001 From: suryo12 Date: Sun, 24 May 2026 17:23:27 +0700 Subject: [PATCH 041/198] fix(web): remove orphaned hasPermission inline body in roles-manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #1428 (issue #1366) extracted the inline `hasPermission` callback into a shared `canPerform` helper but left the original arrow-function body, its dependency array, and trailing `)` behind after the new `useCallback` block. The result was a syntactically invalid statement that broke `pnpm build` on the `dev` branch and is now blocking every E2E job in the PR queue. Delete the orphaned lines so the file parses again. No behavior change — the working `useCallback(canPerform(access, permission))` already supplies the same predicate the duplicated body did. --- surfsense_web/components/settings/roles-manager.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/surfsense_web/components/settings/roles-manager.tsx b/surfsense_web/components/settings/roles-manager.tsx index 65f47bc43..88595e748 100644 --- a/surfsense_web/components/settings/roles-manager.tsx +++ b/surfsense_web/components/settings/roles-manager.tsx @@ -26,7 +26,7 @@ import { } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; -import { myAccessAtom, canPerform } from "@/atoms/members/members-query.atoms"; +import { canPerform, myAccessAtom } from "@/atoms/members/members-query.atoms"; import { permissionsAtom } from "@/atoms/permissions/permissions-query.atoms"; import { createRoleMutationAtom, @@ -260,13 +260,6 @@ export function RolesManager({ searchSpaceId }: { searchSpaceId: number }) { (permission: string) => canPerform(access, permission), [access] ); - (permission: string) => { - if (!access) return false; - if (access.is_owner) return true; - return access.permissions?.includes(permission) ?? false; - }, - [access] - ); const { data: roles = [], isLoading: rolesLoading } = useQuery({ queryKey: cacheKeys.roles.all(searchSpaceId.toString()), From d571cb23fad78057bae89903a6ebdff378ad932a Mon Sep 17 00:00:00 2001 From: suryo12 Date: Sun, 24 May 2026 18:13:36 +0700 Subject: [PATCH 042/198] refactor(web): use last-seen-tick comparison for slideout listener MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the boolean "skip first render" ref with a ref that stores the previously-seen tick value. The effect now compares against the stored value and only fires when it differs, which makes the dependency naturally used (removes the `void slideoutOpenedTick;` acknowledgement) and self-documents the intent of the guard. Behavior is unchanged — both forms preserve the one-shot-per-event semantics of the prior window-event implementation. The JSDoc on `slideoutOpenedTickAtom` is updated to describe the new pattern. --- surfsense_web/components/assistant-ui/thread.tsx | 12 ++++++------ surfsense_web/lib/layout-events.ts | 8 +++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 24f070b4e..c4f6fed05 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -479,14 +479,14 @@ const Composer: FC = () => { }, [isDesktop, showDocumentPopover, showPromptPicker, threadId]); // Close document picker when a sidebar slide-out panel (inbox, etc.) opens. + // React only on changes to the tick — comparing against the previously-seen + // value preserves the one-shot semantics of the prior window-event approach + // (no retroactive close on mount if a panel had already opened earlier). const slideoutOpenedTick = useAtomValue(slideoutOpenedTickAtom); - const isFirstSlideoutTickRef = useRef(true); + const lastSeenSlideoutTickRef = useRef(slideoutOpenedTick); useEffect(() => { - void slideoutOpenedTick; - if (isFirstSlideoutTickRef.current) { - isFirstSlideoutTickRef.current = false; - return; - } + if (lastSeenSlideoutTickRef.current === slideoutOpenedTick) return; + lastSeenSlideoutTickRef.current = slideoutOpenedTick; setShowDocumentPopover(false); setMentionQuery(""); }, [slideoutOpenedTick]); diff --git a/surfsense_web/lib/layout-events.ts b/surfsense_web/lib/layout-events.ts index 27ea8de39..755329c41 100644 --- a/surfsense_web/lib/layout-events.ts +++ b/surfsense_web/lib/layout-events.ts @@ -2,8 +2,10 @@ import { atom } from "jotai"; /** * Tick counter that increments each time a sidebar slide-out panel opens. - * Consumers read this with `useAtomValue` and react to it changing — guard - * the initial render with a ref so the effect only fires on subsequent - * opens, matching the one-shot semantics of the previous window event. + * Consumers read this with `useAtomValue` and store the last-seen value in + * a ref so the effect fires only when the tick changes. This preserves the + * one-shot semantics of the previous window-event implementation: a + * subscriber that mounts after a panel has already opened does not + * retroactively re-trigger. */ export const slideoutOpenedTickAtom = atom(0); From 7be4231ad4855e2a76b9c80675cc4f71cd069d20 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 25 May 2026 17:45:27 +0530 Subject: [PATCH 043/198] feat(app): update product name to 'SurfSense' and implement server shutdown on app quit --- surfsense_desktop/package.json | 1 + surfsense_desktop/src/main.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/surfsense_desktop/package.json b/surfsense_desktop/package.json index 0ad279ece..4ee6ea3c4 100644 --- a/surfsense_desktop/package.json +++ b/surfsense_desktop/package.json @@ -1,5 +1,6 @@ { "name": "surfsense-desktop", + "productName": "SurfSense", "version": "0.0.25", "description": "SurfSense Desktop App", "main": "dist/main.js", diff --git a/surfsense_desktop/src/main.ts b/surfsense_desktop/src/main.ts index 492c61f17..632758ba8 100644 --- a/surfsense_desktop/src/main.ts +++ b/surfsense_desktop/src/main.ts @@ -1,7 +1,7 @@ import { app } from 'electron'; import { registerGlobalErrorHandlers, showErrorDialog } from './modules/errors'; -import { startNextServer } from './modules/server'; +import { startNextServer, stopNextServer } from './modules/server'; import { createMainWindow, getMainWindow, markQuitting } from './modules/window'; import { setupDeepLinks, handlePendingDeepLink, hasPendingDeepLink } from './modules/deep-links'; import { setupAutoUpdater } from './modules/auto-updater'; @@ -19,6 +19,7 @@ import { } from './modules/auto-launch'; registerGlobalErrorHandlers(); +app.setName('SurfSense'); if (!setupDeepLinks()) { app.quit(); @@ -93,6 +94,7 @@ app.on('will-quit', async (e) => { e.preventDefault(); unregisterQuickAsk(); unregisterFolderWatcher(); + stopNextServer(); destroyTray(); await shutdownAnalytics(); app.exit(); From a2847664c8112d04bb43fdadb30e1185943e6a2b Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 25 May 2026 17:52:10 +0530 Subject: [PATCH 044/198] feat(server): enhance server management with process forking and implement server origin retrieval --- surfsense_desktop/src/modules/server.ts | 53 +++++++++++++++++++++---- surfsense_desktop/src/modules/window.ts | 10 +++++ 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/surfsense_desktop/src/modules/server.ts b/surfsense_desktop/src/modules/server.ts index e2f078a8c..daec9bed0 100644 --- a/surfsense_desktop/src/modules/server.ts +++ b/surfsense_desktop/src/modules/server.ts @@ -1,9 +1,10 @@ import path from 'path'; -import { app } from 'electron'; +import { app, utilityProcess } from 'electron'; import { getPort } from 'get-port-please'; const isDev = !app.isPackaged; let serverPort = 3000; +let nextServerProcess: ReturnType | null = null; export function getServerPort(): number { return serverPort; @@ -38,16 +39,54 @@ export async function startNextServer(): Promise { const standalonePath = getStandalonePath(); const serverScript = path.join(standalonePath, 'server.js'); - process.env.PORT = String(serverPort); - process.env.HOSTNAME = '0.0.0.0'; - process.env.NODE_ENV = 'production'; - process.chdir(standalonePath); + const child = utilityProcess.fork(serverScript, [], { + cwd: standalonePath, + env: { + ...process.env, + PORT: String(serverPort), + HOSTNAME: '127.0.0.1', + NODE_ENV: 'production', + }, + serviceName: 'SurfSense Next Server', + stdio: 'pipe', + }); + nextServerProcess = child; - require(serverScript); + child.stdout?.on('data', (chunk) => { + process.stdout.write(chunk); + }); + child.stderr?.on('data', (chunk) => { + process.stderr.write(chunk); + }); - const ready = await waitForServer(`http://localhost:${serverPort}`); + const handleExit = (code: number) => { + if (nextServerProcess === child) { + nextServerProcess = null; + } + console.error(`Next.js server exited with code ${code}`); + }; + child.on('exit', handleExit); + + let startupExitHandler: ((code: number) => void) | null = null; + const exited = new Promise((_resolve, reject) => { + startupExitHandler = (code: number) => { + reject(new Error(`Next.js server exited before startup completed with code ${code}`)); + }; + child.once('exit', startupExitHandler); + }); + + const ready = await Promise.race([waitForServer(`http://localhost:${serverPort}`), exited]); + if (startupExitHandler) { + child.removeListener('exit', startupExitHandler); + } if (!ready) { + stopNextServer(); throw new Error('Next.js server failed to start within 30 s'); } console.log(`Next.js server ready on port ${serverPort}`); } + +export function stopNextServer(): void { + nextServerProcess?.kill(); + nextServerProcess = null; +} diff --git a/surfsense_desktop/src/modules/window.ts b/surfsense_desktop/src/modules/window.ts index 5317005d5..7ee7eb8db 100644 --- a/surfsense_desktop/src/modules/window.ts +++ b/surfsense_desktop/src/modules/window.ts @@ -8,6 +8,7 @@ import { setActiveSearchSpaceId } from './active-search-space'; const isDev = !app.isPackaged; const HOSTED_FRONTEND_URL = process.env.HOSTED_FRONTEND_URL as string; const isMac = process.platform === 'darwin'; +const WINDOW_TITLE = 'SurfSense'; let mainWindow: BrowserWindow | null = null; let isQuitting = false; @@ -24,6 +25,7 @@ export function markQuitting(): void { export function createMainWindow(initialPath = '/dashboard'): BrowserWindow { mainWindow = new BrowserWindow({ + title: WINDOW_TITLE, width: 1280, height: 800, minWidth: 800, @@ -48,6 +50,14 @@ export function createMainWindow(initialPath = '/dashboard'): BrowserWindow { mainWindow?.show(); }); + mainWindow.webContents.on('page-title-updated', (event) => { + event.preventDefault(); + mainWindow?.setTitle(WINDOW_TITLE); + }); + mainWindow.webContents.on('did-finish-load', () => { + mainWindow?.setTitle(WINDOW_TITLE); + }); + mainWindow.loadURL(`http://localhost:${getServerPort()}${initialPath}`); mainWindow.webContents.setWindowOpenHandler(({ url }) => { From fe797e65d6def309817b0a3c8ef2714fd30fd2de Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 25 May 2026 17:55:03 +0530 Subject: [PATCH 045/198] refactor(deep-links, quick-ask, window): replace localhost references with dynamic server origin retrieval --- surfsense_desktop/src/modules/deep-links.ts | 4 ++-- surfsense_desktop/src/modules/quick-ask.ts | 6 +++--- surfsense_desktop/src/modules/server.ts | 9 +++++++-- surfsense_desktop/src/modules/window.ts | 8 ++++---- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/surfsense_desktop/src/modules/deep-links.ts b/surfsense_desktop/src/modules/deep-links.ts index 11b7bfcff..635e242fe 100644 --- a/surfsense_desktop/src/modules/deep-links.ts +++ b/surfsense_desktop/src/modules/deep-links.ts @@ -1,7 +1,7 @@ import { app } from 'electron'; import path from 'path'; import { getMainWindow } from './window'; -import { getServerPort } from './server'; +import { getServerOrigin } from './server'; import { trackEvent } from './analytics'; const PROTOCOL = 'surfsense'; @@ -23,7 +23,7 @@ function handleDeepLink(url: string) { }); if (parsed.hostname === 'auth' && parsed.pathname === '/callback') { const params = parsed.searchParams.toString(); - win.loadURL(`http://localhost:${getServerPort()}/auth/callback?${params}`); + win.loadURL(`${getServerOrigin()}/auth/callback?${params}`); } win.show(); diff --git a/surfsense_desktop/src/modules/quick-ask.ts b/surfsense_desktop/src/modules/quick-ask.ts index b31ae1bcd..d7602f470 100644 --- a/surfsense_desktop/src/modules/quick-ask.ts +++ b/surfsense_desktop/src/modules/quick-ask.ts @@ -2,7 +2,7 @@ import { BrowserWindow, clipboard, globalShortcut, ipcMain, screen, shell } from import path from 'path'; import { IPC_CHANNELS } from '../ipc/channels'; import { checkAccessibilityPermission, getFrontmostApp, simulateCopy, simulatePaste } from './platform'; -import { getServerPort } from './server'; +import { getServerOrigin } from './server'; import { getShortcuts } from './shortcuts'; import { getActiveSearchSpaceId } from './active-search-space'; import { trackEvent } from './analytics'; @@ -58,7 +58,7 @@ function createQuickAskWindow(x: number, y: number): BrowserWindow { const spaceId = pendingSearchSpaceId; const route = spaceId ? `/dashboard/${spaceId}/new-chat` : '/dashboard'; - quickAskWindow.loadURL(`http://localhost:${getServerPort()}${route}?quickAssist=true`); + quickAskWindow.loadURL(`${getServerOrigin()}${route}?quickAssist=true`); quickAskWindow.once('ready-to-show', () => { quickAskWindow?.show(); @@ -69,7 +69,7 @@ function createQuickAskWindow(x: number, y: number): BrowserWindow { }); quickAskWindow.webContents.setWindowOpenHandler(({ url }) => { - if (url.startsWith('http://localhost')) { + if (url.startsWith(getServerOrigin())) { return { action: 'allow' }; } shell.openExternal(url); diff --git a/surfsense_desktop/src/modules/server.ts b/surfsense_desktop/src/modules/server.ts index daec9bed0..55753d8fb 100644 --- a/surfsense_desktop/src/modules/server.ts +++ b/surfsense_desktop/src/modules/server.ts @@ -3,6 +3,7 @@ import { app, utilityProcess } from 'electron'; import { getPort } from 'get-port-please'; const isDev = !app.isPackaged; +const SERVER_HOST = '127.0.0.1'; let serverPort = 3000; let nextServerProcess: ReturnType | null = null; @@ -10,6 +11,10 @@ export function getServerPort(): number { return serverPort; } +export function getServerOrigin(): string { + return `http://${SERVER_HOST}:${serverPort}`; +} + function getStandalonePath(): string { if (isDev) { return path.join(__dirname, '..', '..', 'surfsense_web', '.next', 'standalone', 'surfsense_web'); @@ -44,7 +49,7 @@ export async function startNextServer(): Promise { env: { ...process.env, PORT: String(serverPort), - HOSTNAME: '127.0.0.1', + HOSTNAME: SERVER_HOST, NODE_ENV: 'production', }, serviceName: 'SurfSense Next Server', @@ -75,7 +80,7 @@ export async function startNextServer(): Promise { child.once('exit', startupExitHandler); }); - const ready = await Promise.race([waitForServer(`http://localhost:${serverPort}`), exited]); + const ready = await Promise.race([waitForServer(getServerOrigin()), exited]); if (startupExitHandler) { child.removeListener('exit', startupExitHandler); } diff --git a/surfsense_desktop/src/modules/window.ts b/surfsense_desktop/src/modules/window.ts index 7ee7eb8db..153fa0879 100644 --- a/surfsense_desktop/src/modules/window.ts +++ b/surfsense_desktop/src/modules/window.ts @@ -2,7 +2,7 @@ import { app, BrowserWindow, shell, session } from 'electron'; import path from 'path'; import { trackEvent } from './analytics'; import { showErrorDialog } from './errors'; -import { getServerPort } from './server'; +import { getServerOrigin } from './server'; import { setActiveSearchSpaceId } from './active-search-space'; const isDev = !app.isPackaged; @@ -58,10 +58,10 @@ export function createMainWindow(initialPath = '/dashboard'): BrowserWindow { mainWindow?.setTitle(WINDOW_TITLE); }); - mainWindow.loadURL(`http://localhost:${getServerPort()}${initialPath}`); + mainWindow.loadURL(`${getServerOrigin()}${initialPath}`); mainWindow.webContents.setWindowOpenHandler(({ url }) => { - if (url.startsWith('http://localhost')) { + if (url.startsWith(getServerOrigin())) { return { action: 'allow' }; } shell.openExternal(url); @@ -70,7 +70,7 @@ export function createMainWindow(initialPath = '/dashboard'): BrowserWindow { const filter = { urls: [`${HOSTED_FRONTEND_URL}/*`] }; session.defaultSession.webRequest.onBeforeRequest(filter, (details, callback) => { - const rewritten = details.url.replace(HOSTED_FRONTEND_URL, `http://localhost:${getServerPort()}`); + const rewritten = details.url.replace(HOSTED_FRONTEND_URL, getServerOrigin()); callback({ redirectURL: rewritten }); }); From 20b8b999388ca8e162d16a1823b018ef8eb4019d Mon Sep 17 00:00:00 2001 From: suryo12 Date: Mon, 25 May 2026 22:06:44 +0700 Subject: [PATCH 046/198] refactor(web): shrink LayoutShell.documentsPanel interface (fixes #1360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `documentsPanel` prop on `LayoutShell` declared `isDocked` and `onDockedChange` alongside `open` / `onOpenChange`, but the shell never forwarded those two extra fields to its consumers. `RightPanel` already defines its own interface accepting only `open` / `onOpenChange`, and `DocumentsSidebar` keeps both as optional props with safe fallbacks for the rare cases that pass them directly. Trim the interface to the two fields the shell actually plumbs through and drop the matching `isDocumentsDocked` state and prop entries from `LayoutDataProvider`, which was the only caller still populating the dead fields (`FreeLayoutDataProvider` already passed the minimal pair). `DocumentsSidebar` itself is untouched — its props remain optional so direct consumers that want docking behaviour can still wire it. --- .../components/layout/providers/LayoutDataProvider.tsx | 3 --- surfsense_web/components/layout/ui/shell/LayoutShell.tsx | 2 -- 2 files changed, 5 deletions(-) diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index 917d1c6e1..73b81d439 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -127,7 +127,6 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid // Documents sidebar state (shared atom so Composer can toggle it) const [isDocumentsSidebarOpen, setIsDocumentsSidebarOpen] = useAtom(documentsSidebarOpenAtom); - const [isDocumentsDocked, setIsDocumentsDocked] = useState(true); const setIsRightPanelCollapsed = useSetAtom(rightPanelCollapsedAtom); // Open documents sidebar by default on desktop (docked mode) @@ -748,8 +747,6 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid documentsPanel={{ open: isDocumentsSidebarOpen, onOpenChange: setIsDocumentsSidebarOpen, - isDocked: isDocumentsDocked, - onDockedChange: setIsDocumentsDocked, }} onTabSwitch={handleTabSwitch} > diff --git a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx index 2f799929b..91d85cc1e 100644 --- a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx +++ b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx @@ -157,8 +157,6 @@ interface LayoutShellProps { documentsPanel?: { open: boolean; onOpenChange: (open: boolean) => void; - isDocked?: boolean; - onDockedChange?: (docked: boolean) => void; }; onTabSwitch?: (tab: Tab) => void; } From 26fe4d74934f7026b0eaa5fba291dc039a914ebd Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 25 May 2026 21:44:31 +0530 Subject: [PATCH 047/198] feat(auto-updater, menu): enhance update management and add menu options for updates and policies --- surfsense_desktop/.env.example | 2 +- surfsense_desktop/src/modules/auto-updater.ts | 119 ++++++++++++++++-- surfsense_desktop/src/modules/menu.ts | 36 +++++- surfsense_desktop/src/modules/window.ts | 2 +- 4 files changed, 146 insertions(+), 13 deletions(-) diff --git a/surfsense_desktop/.env.example b/surfsense_desktop/.env.example index 2d9de7561..f4e797250 100644 --- a/surfsense_desktop/.env.example +++ b/surfsense_desktop/.env.example @@ -3,7 +3,7 @@ # The hosted web frontend URL. Used to intercept OAuth redirects and keep them # inside the desktop app. Set to your production frontend domain. -HOSTED_FRONTEND_URL=https://surfsense.net +HOSTED_FRONTEND_URL=https://surfsense.com # Runtime override for the above (read at app start, no rebuild required). # Useful for self-hosters whose backend NEXT_FRONTEND_URL differs from the diff --git a/surfsense_desktop/src/modules/auto-updater.ts b/surfsense_desktop/src/modules/auto-updater.ts index e323abe53..c0fea6634 100644 --- a/surfsense_desktop/src/modules/auto-updater.ts +++ b/surfsense_desktop/src/modules/auto-updater.ts @@ -3,20 +3,35 @@ import { trackEvent } from './analytics'; const SEMVER_RE = /^\d+\.\d+\.\d+/; -export function setupAutoUpdater(): void { - if (!app.isPackaged) return; +type AutoUpdater = { + autoDownload: boolean; + on(event: string, listener: (...args: any[]) => void): void; + once(event: string, listener: (...args: any[]) => void): void; + removeListener(event: string, listener: (...args: any[]) => void): void; + checkForUpdates(): Promise; + quitAndInstall(): void; +}; - const version = app.getVersion(); - if (!SEMVER_RE.test(version)) { - console.log(`Auto-updater: skipping — "${version}" is not valid semver`); - return; - } +type UpdateInfo = { + version: string; +}; +let listenersRegistered = false; + +function getAutoUpdater(): AutoUpdater { const { autoUpdater } = require('electron-updater'); + return autoUpdater as AutoUpdater; +} +function configureAutoUpdater(autoUpdater: AutoUpdater): void { autoUpdater.autoDownload = true; - autoUpdater.on('update-available', (info: { version: string }) => { + if (listenersRegistered) return; + listenersRegistered = true; + + const version = app.getVersion(); + + autoUpdater.on('update-available', (info: UpdateInfo) => { console.log(`Update available: ${info.version}`); trackEvent('desktop_update_available', { current_version: version, @@ -24,7 +39,7 @@ export function setupAutoUpdater(): void { }); }); - autoUpdater.on('update-downloaded', (info: { version: string }) => { + autoUpdater.on('update-downloaded', (info: UpdateInfo) => { console.log(`Update downloaded: ${info.version}`); trackEvent('desktop_update_downloaded', { current_version: version, @@ -52,6 +67,92 @@ export function setupAutoUpdater(): void { message: err.message?.split('\n')[0], }); }); +} + +export function setupAutoUpdater(): void { + if (!app.isPackaged) return; + + const version = app.getVersion(); + if (!SEMVER_RE.test(version)) { + console.log(`Auto-updater: skipping - "${version}" is not valid semver`); + return; + } + + const autoUpdater = getAutoUpdater(); + configureAutoUpdater(autoUpdater); autoUpdater.checkForUpdates().catch(() => {}); } + +export async function checkForUpdatesManually(): Promise { + if (!app.isPackaged) { + await dialog.showMessageBox({ + type: 'info', + title: 'Updates Unavailable', + message: 'Updates are only available in packaged builds.', + }); + return; + } + + const version = app.getVersion(); + if (!SEMVER_RE.test(version)) { + await dialog.showMessageBox({ + type: 'info', + title: 'Updates Unavailable', + message: `Version "${version}" is not a valid release version, so updates cannot be checked.`, + }); + return; + } + + const autoUpdater = getAutoUpdater(); + configureAutoUpdater(autoUpdater); + + try { + const result = await new Promise<'available' | 'not-available'>((resolve, reject) => { + const cleanup = () => { + autoUpdater.removeListener('update-available', onAvailable); + autoUpdater.removeListener('update-not-available', onNotAvailable); + autoUpdater.removeListener('error', onError); + }; + const onAvailable = (info: UpdateInfo) => { + cleanup(); + void dialog.showMessageBox({ + type: 'info', + title: 'Update Available', + message: `Version ${info.version} is available and will download in the background.`, + }); + resolve('available'); + }; + const onNotAvailable = () => { + cleanup(); + resolve('not-available'); + }; + const onError = (err: Error) => { + cleanup(); + reject(err); + }; + + autoUpdater.once('update-available', onAvailable); + autoUpdater.once('update-not-available', onNotAvailable); + autoUpdater.once('error', onError); + autoUpdater.checkForUpdates().catch((err: Error) => { + cleanup(); + reject(err); + }); + }); + + if (result === 'not-available') { + await dialog.showMessageBox({ + type: 'info', + title: 'No Updates Available', + message: "You're up to date.", + }); + } + } catch (err) { + await dialog.showMessageBox({ + type: 'error', + title: 'Update Check Failed', + message: err instanceof Error ? err.message : String(err), + }); + } +} diff --git a/surfsense_desktop/src/modules/menu.ts b/surfsense_desktop/src/modules/menu.ts index 128a73a21..067a6e461 100644 --- a/surfsense_desktop/src/modules/menu.ts +++ b/surfsense_desktop/src/modules/menu.ts @@ -1,13 +1,45 @@ -import { Menu } from 'electron'; +import { app, Menu } from 'electron'; +import { checkForUpdatesManually } from './auto-updater'; + +const checkForUpdatesItem: Electron.MenuItemConstructorOptions = { + label: 'Check for Updates...', + click: () => { + void checkForUpdatesManually(); + }, +}; export function setupMenu(): void { const isMac = process.platform === 'darwin'; const template: Electron.MenuItemConstructorOptions[] = [ - ...(isMac ? [{ role: 'appMenu' as const }] : []), + ...(isMac + ? [{ + label: app.name, + submenu: [ + { role: 'about' as const }, + checkForUpdatesItem, + { type: 'separator' as const }, + { role: 'services' as const }, + { type: 'separator' as const }, + { role: 'hide' as const }, + { role: 'hideOthers' as const }, + { role: 'unhide' as const }, + { type: 'separator' as const }, + { role: 'quit' as const }, + ], + }] + : []), { role: 'fileMenu' as const }, { role: 'editMenu' as const }, { role: 'viewMenu' as const }, { role: 'windowMenu' as const }, + ...(!isMac + ? [{ + role: 'help' as const, + submenu: [ + checkForUpdatesItem, + ], + }] + : []), ]; Menu.setApplicationMenu(Menu.buildFromTemplate(template)); } diff --git a/surfsense_desktop/src/modules/window.ts b/surfsense_desktop/src/modules/window.ts index e80bf7879..bcae1799c 100644 --- a/surfsense_desktop/src/modules/window.ts +++ b/surfsense_desktop/src/modules/window.ts @@ -13,7 +13,7 @@ function getHostedFrontendUrl(): string { return ( process.env.SURFSENSE_HOSTED_FRONTEND_URL_OVERRIDE || process.env.HOSTED_FRONTEND_URL || - 'https://surfsense.net' + 'https://surfsense.com' ); } From 79378db7c8195fcddd3860776b3532c455577a69 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 25 May 2026 22:21:34 +0530 Subject: [PATCH 048/198] refactor(connector): streamline connector telemetry imports and clean up unused constants --- .../views/connector-edit-view.tsx | 5 +++-- .../constants/connector-constants.ts | 21 ------------------- .../views/connector-accounts-list-view.tsx | 5 +++-- surfsense_web/lib/connector-telemetry.ts | 2 +- 4 files changed, 7 insertions(+), 26 deletions(-) diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx index 90b28dd1a..3e9e9bb27 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx @@ -12,16 +12,17 @@ import { EnumConnectorName } from "@/contracts/enums/connector"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import { authenticatedFetch } from "@/lib/auth-utils"; +import { getReauthEndpoint } from "@/lib/connector-telemetry"; +import { BACKEND_URL } from "@/lib/env-config"; import { cn } from "@/lib/utils"; import { DateRangeSelector } from "../../components/date-range-selector"; import { PeriodicSyncConfig } from "../../components/periodic-sync-config"; import { SummaryConfig } from "../../components/summary-config"; import { VisionLLMConfig } from "../../components/vision-llm-config"; -import { getReauthEndpoint, LIVE_CONNECTOR_TYPES } from "../../constants/connector-constants"; +import { LIVE_CONNECTOR_TYPES } from "../../constants/connector-constants"; import { getConnectorDisplayName } from "../../tabs/all-connectors-tab"; import { MCPServiceConfig } from "../components/mcp-service-config"; import { getConnectorConfigComponent } from "../index"; -import { BACKEND_URL } from "@/lib/env-config"; const VISION_LLM_CONNECTOR_TYPES = new Set([ EnumConnectorName.GOOGLE_DRIVE_CONNECTOR, EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR, diff --git a/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts b/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts index 01a911d70..e62b9546a 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts @@ -1,5 +1,4 @@ import { EnumConnectorName } from "@/contracts/enums/connector"; -import type { SearchSourceConnector } from "@/contracts/types/connector.types"; /** * Connectors that operate in real time (no background indexing). @@ -294,25 +293,5 @@ export const AUTO_INDEX_DEFAULTS: Record = { export const AUTO_INDEX_CONNECTOR_TYPES = new Set(Object.keys(AUTO_INDEX_DEFAULTS)); -// ============================================================================ -// CONNECTOR TELEMETRY REGISTRY -// ---------------------------------------------------------------------------- -// Single source of truth for "what does this connector_type look like in -// analytics?". Any connector added to the lists above is automatically -// picked up here, so adding a new integration does NOT require touching -// `lib/posthog/events.ts` or per-connector tracking code. -// ============================================================================ - -// Telemetry types & helpers are now defined in `@/lib/connector-telemetry`. -// Re-exported here for backward compatibility with existing imports. -export type { - ConnectorTelemetryGroup, - ConnectorTelemetryMeta, -} from "@/lib/connector-telemetry"; -export { - getConnectorTelemetryMeta, - getReauthEndpoint, -} from "@/lib/connector-telemetry"; - // Re-export IndexingConfigState from schemas for backward compatibility export type { IndexingConfigState } from "./connector-popup.schemas"; diff --git a/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx index 41dae221e..27e102d7e 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx @@ -11,12 +11,13 @@ import { EnumConnectorName } from "@/contracts/enums/connector"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import { authenticatedFetch } from "@/lib/auth-utils"; +import { getReauthEndpoint } from "@/lib/connector-telemetry"; +import { BACKEND_URL } from "@/lib/env-config"; import { formatRelativeDate } from "@/lib/format-date"; import { cn } from "@/lib/utils"; -import { getReauthEndpoint, LIVE_CONNECTOR_TYPES } from "../constants/connector-constants"; +import { LIVE_CONNECTOR_TYPES } from "../constants/connector-constants"; import { useConnectorStatus } from "../hooks/use-connector-status"; import { getConnectorDisplayName } from "../tabs/all-connectors-tab"; -import { BACKEND_URL } from "@/lib/env-config"; interface ConnectorAccountsListViewProps { connectorType: string; connectorTitle: string; diff --git a/surfsense_web/lib/connector-telemetry.ts b/surfsense_web/lib/connector-telemetry.ts index ef1b3de32..396097445 100644 --- a/surfsense_web/lib/connector-telemetry.ts +++ b/surfsense_web/lib/connector-telemetry.ts @@ -1,9 +1,9 @@ import { EnumConnectorName } from "@/contracts/enums/connector"; import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import { - OAUTH_CONNECTORS, COMPOSIO_CONNECTORS, CRAWLERS, + OAUTH_CONNECTORS, OTHER_CONNECTORS, } from "@/components/assistant-ui/connector-popup/constants/connector-constants"; From 74fff647792e04749415e9b2f2fa281a0b7df384 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 25 May 2026 22:26:14 +0530 Subject: [PATCH 049/198] feat(menu): add Privacy Policy and Terms of Service options to the application menu --- surfsense_desktop/src/modules/menu.ts | 37 ++++++++++++++----- .../components/AgentPermissionsContent.tsx | 4 +- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/surfsense_desktop/src/modules/menu.ts b/surfsense_desktop/src/modules/menu.ts index 067a6e461..40e19398c 100644 --- a/surfsense_desktop/src/modules/menu.ts +++ b/surfsense_desktop/src/modules/menu.ts @@ -1,4 +1,4 @@ -import { app, Menu } from 'electron'; +import { app, Menu, shell } from 'electron'; import { checkForUpdatesManually } from './auto-updater'; const checkForUpdatesItem: Electron.MenuItemConstructorOptions = { @@ -8,6 +8,20 @@ const checkForUpdatesItem: Electron.MenuItemConstructorOptions = { }, }; +const privacyPolicyItem: Electron.MenuItemConstructorOptions = { + label: 'Privacy Policy', + click: () => { + void shell.openExternal('https://www.surfsense.com/privacy'); + }, +}; + +const termsOfServiceItem: Electron.MenuItemConstructorOptions = { + label: 'Terms of Service', + click: () => { + void shell.openExternal('https://www.surfsense.com/terms'); + }, +}; + export function setupMenu(): void { const isMac = process.platform === 'darwin'; const template: Electron.MenuItemConstructorOptions[] = [ @@ -32,14 +46,19 @@ export function setupMenu(): void { { role: 'editMenu' as const }, { role: 'viewMenu' as const }, { role: 'windowMenu' as const }, - ...(!isMac - ? [{ - role: 'help' as const, - submenu: [ - checkForUpdatesItem, - ], - }] - : []), + { + role: 'help' as const, + submenu: [ + ...(!isMac + ? [ + checkForUpdatesItem, + { type: 'separator' as const }, + ] + : []), + privacyPolicyItem, + termsOfServiceItem, + ], + }, ]; Menu.setApplicationMenu(Menu.buildFromTemplate(template)); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/AgentPermissionsContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/AgentPermissionsContent.tsx index 5af94f7e3..5919abcd6 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/AgentPermissionsContent.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/AgentPermissionsContent.tsx @@ -474,8 +474,10 @@ export function AgentPermissionsContent() { handleConfirmDelete(); }} disabled={deleteMutation.isPending} + className="relative min-w-[88px]" > - {deleteMutation.isPending ? "Deleting…" : "Delete"} + Delete + {deleteMutation.isPending && } From c0fefa4db1a67387bc3411852a8b590f4471e41e Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 25 May 2026 23:24:26 +0530 Subject: [PATCH 050/198] feat(auto-updater, ui): implement update notification and installation prompt in desktop application --- surfsense_desktop/src/ipc/channels.ts | 2 + surfsense_desktop/src/ipc/handlers.ts | 5 ++ surfsense_desktop/src/modules/auto-updater.ts | 68 +++++++++++---- surfsense_desktop/src/preload.ts | 8 ++ surfsense_web/app/layout.tsx | 2 + .../desktop/DesktopUpdatePrompt.tsx | 82 +++++++++++++++++++ surfsense_web/types/window.d.ts | 6 ++ 7 files changed, 155 insertions(+), 18 deletions(-) create mode 100644 surfsense_web/components/desktop/DesktopUpdatePrompt.tsx diff --git a/surfsense_desktop/src/ipc/channels.ts b/surfsense_desktop/src/ipc/channels.ts index 8d2af5107..17daab9a6 100644 --- a/surfsense_desktop/src/ipc/channels.ts +++ b/surfsense_desktop/src/ipc/channels.ts @@ -2,6 +2,8 @@ export const IPC_CHANNELS = { OPEN_EXTERNAL: 'open-external', GET_APP_VERSION: 'get-app-version', DEEP_LINK: 'deep-link', + UPDATE_DOWNLOADED: 'update:downloaded', + UPDATE_INSTALL_NOW: 'update:install-now', QUICK_ASK_TEXT: 'quick-ask-text', SET_QUICK_ASK_MODE: 'set-quick-ask-mode', GET_QUICK_ASK_MODE: 'get-quick-ask-mode', diff --git a/surfsense_desktop/src/ipc/handlers.ts b/surfsense_desktop/src/ipc/handlers.ts index d918fd90d..ed7eaac66 100644 --- a/surfsense_desktop/src/ipc/handlers.ts +++ b/surfsense_desktop/src/ipc/handlers.ts @@ -51,6 +51,7 @@ import { stopAgentFilesystemTreeWatch, type AgentFilesystemTreeWatchOptions, } from '../modules/agent-filesystem-tree-watcher'; +import { installDownloadedUpdate } from '../modules/auto-updater'; let authTokens: { bearer: string; refresh: string } | null = null; @@ -70,6 +71,10 @@ export function registerIpcHandlers(): void { return app.getVersion(); }); + ipcMain.handle(IPC_CHANNELS.UPDATE_INSTALL_NOW, () => { + installDownloadedUpdate(); + }); + ipcMain.handle(IPC_CHANNELS.GET_PERMISSIONS_STATUS, () => { return getPermissionsStatus(); }); diff --git a/surfsense_desktop/src/modules/auto-updater.ts b/surfsense_desktop/src/modules/auto-updater.ts index c0fea6634..4745fa24b 100644 --- a/surfsense_desktop/src/modules/auto-updater.ts +++ b/surfsense_desktop/src/modules/auto-updater.ts @@ -1,4 +1,5 @@ -import { app, dialog } from 'electron'; +import { app, BrowserWindow, dialog } from 'electron'; +import { IPC_CHANNELS } from '../ipc/channels'; import { trackEvent } from './analytics'; const SEMVER_RE = /^\d+\.\d+\.\d+/; @@ -17,6 +18,7 @@ type UpdateInfo = { }; let listenersRegistered = false; +let manualUpdateCheckInProgress = false; function getAutoUpdater(): AutoUpdater { const { autoUpdater } = require('electron-updater'); @@ -45,20 +47,9 @@ function configureAutoUpdater(autoUpdater: AutoUpdater): void { current_version: version, new_version: info.version, }); - dialog.showMessageBox({ - type: 'info', - buttons: ['Restart', 'Later'], - defaultId: 0, - title: 'Update Ready', - message: `Version ${info.version} has been downloaded. Restart to apply the update.`, - }).then(({ response }: { response: number }) => { - if (response === 0) { - trackEvent('desktop_update_install_accepted', { new_version: info.version }); - autoUpdater.quitAndInstall(); - } else { - trackEvent('desktop_update_install_deferred', { new_version: info.version }); - } - }); + if (!manualUpdateCheckInProgress) { + notifyRenderersUpdateDownloaded(info); + } }); autoUpdater.on('error', (err: Error) => { @@ -69,6 +60,39 @@ function configureAutoUpdater(autoUpdater: AutoUpdater): void { }); } +function notifyRenderersUpdateDownloaded(info: UpdateInfo): void { + for (const win of BrowserWindow.getAllWindows()) { + if (!win.isDestroyed()) { + win.webContents.send(IPC_CHANNELS.UPDATE_DOWNLOADED, { + version: info.version, + }); + } + } +} + +async function showNativeInstallDialog(autoUpdater: AutoUpdater, info: UpdateInfo): Promise { + const { response } = await dialog.showMessageBox({ + type: 'info', + buttons: ['Restart', 'Later'], + defaultId: 0, + title: 'Update Ready', + message: `Version ${info.version} has been downloaded. Restart to apply the update.`, + }); + + if (response === 0) { + trackEvent('desktop_update_install_accepted', { new_version: info.version }); + autoUpdater.quitAndInstall(); + } else { + trackEvent('desktop_update_install_deferred', { new_version: info.version }); + } +} + +export function installDownloadedUpdate(): void { + const autoUpdater = getAutoUpdater(); + trackEvent('desktop_update_install_accepted', { source: 'renderer_prompt' }); + autoUpdater.quitAndInstall(); +} + export function setupAutoUpdater(): void { if (!app.isPackaged) return; @@ -108,25 +132,31 @@ export async function checkForUpdatesManually(): Promise { configureAutoUpdater(autoUpdater); try { - const result = await new Promise<'available' | 'not-available'>((resolve, reject) => { + manualUpdateCheckInProgress = true; + const result = await new Promise<'not-available' | 'downloaded'>((resolve, reject) => { const cleanup = () => { + manualUpdateCheckInProgress = false; autoUpdater.removeListener('update-available', onAvailable); autoUpdater.removeListener('update-not-available', onNotAvailable); + autoUpdater.removeListener('update-downloaded', onDownloaded); autoUpdater.removeListener('error', onError); }; const onAvailable = (info: UpdateInfo) => { - cleanup(); void dialog.showMessageBox({ type: 'info', title: 'Update Available', message: `Version ${info.version} is available and will download in the background.`, }); - resolve('available'); }; const onNotAvailable = () => { cleanup(); resolve('not-available'); }; + const onDownloaded = (info: UpdateInfo) => { + cleanup(); + void showNativeInstallDialog(autoUpdater, info); + resolve('downloaded'); + }; const onError = (err: Error) => { cleanup(); reject(err); @@ -134,6 +164,7 @@ export async function checkForUpdatesManually(): Promise { autoUpdater.once('update-available', onAvailable); autoUpdater.once('update-not-available', onNotAvailable); + autoUpdater.once('update-downloaded', onDownloaded); autoUpdater.once('error', onError); autoUpdater.checkForUpdates().catch((err: Error) => { cleanup(); @@ -149,6 +180,7 @@ export async function checkForUpdatesManually(): Promise { }); } } catch (err) { + manualUpdateCheckInProgress = false; await dialog.showMessageBox({ type: 'error', title: 'Update Check Failed', diff --git a/surfsense_desktop/src/preload.ts b/surfsense_desktop/src/preload.ts index 7d72e9da5..97232179c 100644 --- a/surfsense_desktop/src/preload.ts +++ b/surfsense_desktop/src/preload.ts @@ -10,6 +10,14 @@ contextBridge.exposeInMainWorld('electronAPI', { }, openExternal: (url: string) => ipcRenderer.send(IPC_CHANNELS.OPEN_EXTERNAL, url), getAppVersion: () => ipcRenderer.invoke(IPC_CHANNELS.GET_APP_VERSION), + onUpdateDownloaded: (callback: (data: { version: string }) => void) => { + const listener = (_event: unknown, data: { version: string }) => callback(data); + ipcRenderer.on(IPC_CHANNELS.UPDATE_DOWNLOADED, listener); + return () => { + ipcRenderer.removeListener(IPC_CHANNELS.UPDATE_DOWNLOADED, listener); + }; + }, + installUpdateNow: () => ipcRenderer.invoke(IPC_CHANNELS.UPDATE_INSTALL_NOW), onDeepLink: (callback: (url: string) => void) => { const listener = (_event: unknown, url: string) => callback(url); ipcRenderer.on(IPC_CHANNELS.DEEP_LINK, listener); diff --git a/surfsense_web/app/layout.tsx b/surfsense_web/app/layout.tsx index 4e88709e9..4bb15c607 100644 --- a/surfsense_web/app/layout.tsx +++ b/surfsense_web/app/layout.tsx @@ -3,6 +3,7 @@ import "./globals.css"; import { RootProvider } from "fumadocs-ui/provider/next"; import { Roboto } from "next/font/google"; import { AnnouncementToastProvider } from "@/components/announcements/AnnouncementToastProvider"; +import { DesktopUpdatePrompt } from "@/components/desktop/DesktopUpdatePrompt"; import { GlobalLoadingProvider } from "@/components/providers/GlobalLoadingProvider"; import { I18nProvider } from "@/components/providers/I18nProvider"; import { PostHogProvider } from "@/components/providers/PostHogProvider"; @@ -154,6 +155,7 @@ export default function RootLayout({ {children} + diff --git a/surfsense_web/components/desktop/DesktopUpdatePrompt.tsx b/surfsense_web/components/desktop/DesktopUpdatePrompt.tsx new file mode 100644 index 000000000..091121141 --- /dev/null +++ b/surfsense_web/components/desktop/DesktopUpdatePrompt.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { Download, X } from "lucide-react"; +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +type UpdatePromptState = { + version: string; +}; + +export function DesktopUpdatePrompt() { + const [update, setUpdate] = useState(null); + + useEffect(() => { + const api = window.electronAPI; + if (!api?.onUpdateDownloaded) return; + + return api.onUpdateDownloaded(({ version }) => { + setUpdate({ version }); + }); + }, []); + + if (!update) return null; + + const installAndRestart = () => { + void window.electronAPI?.installUpdateNow(); + }; + + return ( +
+
+
+ +
+ +
+
Update available
+

+ A new version of SurfSense ({update.version}) is now available to install. +

+ +
+ + +
+
+ + +
+
+ ); +} diff --git a/surfsense_web/types/window.d.ts b/surfsense_web/types/window.d.ts index f25d43f5e..2d12169b1 100644 --- a/surfsense_web/types/window.d.ts +++ b/surfsense_web/types/window.d.ts @@ -83,6 +83,10 @@ interface LocalTextFileResult { error?: string; } +interface UpdateDownloadedEvent { + version: string; +} + interface ElectronAPI { versions: { electron: string; @@ -92,6 +96,8 @@ interface ElectronAPI { }; openExternal: (url: string) => void; getAppVersion: () => Promise; + onUpdateDownloaded: (callback: (data: UpdateDownloadedEvent) => void) => () => void; + installUpdateNow: () => Promise; onDeepLink: (callback: (url: string) => void) => () => void; onChatScreenCapture: (callback: (dataUrl: string) => void) => () => void; getQuickAskText: () => Promise; From 4910263c93045cec1b7bbc7f4f5c368bf1313bf6 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:48:04 +0200 Subject: [PATCH 051/198] refactor(chat): add streaming/shared/ package for StreamResult and utils Foundation layer for the parallel refactor of stream_new_chat.py. Extracts the StreamResult dataclass (tracks per-turn streaming state) and a small set of shared utilities (resume_step_prefix, safe_float). Add-only; no existing code imports from this package yet. Existing stream_new_chat.py keeps its inline equivalents until cutover. --- .../tasks/chat/streaming/shared/__init__.py | 15 ++++++++ .../chat/streaming/shared/stream_result.py | 37 +++++++++++++++++++ .../app/tasks/chat/streaming/shared/utils.py | 27 ++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/shared/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/shared/stream_result.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/shared/utils.py diff --git a/surfsense_backend/app/tasks/chat/streaming/shared/__init__.py b/surfsense_backend/app/tasks/chat/streaming/shared/__init__.py new file mode 100644 index 000000000..6c9f1f6b5 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/shared/__init__.py @@ -0,0 +1,15 @@ +"""Shared building blocks used across every streaming flow.""" + +from __future__ import annotations + +from app.tasks.chat.streaming.shared.stream_result import StreamResult +from app.tasks.chat.streaming.shared.utils import ( + resume_step_prefix, + safe_float, +) + +__all__ = [ + "StreamResult", + "resume_step_prefix", + "safe_float", +] diff --git a/surfsense_backend/app/tasks/chat/streaming/shared/stream_result.py b/surfsense_backend/app/tasks/chat/streaming/shared/stream_result.py new file mode 100644 index 000000000..a940e8a9f --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/shared/stream_result.py @@ -0,0 +1,37 @@ +"""Per-turn streaming state shared between the orchestrator and event loop.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any + + +@dataclass +class StreamResult: + accumulated_text: str = "" + is_interrupted: bool = False + sandbox_files: list[str] = field(default_factory=list) + request_id: str | None = None + turn_id: str = "" + filesystem_mode: str = "cloud" + client_platform: str = "web" + intent_detected: str = "chat_only" + intent_confidence: float = 0.0 + write_attempted: bool = False + write_succeeded: bool = False + verification_succeeded: bool = False + commit_gate_passed: bool = True + commit_gate_reason: str = "" + # Pre-allocated assistant ``new_chat_messages.id`` for this turn, captured by + # ``persist_assistant_shell`` right after the user row is persisted. ``None`` + # for the legacy/anonymous code paths that don't opt in to server-side + # ``ContentPart[]`` projection. + assistant_message_id: int | None = None + # In-memory mirror of the FE's assistant-ui ``ContentPartsState``, populated + # by the lifecycle methods called from the streaming event loop at each + # ``streaming_service.format_*`` yield site. Snapshot in the streaming + # ``finally`` to produce the rich JSONB persisted by + # ``finalize_assistant_turn``. ``repr=False`` keeps the log-on-error path + # (``StreamResult`` is logged in some error branches) from dumping a + # potentially-large parts list. + content_builder: Any | None = field(default=None, repr=False) diff --git a/surfsense_backend/app/tasks/chat/streaming/shared/utils.py b/surfsense_backend/app/tasks/chat/streaming/shared/utils.py new file mode 100644 index 000000000..fe6901543 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/shared/utils.py @@ -0,0 +1,27 @@ +"""Small utilities used by streaming orchestrators and phases.""" + +from __future__ import annotations + +from typing import Any + + +def resume_step_prefix(turn_id: str) -> str: + """Per-turn ``step_prefix`` for resume invocations. + + Each ``stream_agent_events`` call constructs a fresh + ``AgentEventRelayState`` with ``thinking_step_counter=0``, so two consecutive + resume turns would otherwise both emit ``thinking-resume-1``, ``-2`` etc. + The frontend rehydrates ``currentThinkingSteps`` from the immediate prior + assistant message at the start of every resume — if the new stream's IDs + collide with the seeded ones, React renders sibling Timeline rows with the + same key. Salting with ``turn_id`` guarantees disjoint IDs across resumes + within one thread. + """ + return f"thinking-resume-{turn_id}" + + +def safe_float(value: Any, default: float = 0.0) -> float: + try: + return float(value) + except (TypeError, ValueError): + return default From c13beae1ceea2e344757baea13247d111cf3bd3b Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:48:08 +0200 Subject: [PATCH 052/198] refactor(chat): add streaming/context/ for mentioned-docs and deep-agents todos Extracts two pure context helpers used during input-state assembly: * mentioned_docs.format_mentioned_surfsense_docs_as_context: renders the user's @-mentioned SurfSense docs into the LLM context block. * deepagents_todos.extract_todos_from_deepagents: pulls the in-progress todo list from a deep-agents state snapshot for the title generator. Add-only; existing call sites in stream_new_chat.py remain untouched until cutover. --- .../tasks/chat/streaming/context/__init__.py | 15 +++++ .../streaming/context/deepagents_todos.py | 27 +++++++++ .../chat/streaming/context/mentioned_docs.py | 58 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/context/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py diff --git a/surfsense_backend/app/tasks/chat/streaming/context/__init__.py b/surfsense_backend/app/tasks/chat/streaming/context/__init__.py new file mode 100644 index 000000000..f858a6c06 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/context/__init__.py @@ -0,0 +1,15 @@ +"""Pre-agent context shaping: mentioned-doc rendering and todos extraction.""" + +from __future__ import annotations + +from app.tasks.chat.streaming.context.deepagents_todos import ( + extract_todos_from_deepagents, +) +from app.tasks.chat.streaming.context.mentioned_docs import ( + format_mentioned_surfsense_docs_as_context, +) + +__all__ = [ + "extract_todos_from_deepagents", + "format_mentioned_surfsense_docs_as_context", +] diff --git a/surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py b/surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py new file mode 100644 index 000000000..0bbf4f0a5 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py @@ -0,0 +1,27 @@ +"""Extract todos from a deepagents ``TodoListMiddleware`` ``Command`` output.""" + +from __future__ import annotations + +from typing import Any + + +def extract_todos_from_deepagents(command_output: Any) -> dict: + """Normalize todos out of a deepagents ``Command`` or dict payload. + + deepagents returns a ``Command`` whose ``update['todos']`` is a list of + ``{'content': str, 'status': str}`` dicts. The UI expects the same shape, + so no transformation is required — only extraction. + """ + todos_data: list = [] + if hasattr(command_output, "update"): + update = command_output.update + todos_data = update.get("todos", []) + elif isinstance(command_output, dict): + if "todos" in command_output: + todos_data = command_output.get("todos", []) + elif "update" in command_output and isinstance( + command_output["update"], dict + ): + todos_data = command_output["update"].get("todos", []) + + return {"todos": todos_data} diff --git a/surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py b/surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py new file mode 100644 index 000000000..e02e98d34 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py @@ -0,0 +1,58 @@ +"""Render user-mentioned SurfSense docs as XML context for the agent.""" + +from __future__ import annotations + +import json + +from app.db import SurfsenseDocsDocument +from app.utils.surfsense_docs import surfsense_docs_public_url + + +def format_mentioned_surfsense_docs_as_context( + documents: list[SurfsenseDocsDocument], +) -> str: + if not documents: + return "" + + context_parts = [""] + context_parts.append( + "The user has explicitly mentioned the following SurfSense documentation pages. " + "These are official documentation about how to use SurfSense and should be used to answer questions about the application. " + "Use [citation:CHUNK_ID] format for citations (e.g., [citation:doc-123])." + ) + + for doc in documents: + public_url = surfsense_docs_public_url(doc.source) + metadata_json = json.dumps( + {"source": doc.source, "public_url": public_url}, ensure_ascii=False + ) + + context_parts.append("") + context_parts.append("") + context_parts.append(f" doc-{doc.id}") + context_parts.append(" SURFSENSE_DOCS") + context_parts.append(f" <![CDATA[{doc.title}]]>") + context_parts.append(f" ") + context_parts.append( + f" " + ) + context_parts.append("") + context_parts.append("") + context_parts.append("") + + if hasattr(doc, "chunks") and doc.chunks: + for chunk in doc.chunks: + context_parts.append( + f" " + ) + else: + context_parts.append( + f" " + ) + + context_parts.append("") + context_parts.append("") + context_parts.append("") + + context_parts.append("") + return "\n".join(context_parts) From 88a58f6aff8f7bcd4b1b5191784ceaf5ab0de57e Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:48:14 +0200 Subject: [PATCH 053/198] refactor(chat): add streaming/contract/ for file-write contract enforcement Extracts the desktop_local_folder file-operation contract helpers: * contract_enforcement_active: gates the contract on filesystem mode. * evaluate_file_contract_outcome: scores tool outputs as success/no-op. * log_file_contract: structured logging of contract verdicts. This is the unit responsible for catching agents that claim to have written/edited a file without actually invoking the filesystem tool. Add-only; stream_new_chat.py keeps its inline duplicates until cutover. --- .../tasks/chat/streaming/contract/__init__.py | 15 ++++++ .../chat/streaming/contract/file_contract.py | 53 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/contract/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/contract/file_contract.py diff --git a/surfsense_backend/app/tasks/chat/streaming/contract/__init__.py b/surfsense_backend/app/tasks/chat/streaming/contract/__init__.py new file mode 100644 index 000000000..4562b362c --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/contract/__init__.py @@ -0,0 +1,15 @@ +"""File-operation contract evaluation and logging.""" + +from __future__ import annotations + +from app.tasks.chat.streaming.contract.file_contract import ( + contract_enforcement_active, + evaluate_file_contract_outcome, + log_file_contract, +) + +__all__ = [ + "contract_enforcement_active", + "evaluate_file_contract_outcome", + "log_file_contract", +] diff --git a/surfsense_backend/app/tasks/chat/streaming/contract/file_contract.py b/surfsense_backend/app/tasks/chat/streaming/contract/file_contract.py new file mode 100644 index 000000000..f21f5da02 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/contract/file_contract.py @@ -0,0 +1,53 @@ +"""File-operation contract: when to enforce, how to score, how to log.""" + +from __future__ import annotations + +import json +from typing import Any + +from app.tasks.chat.streaming.shared.stream_result import StreamResult +from app.utils.perf import get_perf_logger + +_perf_log = get_perf_logger() + + +def contract_enforcement_active(result: StreamResult) -> bool: + # Enforce only in desktop local-folder mode. Kept deterministic, no + # env-driven progression modes. + return result.filesystem_mode == "desktop_local_folder" + + +def evaluate_file_contract_outcome(result: StreamResult) -> tuple[bool, str]: + if result.intent_detected != "file_write": + return True, "" + if not result.write_attempted: + return False, "no_write_attempt" + if not result.write_succeeded: + return False, "write_failed" + if not result.verification_succeeded: + return False, "verification_failed" + return True, "" + + +def log_file_contract(stage: str, result: StreamResult, **extra: Any) -> None: + payload: dict[str, Any] = { + "stage": stage, + "request_id": result.request_id or "unknown", + "turn_id": result.turn_id or "unknown", + "chat_id": ( + result.turn_id.split(":", 1)[0] if ":" in result.turn_id else "unknown" + ), + "filesystem_mode": result.filesystem_mode, + "client_platform": result.client_platform, + "intent_detected": result.intent_detected, + "intent_confidence": result.intent_confidence, + "write_attempted": result.write_attempted, + "write_succeeded": result.write_succeeded, + "verification_succeeded": result.verification_succeeded, + "commit_gate_passed": result.commit_gate_passed, + "commit_gate_reason": result.commit_gate_reason or None, + } + payload.update(extra) + _perf_log.info( + "[file_operation_contract] %s", json.dumps(payload, ensure_ascii=False) + ) From 94bc827252ad20f0335474981eb1009780f25695 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:48:20 +0200 Subject: [PATCH 054/198] refactor(chat): add streaming/agent/ package with build_main_agent_for_thread Extracts the agent-construction wrapper that the chat streamers call to materialize the LangGraph agent for a given thread. Centralizes how we pass the agent factory plus checkpointer, runtime context, and the in-memory content builder. Add-only; pre-existing inline equivalent in stream_new_chat.py stays until cutover. --- .../tasks/chat/streaming/agent/__init__.py | 8 +++ .../app/tasks/chat/streaming/agent/builder.py | 49 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/agent/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/agent/builder.py diff --git a/surfsense_backend/app/tasks/chat/streaming/agent/__init__.py b/surfsense_backend/app/tasks/chat/streaming/agent/__init__.py new file mode 100644 index 000000000..260dcb3f2 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/agent/__init__.py @@ -0,0 +1,8 @@ +"""Agent construction and per-turn event-loop drivers.""" + +from __future__ import annotations + +from app.tasks.chat.streaming.agent.builder import build_main_agent_for_thread +from app.tasks.chat.streaming.agent.event_loop import stream_agent_events + +__all__ = ["build_main_agent_for_thread", "stream_agent_events"] diff --git a/surfsense_backend/app/tasks/chat/streaming/agent/builder.py b/surfsense_backend/app/tasks/chat/streaming/agent/builder.py new file mode 100644 index 000000000..0db42edbf --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/agent/builder.py @@ -0,0 +1,49 @@ +"""Single per-thread agent (re)build path. + +A graph swap mid-turn would corrupt checkpointer state for the same +``thread_id``, so both the initial build and any mid-stream 429 recovery rebuild +must funnel through this single function. +""" + +from __future__ import annotations + +from typing import Any + +from app.agents.new_chat.filesystem_selection import FilesystemSelection +from app.agents.new_chat.llm_config import AgentConfig +from app.db import ChatVisibility +from app.services.connector_service import ConnectorService + + +async def build_main_agent_for_thread( + agent_factory: Any, + *, + llm: Any, + search_space_id: int, + db_session: Any, + connector_service: ConnectorService, + checkpointer: Any, + user_id: str | None, + thread_id: int | None, + agent_config: AgentConfig | None, + firecrawl_api_key: str | None, + thread_visibility: ChatVisibility | None, + filesystem_selection: FilesystemSelection | None, + disabled_tools: list[str] | None = None, + mentioned_document_ids: list[int] | None = None, +) -> Any: + return await agent_factory( + llm=llm, + search_space_id=search_space_id, + db_session=db_session, + connector_service=connector_service, + checkpointer=checkpointer, + user_id=user_id, + thread_id=thread_id, + agent_config=agent_config, + firecrawl_api_key=firecrawl_api_key, + thread_visibility=thread_visibility, + filesystem_selection=filesystem_selection, + disabled_tools=disabled_tools, + mentioned_document_ids=mentioned_document_ids, + ) From 26c569467dc08515f71d697bca379f6e7e3cfb8d Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:48:26 +0200 Subject: [PATCH 055/198] refactor(chat): add streaming/agent/event_loop.stream_agent_events Extracts the inner agent-streaming driver previously inlined as _stream_agent_events in stream_new_chat.py. stream_agent_events drives graph_stream.event_stream.stream_output and, after the agent finishes, performs the post-stream safety-net work: * commit any pending content the agent never explicitly finished * evaluate file-operation contract outcomes and emit the appropriate contract verdict for desktop_local_folder turns This unit is what flows/shared/stream_loop.py wraps in the rate-limit recovery while-loop. Add-only; no existing wiring uses it yet. --- .../tasks/chat/streaming/agent/event_loop.py | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/agent/event_loop.py diff --git a/surfsense_backend/app/tasks/chat/streaming/agent/event_loop.py b/surfsense_backend/app/tasks/chat/streaming/agent/event_loop.py new file mode 100644 index 000000000..b77bd3890 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/agent/event_loop.py @@ -0,0 +1,175 @@ +"""Per-turn agent event-loop driver. + +Drives ``stream_output`` (graph_stream relay) for one agent turn, then runs the +post-stream agent-state inspection: safety-net commit of any staged filesystem +state (in case ``aafter_agent`` was skipped), file-operation contract scoring, +intent classification, and interrupt detection. +""" + +from __future__ import annotations + +from collections.abc import AsyncGenerator +from typing import Any + +from app.agents.new_chat.filesystem_selection import FilesystemMode +from app.agents.new_chat.middleware.kb_persistence import ( + commit_staged_filesystem_state, +) +from app.services.new_streaming_service import VercelStreamingService +from app.tasks.chat.streaming.contract.file_contract import ( + contract_enforcement_active, + evaluate_file_contract_outcome, + log_file_contract, +) +from app.tasks.chat.streaming.graph_stream.event_stream import stream_output +from app.tasks.chat.streaming.helpers.interrupt_inspector import ( + all_interrupt_values, +) +from app.tasks.chat.streaming.shared.stream_result import StreamResult +from app.tasks.chat.streaming.shared.utils import safe_float +from app.utils.perf import get_perf_logger + +_perf_log = get_perf_logger() + + +async def stream_agent_events( + agent: Any, + config: dict[str, Any], + input_data: Any, + streaming_service: VercelStreamingService, + result: StreamResult, + step_prefix: str = "thinking", + initial_step_id: str | None = None, + initial_step_title: str = "", + initial_step_items: list[str] | None = None, + *, + fallback_commit_search_space_id: int | None = None, + fallback_commit_created_by_id: str | None = None, + fallback_commit_filesystem_mode: FilesystemMode = FilesystemMode.CLOUD, + fallback_commit_thread_id: int | None = None, + runtime_context: Any = None, + content_builder: Any | None = None, +) -> AsyncGenerator[str, None]: + """Stream and format ``astream_events`` from the agent. + + Yields SSE-formatted strings; after exhausting, ``result`` carries + ``accumulated_text`` and interrupt state. See ``StreamResult`` for the + side-channel surface populated by the underlying relay. + """ + async for sse in stream_output( + agent=agent, + config=config, + input_data=input_data, + streaming_service=streaming_service, + result=result, + step_prefix=step_prefix, + initial_step_id=initial_step_id, + initial_step_title=initial_step_title, + initial_step_items=initial_step_items, + content_builder=content_builder, + runtime_context=runtime_context, + ): + yield sse + + accumulated_text = result.accumulated_text + + state = await agent.aget_state(config) + state_values = getattr(state, "values", {}) or {} + + # Safety net: if astream_events was cancelled before + # KnowledgeBasePersistenceMiddleware.aafter_agent ran, any staged work + # (dirty_paths / staged_dirs / pending_moves / pending_deletes / + # pending_dir_deletes) is still in the checkpointed state. Run the SAME + # shared commit helper so the turn's writes don't get lost on client + # disconnect, then push the delta back into the graph using ``as_node=...`` + # so reducers fire as if the after_agent hook produced it. + if ( + fallback_commit_filesystem_mode == FilesystemMode.CLOUD + and fallback_commit_search_space_id is not None + and ( + (state_values.get("dirty_paths") or []) + or (state_values.get("staged_dirs") or []) + or (state_values.get("pending_moves") or []) + or (state_values.get("pending_deletes") or []) + or (state_values.get("pending_dir_deletes") or []) + ) + ): + try: + delta = await commit_staged_filesystem_state( + state_values, + search_space_id=fallback_commit_search_space_id, + created_by_id=fallback_commit_created_by_id, + filesystem_mode=fallback_commit_filesystem_mode, + thread_id=fallback_commit_thread_id, + dispatch_events=False, + ) + if delta: + await agent.aupdate_state( + config, + delta, + as_node="KnowledgeBasePersistenceMiddleware.after_agent", + ) + except Exception as exc: + _perf_log.warning("[stream_agent_events] safety-net commit failed: %s", exc) + + contract_state = state_values.get("file_operation_contract") or {} + contract_turn_id = contract_state.get("turn_id") + current_turn_id = config.get("configurable", {}).get("turn_id", "") + intent_value = contract_state.get("intent") + if ( + isinstance(intent_value, str) + and intent_value in ("chat_only", "file_write", "file_read") + and contract_turn_id == current_turn_id + ): + result.intent_detected = intent_value + if ( + isinstance(intent_value, str) + and intent_value in ("chat_only", "file_write", "file_read") + and contract_turn_id != current_turn_id + ): + # Ignore stale intent contracts from previous turns/checkpoints. + result.intent_detected = "chat_only" + result.intent_confidence = ( + safe_float(contract_state.get("confidence"), default=0.0) + if contract_turn_id == current_turn_id + else 0.0 + ) + + if result.intent_detected == "file_write": + result.commit_gate_passed, result.commit_gate_reason = ( + evaluate_file_contract_outcome(result) + ) + if not result.commit_gate_passed and contract_enforcement_active(result): + gate_notice = ( + "I could not complete the requested file write because no successful " + "write_file/edit_file operation was confirmed." + ) + gate_text_id = streaming_service.generate_text_id() + yield streaming_service.format_text_start(gate_text_id) + if content_builder is not None: + content_builder.on_text_start(gate_text_id) + yield streaming_service.format_text_delta(gate_text_id, gate_notice) + if content_builder is not None: + content_builder.on_text_delta(gate_text_id, gate_notice) + yield streaming_service.format_text_end(gate_text_id) + if content_builder is not None: + content_builder.on_text_end(gate_text_id) + yield streaming_service.format_terminal_info(gate_notice, "error") + accumulated_text = gate_notice + else: + result.commit_gate_passed = True + result.commit_gate_reason = "" + + result.accumulated_text = accumulated_text + log_file_contract("turn_outcome", result) + + pending_values = all_interrupt_values(state) + if pending_values: + result.is_interrupted = True + # One frame per paused subagent so each parallel HITL renders its own + # approval card on the wire. Order matches ``state.interrupts``, which + # the resume slicer in + # ``checkpointed_subagent_middleware.resume_routing`` consumes in the + # same order — keeping emit and resume in lock-step. + for interrupt_value in pending_values: + yield streaming_service.format_interrupt_request(interrupt_value) From e9a98ecafb6d5f1af3dc8fc2f934e2882da11ec3 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:49:09 +0200 Subject: [PATCH 056/198] refactor(chat): add streaming/flows/shared/ base helpers Six small, single-purpose modules shared by the upcoming new_chat and resume_chat orchestrators: * llm_bundle: dispatches negative config_id to the YAML loader and non-negative config_id to the DB loader, returning (llm, AgentConfig). * pre_stream_setup: builds the connector service, resolves the Firecrawl API key, and returns the chat checkpointer. * first_frames: iter_initial_frames + iter_final_frames emit the canonical message-start / step-start / idle / finish / done SSE envelope. * finalize_emit: iter_token_usage_frame emits the per-turn usage frame from a TokenAccumulator summary. * finally_cleanup: close_session_and_clear_ai_responding and run_gc_pass centralize the finally-block bookkeeping. * span: open_chat_request_span / set_agent_mode / close_chat_request_span / record_outcome_attrs wrap the OpenTelemetry chat_request span. Add-only; these are not yet wired into stream_new_chat.py. --- .../chat/streaming/flows/shared/__init__.py | 3 + .../streaming/flows/shared/finalize_emit.py | 54 +++++++++++++ .../streaming/flows/shared/finally_cleanup.py | 69 ++++++++++++++++ .../streaming/flows/shared/first_frames.py | 40 ++++++++++ .../chat/streaming/flows/shared/llm_bundle.py | 57 +++++++++++++ .../flows/shared/pre_stream_setup.py | 40 ++++++++++ .../tasks/chat/streaming/flows/shared/span.py | 80 +++++++++++++++++++ 7 files changed, 343 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/finalize_emit.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/first_frames.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/llm_bundle.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/pre_stream_setup.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/__init__.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/__init__.py new file mode 100644 index 000000000..b65acc43c --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/__init__.py @@ -0,0 +1,3 @@ +"""Building blocks shared by ``new_chat`` and ``resume_chat`` orchestrators.""" + +from __future__ import annotations diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/finalize_emit.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/finalize_emit.py new file mode 100644 index 000000000..e5de3f6a4 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/finalize_emit.py @@ -0,0 +1,54 @@ +"""Emit the per-turn token-usage SSE frame from the accumulator. + +``per_message_summary()`` returns ``None`` when the turn made no chargeable +LLM calls (e.g. interrupt-on-input). In that case we skip the frame; the +frontend has no usage to render. +""" + +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +from app.services.new_streaming_service import VercelStreamingService +from app.utils.perf import get_perf_logger + +if TYPE_CHECKING: + from app.services.token_tracking_service import TokenAccumulator + +_perf_log = get_perf_logger() +logger = logging.getLogger(__name__) + + +def iter_token_usage_frame( + streaming_service: VercelStreamingService, + *, + accumulator: TokenAccumulator, + log_label: str, +): + """Yield zero or one ``data: token-usage`` SSE frame. + + Side effect: logs a one-line ``[token_usage] {log_label}: ...`` summary so + cost analysis can grep call/total/cost across all flows. + """ + usage_summary = accumulator.per_message_summary() + _perf_log.info( + "[token_usage] %s: calls=%d total=%d cost_micros=%d summary=%s", + log_label, + len(accumulator.calls), + accumulator.grand_total, + accumulator.total_cost_micros, + usage_summary, + ) + if usage_summary: + yield streaming_service.format_data( + "token-usage", + { + "usage": usage_summary, + "prompt_tokens": accumulator.total_prompt_tokens, + "completion_tokens": accumulator.total_completion_tokens, + "total_tokens": accumulator.grand_total, + "cost_micros": accumulator.total_cost_micros, + "call_details": accumulator.serialized_calls(), + }, + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py new file mode 100644 index 000000000..8d425402f --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py @@ -0,0 +1,69 @@ +"""Shared finally-block helpers: session close, GC pass, native-heap trim. + +These are called from inside an ``anyio.CancelScope(shield=True)`` block in +each flow's ``finally`` (Starlette's BaseHTTPMiddleware cancels the scope on +client disconnect; without the shield the very first ``await`` would raise +``CancelledError`` and the rest of cleanup — including ``session.close()`` — +would never run). +""" + +from __future__ import annotations + +import contextlib +import gc +import logging + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.db import shielded_async_session +from app.services.chat_session_state_service import clear_ai_responding +from app.utils.perf import get_perf_logger, log_system_snapshot, trim_native_heap + +_perf_log = get_perf_logger() +logger = logging.getLogger(__name__) + + +async def close_session_and_clear_ai_responding( + session: AsyncSession, chat_id: int +) -> None: + """Rollback + clear AI-responding flag + expunge_all + close. + + On rollback failure we fall back to a fresh shielded session for the flag + clear so a UI is never stuck on "AI is responding…" after a crash. + """ + try: + await session.rollback() + await clear_ai_responding(session, chat_id) + except Exception: + try: + async with shielded_async_session() as fresh_session: + await clear_ai_responding(fresh_session, chat_id) + except Exception: + logger.warning( + "Failed to clear AI responding state for thread %s", chat_id + ) + + with contextlib.suppress(Exception): + session.expunge_all() + + with contextlib.suppress(Exception): + await session.close() + + +def run_gc_pass(*, log_prefix: str, chat_id: int) -> None: + """One full gen0/1/2 pass + native-heap trim + END system snapshot. + + Breaking circular refs held by the agent graph, tools, and LLM wrappers + needs to happen in the caller (set the locals to ``None``) — this just + runs the collector and logs how many objects came back. + """ + collected = gc.collect(0) + gc.collect(1) + gc.collect(2) + if collected: + _perf_log.info( + "[%s] gc.collect() reclaimed %d objects (chat_id=%s)", + log_prefix, + collected, + chat_id, + ) + trim_native_heap() + log_system_snapshot(f"{log_prefix}_END") diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/first_frames.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/first_frames.py new file mode 100644 index 000000000..5e568b1e8 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/first_frames.py @@ -0,0 +1,40 @@ +"""Initial SSE frames every flow emits right after pre-stream setup. + +Order matters: ``message_start`` opens the assistant message, ``start_step`` +opens the first thinking step, ``turn-info`` lets the frontend stamp the +correlation id onto the in-flight message, and ``turn-status: busy`` flips the +UI into the streaming state. +""" + +from __future__ import annotations + +from collections.abc import Iterator + +from app.services.new_streaming_service import VercelStreamingService + + +def iter_initial_frames( + streaming_service: VercelStreamingService, + *, + turn_id: str, +) -> Iterator[str]: + """Yield the four canonical opening frames in order. + + ``turn-info`` carries ``chat_turn_id`` so even pure-text turns (which + never produce a tool / action-log event) still teach the frontend the + turn correlation id used for ``appendMessage`` durable storage. + """ + yield streaming_service.format_message_start() + yield streaming_service.format_start_step() + yield streaming_service.format_data("turn-info", {"chat_turn_id": turn_id}) + yield streaming_service.format_data("turn-status", {"status": "busy"}) + + +def iter_final_frames( + streaming_service: VercelStreamingService, +) -> Iterator[str]: + """Yield ``turn-status: idle`` plus the finish/done trailer in order.""" + yield streaming_service.format_data("turn-status", {"status": "idle"}) + yield streaming_service.format_finish_step() + yield streaming_service.format_finish() + yield streaming_service.format_done() diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/llm_bundle.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/llm_bundle.py new file mode 100644 index 000000000..2f334114c --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/llm_bundle.py @@ -0,0 +1,57 @@ +"""Load an LLM + AgentConfig bundle for a given config id. + +Handles both code paths uniformly: +- ``config_id >= 0`` → database-backed ``NewLLMConfig`` row (per-user/per-space). +- ``config_id < 0`` → YAML-defined global LLM config (built-in defaults). + +Returns ``(llm, agent_config, error_message)``; on success ``error_message`` is +``None``. The caller emits the friendly SSE error frame. +""" + +from __future__ import annotations + +from typing import Any + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.agents.new_chat.llm_config import ( + AgentConfig, + create_chat_litellm_from_agent_config, + create_chat_litellm_from_config, + load_agent_config, + load_global_llm_config_by_id, +) + + +async def load_llm_bundle( + session: AsyncSession, + *, + config_id: int, + search_space_id: int, +) -> tuple[Any, AgentConfig | None, str | None]: + if config_id >= 0: + loaded_agent_config = await load_agent_config( + session=session, + config_id=config_id, + search_space_id=search_space_id, + ) + if not loaded_agent_config: + return ( + None, + None, + f"Failed to load NewLLMConfig with id {config_id}", + ) + return ( + create_chat_litellm_from_agent_config(loaded_agent_config), + loaded_agent_config, + None, + ) + + loaded_llm_config = load_global_llm_config_by_id(config_id) + if not loaded_llm_config: + return None, None, f"Failed to load LLM config with id {config_id}" + return ( + create_chat_litellm_from_config(loaded_llm_config), + AgentConfig.from_yaml_config(loaded_llm_config), + None, + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/pre_stream_setup.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/pre_stream_setup.py new file mode 100644 index 000000000..ec92306dd --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/pre_stream_setup.py @@ -0,0 +1,40 @@ +"""Pre-stream setup: connector service, firecrawl key, checkpointer.""" + +from __future__ import annotations + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.agents.new_chat.checkpointer import get_checkpointer +from app.db import SearchSourceConnectorType +from app.services.connector_service import ConnectorService + + +async def setup_connector_and_firecrawl( + session: AsyncSession, + *, + search_space_id: int, +) -> tuple[ConnectorService, str | None]: + """Build the per-turn connector service and pull the firecrawl API key. + + Returns ``(connector_service, firecrawl_api_key)``. ``firecrawl_api_key`` is + ``None`` when no web-crawler connector is configured (the agent simply + skips firecrawl-backed tools in that case). + """ + connector_service = ConnectorService(session, search_space_id=search_space_id) + firecrawl_api_key: str | None = None + webcrawler_connector = await connector_service.get_connector_by_type( + SearchSourceConnectorType.WEBCRAWLER_CONNECTOR, search_space_id + ) + if webcrawler_connector and webcrawler_connector.config: + firecrawl_api_key = webcrawler_connector.config.get("FIRECRAWL_API_KEY") + return connector_service, firecrawl_api_key + + +async def get_chat_checkpointer(): + """Resolve the PostgreSQL checkpointer for persistent conversation memory. + + Thin wrapper around ``app.agents.new_chat.checkpointer.get_checkpointer`` so + flow orchestrators can rely on a streaming-local symbol and we have a hook + point if the checkpointer source ever needs to vary per flow. + """ + return await get_checkpointer() diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py new file mode 100644 index 000000000..1e5169af1 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py @@ -0,0 +1,80 @@ +"""OpenTelemetry chat-request span wrapper for streaming flows.""" + +from __future__ import annotations + +import contextlib +import sys +from typing import Any, Literal + +from app.observability import metrics as ot_metrics +from app.observability import otel as ot + + +def open_chat_request_span( + *, + chat_id: int, + search_space_id: int, + flow: Literal["new", "regenerate", "resume"], + request_id: str | None, + turn_id: str, + filesystem_mode: str, + client_platform: str, + agent_mode: str, +) -> tuple[Any, Any]: + """Open the per-request span; returns ``(span_cm, span)`` for finally-close.""" + span_cm = ot.chat_request_span( + chat_id=chat_id, + search_space_id=search_space_id, + flow=flow, + request_id=request_id, + turn_id=turn_id, + filesystem_mode=filesystem_mode, + client_platform=client_platform, + agent_mode=agent_mode, + ) + span = span_cm.__enter__() + return span_cm, span + + +def set_agent_mode(span: Any, agent_mode: str) -> None: + """Tag the span with the resolved agent mode (single / multi).""" + with contextlib.suppress(Exception): + span.set_attribute("agent.mode", agent_mode) + + +def close_chat_request_span( + *, + span_cm: Any, + span: Any, + chat_outcome: str, + chat_agent_mode: str, + flow: Literal["new", "regenerate", "resume"], + chat_error_category: str | None, + duration_seconds: float, +) -> None: + """Record metrics + close the span. Swallows errors (finally-block context).""" + with contextlib.suppress(Exception): + span.set_attribute("chat.outcome", chat_outcome) + ot_metrics.record_chat_request_duration( + duration_seconds * 1000, + flow=flow, + outcome=chat_outcome, + agent_mode=chat_agent_mode, + ) + ot_metrics.record_chat_request_outcome( + flow=flow, + outcome=chat_outcome, + agent_mode=chat_agent_mode, + error_category=chat_error_category, + ) + span_cm.__exit__(*sys.exc_info()) + + +def record_outcome_attrs( + span: Any, *, chat_outcome: str, chat_error_category: str | None +) -> None: + """Stamp outcome + error.category on the span (used in the except branch).""" + with contextlib.suppress(Exception): + span.set_attribute("chat.outcome", chat_outcome) + if chat_error_category is not None: + span.set_attribute("error.category", chat_error_category) From 40300d300a01705e3134c9953ef6008cd322c9e8 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:49:14 +0200 Subject: [PATCH 057/198] refactor(chat): add streaming/flows/shared/premium_quota.py Centralizes the premium-credits lifecycle for chat turns: * needs_premium_quota: gate check (premium user + non-fallback config). * PremiumReservation: dataclass capturing reservation state + token totals. * reserve_premium / finalize_premium / release_premium: idempotent reservation, commit, and rollback used by the orchestrators. Add-only; legacy stream_new_chat.py keeps its inline quota handling until cutover. --- .../streaming/flows/shared/premium_quota.py | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py new file mode 100644 index 000000000..0ec40d275 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py @@ -0,0 +1,132 @@ +"""Premium credit (USD micro-units) reserve / finalize / release lifecycle. + +Both ``stream_new_chat`` and ``stream_resume_chat`` reserve premium credits up +front (so a single LLM call can't run away with the budget), then finalize the +actual provider cost reported by LiteLLM when the turn completes successfully, +or release the reservation on the cancellation / interrupted-without-finalize +paths. + +State is held by the orchestrator as a simple ``PremiumReservation`` tuple +so reservation, fallback-on-denied, finalize, and release can all be reasoned +about from one place. +""" + +from __future__ import annotations + +import logging +import uuid as _uuid +from dataclasses import dataclass +from typing import TYPE_CHECKING +from uuid import UUID + +from app.agents.new_chat.llm_config import AgentConfig +from app.db import shielded_async_session + +if TYPE_CHECKING: + from app.services.token_tracking_service import TokenAccumulator + + +@dataclass +class PremiumReservation: + """Active premium-credit reservation for one turn. + + ``request_id`` is the per-reservation idempotency key (also passed to + ``finalize``/``release`` so racing branches resolve to the same row). + ``reserved_micros`` is the up-front estimate; ``finalize`` debits the + actual cost, ``release`` returns it untouched. + """ + + request_id: str + reserved_micros: int + allowed: bool + + +def needs_premium_quota( + agent_config: AgentConfig | None, user_id: str | None +) -> bool: + return bool(agent_config is not None and user_id and agent_config.is_premium) + + +async def reserve_premium( + *, + agent_config: AgentConfig, + user_id: str, +) -> PremiumReservation: + """Reserve estimated micros up front; returns the reservation handle.""" + from app.services.token_quota_service import ( + TokenQuotaService, + estimate_call_reserve_micros, + ) + + request_id = _uuid.uuid4().hex[:16] + litellm_params = agent_config.litellm_params or {} + base_model = ( + litellm_params.get("base_model") if isinstance(litellm_params, dict) else None + ) or agent_config.model_name or "" + reserve_amount_micros = estimate_call_reserve_micros( + base_model=base_model, + quota_reserve_tokens=agent_config.quota_reserve_tokens, + ) + async with shielded_async_session() as quota_session: + quota_result = await TokenQuotaService.premium_reserve( + db_session=quota_session, + user_id=UUID(user_id), + request_id=request_id, + reserve_micros=reserve_amount_micros, + ) + return PremiumReservation( + request_id=request_id, + reserved_micros=reserve_amount_micros, + allowed=quota_result.allowed, + ) + + +async def finalize_premium( + *, + reservation: PremiumReservation, + user_id: str, + accumulator: TokenAccumulator, +) -> None: + """Finalize debit using the actual provider cost reported by LiteLLM. + + Best-effort: failures here must not bubble up to the SSE stream — the user + has already received their tokens; we log and move on. + """ + try: + from app.services.token_quota_service import TokenQuotaService + + async with shielded_async_session() as quota_session: + await TokenQuotaService.premium_finalize( + db_session=quota_session, + user_id=UUID(user_id), + request_id=reservation.request_id, + actual_micros=accumulator.total_cost_micros, + reserved_micros=reservation.reserved_micros, + ) + except Exception: + logging.getLogger(__name__).warning( + "Failed to finalize premium quota for user %s", + user_id, + exc_info=True, + ) + + +async def release_premium( + *, + reservation: PremiumReservation, + user_id: str, +) -> None: + """Release the reservation on cancellation paths; never raises.""" + try: + from app.services.token_quota_service import TokenQuotaService + + async with shielded_async_session() as quota_session: + await TokenQuotaService.premium_release( + db_session=quota_session, + user_id=UUID(user_id), + reserved_micros=reservation.reserved_micros, + ) + except Exception: + logging.getLogger(__name__).warning( + "Failed to release premium quota for user %s", user_id + ) From 2c3edb7c845d1be1a8b4c4d4c772b9ec25945c82 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:49:18 +0200 Subject: [PATCH 058/198] refactor(chat): add streaming/flows/shared/terminal_error.py Extracts handle_terminal_exception: the shared except-branch behavior for the chat orchestrators. Classifies the raised exception, logs the structured chat_stream error event, and emits the terminal-error SSE frame + done sentinel via the streaming service. Add-only; nothing imports it yet. --- .../streaming/flows/shared/terminal_error.py | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py new file mode 100644 index 000000000..c9db2caf2 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py @@ -0,0 +1,120 @@ +"""Handle the ``except Exception`` branch of a streaming flow. + +Classifies the exception, records OpenTelemetry attributes, emits one terminal +error SSE frame and the trailing ``turn-status: idle`` + finish/done frames. + +Used by both ``stream_new_chat`` and ``stream_resume_chat``; flow-specific bits +(label, span, BusyError tracking) are passed by the caller. +""" + +from __future__ import annotations + +import logging +import traceback +from collections.abc import Iterator +from typing import Any, Literal + +from app.agents.new_chat.errors import BusyError +from app.observability import metrics as ot_metrics +from app.observability import otel as ot +from app.services.new_streaming_service import VercelStreamingService +from app.tasks.chat.streaming.errors.classifier import classify_stream_exception +from app.tasks.chat.streaming.errors.emitter import emit_stream_terminal_error +from app.tasks.chat.streaming.flows.shared.first_frames import iter_final_frames +from app.tasks.chat.streaming.flows.shared.span import record_outcome_attrs + +logger = logging.getLogger(__name__) + + +def handle_terminal_exception( + exc: Exception, + *, + flow: Literal["new", "regenerate", "resume"], + flow_label: str, + log_prefix: str, + streaming_service: VercelStreamingService, + request_id: str | None, + chat_id: int, + search_space_id: int, + user_id: str | None, + chat_span: Any, +) -> tuple[Iterator[str], dict[str, Any]]: + """Classify, log, and produce the SSE frames for a terminal exception. + + Returns ``(frame_iterator, summary)``. ``summary`` carries:: + + - ``busy_error_raised``: bool — caller must skip the lock-release path + when True (caller never acquired the busy mutex). + - ``chat_outcome``: str — span outcome attribute. + - ``chat_error_category``: str — categorized error label for metrics. + """ + busy_error_raised = isinstance(exc, BusyError) + + ( + error_kind, + error_code, + severity, + is_expected, + user_message, + error_extra, + ) = classify_stream_exception(exc, flow_label=flow_label) + chat_outcome = error_code or error_kind or "error" + chat_error_category = ot_metrics.categorize_exception(exc) + record_outcome_attrs( + chat_span, + chat_outcome=chat_outcome, + chat_error_category=chat_error_category, + ) + with __suppress(): + ot.record_error(chat_span, exc) + error_message = f"Error during {flow_label}: {exc!s}" + # Match the original behavior: log full traceback via ``print`` so it lands + # in stderr regardless of the logger config. + print(f"[{log_prefix}] {error_message}") + print(f"[{log_prefix}] Exception type: {type(exc).__name__}") + print(f"[{log_prefix}] Traceback:\n{traceback.format_exc()}") + + def _iter_frames() -> Iterator[str]: + if error_code == "TURN_CANCELLING": + status_payload: dict[str, Any] = {"status": "cancelling"} + if error_extra: + status_payload.update(error_extra) + yield streaming_service.format_data("turn-status", status_payload) + else: + yield streaming_service.format_data("turn-status", {"status": "busy"}) + + yield emit_stream_terminal_error( + streaming_service=streaming_service, + flow=flow, + request_id=request_id, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + message=user_message, + error_kind=error_kind, + error_code=error_code, + severity=severity, + is_expected=is_expected, + extra=error_extra, + ) + yield from iter_final_frames(streaming_service) + + return ( + _iter_frames(), + { + "busy_error_raised": busy_error_raised, + "chat_outcome": chat_outcome, + "chat_error_category": chat_error_category, + }, + ) + + +def __suppress(): + """Local single-use ``contextlib.suppress(Exception)`` factory. + + Inlined here so callers don't import ``contextlib`` just for the + ``record_error`` call site. + """ + import contextlib + + return contextlib.suppress(Exception) From b54b803dc9a844d74700f3dc27eb00282d63b081 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:49:27 +0200 Subject: [PATCH 059/198] refactor(chat): add streaming/flows/shared/ rate-limit recovery + stream loop Two cooperating modules that wrap stream_agent_events with in-stream recovery from provider 429s: * rate_limit_recovery: can_recover_provider_rate_limit truth-table guard, reroute_to_next_auto_pin (selects the next eligible auto-pin config and reloads the LLM bundle), log_rate_limit_recovered. * stream_loop: run_stream_loop drives stream_agent_events in a while-True loop, delegating recovery to a flow-supplied RecoverFn callback so new_chat and resume_chat can share the same loop while keeping their own nonlocal state. Add-only; not yet wired into any orchestrator. --- .../flows/shared/rate_limit_recovery.py | 129 ++++++++++++++++++ .../streaming/flows/shared/stream_loop.py | 85 ++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/rate_limit_recovery.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/stream_loop.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/rate_limit_recovery.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/rate_limit_recovery.py new file mode 100644 index 000000000..6b3857594 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/rate_limit_recovery.py @@ -0,0 +1,129 @@ +"""Shared steps for the in-stream provider rate-limit recovery loop. + +Both flows wrap ``run_stream_loop`` with a flow-specific ``recover`` closure; +the *guard*, the *auto-pin reroute*, and the *post-recovery telemetry* are the +same on both sides and live here so behaviour can't drift. + +The orchestrator owns the parts that genuinely diverge: + + * cancelling the title task (new_chat only), + * passing ``mentioned_document_ids`` to ``build_main_agent_for_thread``, + * the log prefix (``stream_new_chat`` vs ``stream_resume``). +""" + +from __future__ import annotations + +from typing import Literal + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.agents.new_chat.middleware.busy_mutex import end_turn +from app.observability import otel as ot +from app.services.auto_model_pin_service import ( + mark_runtime_cooldown, + resolve_or_get_pinned_llm_config_id, +) +from app.tasks.chat.streaming.errors.classifier import ( + is_provider_rate_limited, + log_chat_stream_error, +) + + +def can_recover_provider_rate_limit( + exc: BaseException, + *, + first_event_seen: bool, + runtime_rate_limit_recovered: bool, + requested_llm_config_id: int, + current_llm_config_id: int, +) -> bool: + """Guard: only the first auto-pin → provider-rate-limited failure recovers. + + All conditions must hold: + + * ``runtime_rate_limit_recovered is False`` — at most one recovery per turn. + * ``requested_llm_config_id == 0`` — caller opted into auto-pin (id=0). + * ``current_llm_config_id < 0`` — currently on a YAML config (the only + kind the auto-pin pool draws from). + * ``first_event_seen is False`` — we haven't sent any SSE to the user yet, + so a silent rebuild + retry is invisible. + * The exception is provider-side rate-limited (HTTP 429 or known shape). + """ + return ( + not runtime_rate_limit_recovered + and requested_llm_config_id == 0 + and current_llm_config_id < 0 + and not first_event_seen + and is_provider_rate_limited(exc) + ) + + +async def reroute_to_next_auto_pin( + session: AsyncSession, + *, + chat_id: int, + search_space_id: int, + user_id: str | None, + current_llm_config_id: int, + requires_image_input: bool, +) -> int: + """Release lock, cool down the failing config, pick a new auto-pin id. + + Returns the new ``llm_config_id``. ``end_turn`` is called because the failed + attempt may still hold the per-thread busy mutex (middleware teardown can + lag behind raised provider errors) — the same-request retry would otherwise + bounce on ``BusyError``. + """ + end_turn(str(chat_id)) + mark_runtime_cooldown(current_llm_config_id, reason="provider_rate_limited") + pinned = await resolve_or_get_pinned_llm_config_id( + session, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + selected_llm_config_id=0, + exclude_config_ids={current_llm_config_id}, + requires_image_input=requires_image_input, + ) + return pinned.resolved_llm_config_id + + +def log_rate_limit_recovered( + *, + flow: Literal["new", "regenerate", "resume"], + request_id: str | None, + chat_id: int, + search_space_id: int, + user_id: str | None, + previous_config_id: int, + new_config_id: int, +) -> None: + """Emit the OTEL event + structured ``[chat_stream_error]`` log line.""" + ot.add_event( + "chat.rate_limit.recovered", + { + "recovery.reason": "provider_rate_limited", + "recovery.previous_config_id": previous_config_id, + "recovery.fallback_config_id": new_config_id, + }, + ) + log_chat_stream_error( + flow=flow, + error_kind="rate_limited", + error_code="RATE_LIMITED", + severity="info", + is_expected=True, + request_id=request_id, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + message=( + "Auto-pinned model hit runtime rate limit; switched to " + "another eligible model and retried." + ), + extra={ + "auto_runtime_recover": True, + "previous_config_id": previous_config_id, + "fallback_config_id": new_config_id, + }, + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/stream_loop.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/stream_loop.py new file mode 100644 index 000000000..6cf0df855 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/stream_loop.py @@ -0,0 +1,85 @@ +"""Drive ``stream_agent_events`` with in-stream rate-limit recovery. + +Both ``stream_new_chat`` and ``stream_resume_chat`` wrap the agent event loop +in a ``while True`` that catches the *first* provider rate-limit error +(``can_runtime_recover``) before any SSE event reaches the user, rebuilds the +agent on an alternative auto-pin, and retries the turn. + +The recovery callback is flow-specific (different ``mentioned_document_ids`` +contract, different logging label, etc.) — this module owns the loop shape, +the caller owns the rebuild. +""" + +from __future__ import annotations + +from collections.abc import AsyncGenerator, Awaitable, Callable +from typing import Any + +from app.agents.new_chat.filesystem_selection import FilesystemMode +from app.services.new_streaming_service import VercelStreamingService +from app.tasks.chat.streaming.agent.event_loop import stream_agent_events +from app.tasks.chat.streaming.shared.stream_result import StreamResult + +# Returns the rebuilt agent on a successful recovery, or ``None`` to re-raise +# the original exception (and let the orchestrator's terminal-error path +# handle it). +RecoverFn = Callable[[BaseException, bool], Awaitable[Any | None]] + + +async def run_stream_loop( + *, + agent: Any, + streaming_service: VercelStreamingService, + config: dict[str, Any], + input_data: Any, + stream_result: StreamResult, + step_prefix: str = "thinking", + initial_step_id: str | None = None, + initial_step_title: str = "", + initial_step_items: list[str] | None = None, + fallback_commit_search_space_id: int | None, + fallback_commit_created_by_id: str | None, + fallback_commit_filesystem_mode: FilesystemMode, + fallback_commit_thread_id: int | None, + runtime_context: Any, + content_builder: Any | None, + recover: RecoverFn, + on_first_event: Callable[[], None] | None = None, +) -> AsyncGenerator[str, None]: + """Yield SSE frames; rebuild and retry once on a pre-first-event rate limit. + + ``on_first_event`` fires after the first frame is observed (used by both + flows to write a one-time ``First agent event in N.NNNs`` perf line). + """ + first_event_logged = False + while True: + try: + async for sse in stream_agent_events( + agent=agent, + config=config, + input_data=input_data, + streaming_service=streaming_service, + result=stream_result, + step_prefix=step_prefix, + initial_step_id=initial_step_id, + initial_step_title=initial_step_title, + initial_step_items=initial_step_items, + fallback_commit_search_space_id=fallback_commit_search_space_id, + fallback_commit_created_by_id=fallback_commit_created_by_id, + fallback_commit_filesystem_mode=fallback_commit_filesystem_mode, + fallback_commit_thread_id=fallback_commit_thread_id, + runtime_context=runtime_context, + content_builder=content_builder, + ): + if not first_event_logged: + if on_first_event is not None: + on_first_event() + first_event_logged = True + yield sse + return + except Exception as exc: + new_agent = await recover(exc, first_event_logged) + if new_agent is None: + raise + agent = new_agent + continue From 21bddc73a75c121cc33ee5880d54e618d4409c9d Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:49:31 +0200 Subject: [PATCH 060/198] refactor(chat): add streaming/flows/shared/assistant_finalize.py Extracts finalize_assistant_message: the post-stream server-side write of the final assistant message (with content parts + token usage) guarded by asyncio.shield + shielded_async_session so a client disconnect cannot abort the persist. Add-only; legacy stream_new_chat.py keeps its inline finalize block until cutover. --- .../flows/shared/assistant_finalize.py | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py new file mode 100644 index 000000000..d16f81ac7 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py @@ -0,0 +1,109 @@ +"""Server-side assistant-message + token_usage finalization. + +Runs inside the streaming flow's ``finally`` block, after the main session has +been closed (uses its own shielded session, so we don't fight the same DB +connection). + +Idempotent against the legacy frontend ``appendMessage`` recovery branch: + + * the assistant row was already INSERTed by ``persist_assistant_shell`` + earlier in the turn, so this just UPDATEs it with the rich + ``ContentPart[]`` projection from the builder. + * ``token_usage`` uses ``INSERT ... ON CONFLICT DO NOTHING`` against the + partial unique index from migration 142, so a racing append_message + recovery branch can never double-write. + +``mark_interrupted`` closes any open text/reasoning blocks and flips running +tool-calls (no result) to ``state=aborted`` so the persisted JSONB reflects a +coherent end-state even on client disconnect. + +Never raises (best-effort, logs only). +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from app.tasks.chat.streaming.shared.stream_result import StreamResult +from app.utils.perf import get_perf_logger + +if TYPE_CHECKING: + from app.services.token_tracking_service import TokenAccumulator + +_perf_log = get_perf_logger() + + +async def finalize_assistant_message( + *, + stream_result: StreamResult | None, + chat_id: int, + search_space_id: int, + user_id: str | None, + accumulator: TokenAccumulator, + log_prefix: str, +) -> None: + """Snapshot the content builder and persist the final assistant payload. + + No-op when ``stream_result`` was never populated, the turn never reached + ``persist_assistant_shell`` (no ``assistant_message_id``), or the turn id + was never assigned. + """ + if not ( + stream_result + and stream_result.turn_id + and stream_result.assistant_message_id + ): + return + + from app.tasks.chat.persistence import finalize_assistant_turn + + builder_stats: dict[str, int] | None = None + if stream_result.content_builder is not None: + stream_result.content_builder.mark_interrupted() + # Snapshot stats BEFORE ``snapshot()`` deepcopies so the perf log + # records the actual finalised payload (post-mark_interrupted), not + # the live-mutating builder state. + builder_stats = stream_result.content_builder.stats() + content_payload = stream_result.content_builder.snapshot() + else: + # Defensive fallback — we always set the builder alongside + # ``assistant_message_id`` in the orchestrator, so this branch only + # fires if a future refactor ever decouples them. Persist whatever + # accumulated text we captured so the row at least renders. + content_payload = [ + { + "type": "text", + "text": stream_result.accumulated_text or "", + } + ] + + if builder_stats is not None: + _perf_log.info( + "[%s] finalize_payload chat_id=%s " + "message_id=%s parts=%d bytes=%d text=%d " + "reasoning=%d tool_calls=%d " + "tool_calls_completed=%d tool_calls_aborted=%d " + "thinking_step_parts=%d step_separators=%d", + log_prefix, + chat_id, + stream_result.assistant_message_id, + builder_stats["parts"], + builder_stats["bytes"], + builder_stats["text"], + builder_stats["reasoning"], + builder_stats["tool_calls"], + builder_stats["tool_calls_completed"], + builder_stats["tool_calls_aborted"], + builder_stats["thinking_step_parts"], + builder_stats["step_separators"], + ) + + await finalize_assistant_turn( + message_id=stream_result.assistant_message_id, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + turn_id=stream_result.turn_id, + content=content_payload, + accumulator=accumulator, + ) From 927009745e8fde89c0741e5d4fa503984058c416 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:49:45 +0200 Subject: [PATCH 061/198] refactor(chat): add streaming/flows/new_chat/ per-concern leaf modules Seven focused modules that the upcoming new_chat orchestrator composes: * auto_pin: resolve_initial_auto_pin selects the initial config (with vision-capable filtering and error classification). * llm_capability: check_image_input_capability blocks routing an image-bearing turn to a known text-only model. * runtime_context: build_new_chat_runtime_context assembles the SurfSenseContextSchema for a new-chat turn. * persistence_spawn: spawn_set_ai_responding_bg, spawn_persist_user_task, spawn_persist_assistant_shell_task, and await_persist_task background the four pre-stream DB writes so they overlap with agent build. * initial_thinking_step: build_initial_thinking_step + iter_initial_thinking_step_frame produce the very first thinking-1 SSE step ("Understanding your request" / "Analyzing referenced content"). * title_gen: spawn_title_task + maybe_emit_title_update + await_pending_title_update background the thread-title generator and interleave its update into the stream when ready. * input_state: build_new_chat_input_state assembles the LangGraph input_state (history bootstrap, mentions resolution, context blocks, human-message construction). The heavy one. Add-only; no orchestrator yet (next commit). --- .../chat/streaming/flows/new_chat/auto_pin.py | 95 +++++++ .../flows/new_chat/initial_thinking_step.py | 95 +++++++ .../streaming/flows/new_chat/input_state.py | 264 ++++++++++++++++++ .../flows/new_chat/llm_capability.py | 62 ++++ .../flows/new_chat/persistence_spawn.py | 129 +++++++++ .../flows/new_chat/runtime_context.py | 38 +++ .../streaming/flows/new_chat/title_gen.py | 237 ++++++++++++++++ 7 files changed, 920 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/persistence_spawn.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py new file mode 100644 index 000000000..cb20eb011 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py @@ -0,0 +1,95 @@ +"""Resolve the auto-pin for the *initial* turn config. + +Auto-pin (``selected_llm_config_id=0``) picks the best eligible LLM config for +this thread / search space / user, optionally filtered to vision-capable +configs when the turn carries images. + +Errors classified here: + + * ``MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT`` — the auto-pin pool has no + vision-capable cfg for an image-bearing turn. The same gate fires later + in ``llm_capability`` for explicit selections; mapping both to the same + code keeps the FE error UI consistent. + * ``SERVER_ERROR`` — any other ``ValueError`` from the resolver. + +This module owns *initial* pin resolution; the rate-limit recovery loop has +its own narrower auto-pin call (with ``exclude_config_ids``) in +``flows/shared/rate_limit_recovery``. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Literal + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.observability import otel as ot +from app.services.auto_model_pin_service import resolve_or_get_pinned_llm_config_id + + +@dataclass +class AutoPinResult: + """Outcome of ``resolve_initial_auto_pin``. + + ``llm_config_id`` is set when ``error`` is ``None``; ``error`` carries the + classified user-facing message plus error code/kind so the orchestrator can + emit one terminal-error SSE frame. + """ + + llm_config_id: int | None + error: tuple[str, str, Literal["user_error", "server_error"]] | None + + +async def resolve_initial_auto_pin( + session: AsyncSession, + *, + chat_id: int, + search_space_id: int, + user_id: str | None, + selected_llm_config_id: int, + requires_image_input: bool, + requested_llm_config_id: int, +) -> AutoPinResult: + """Run the resolver and classify any ``ValueError`` for the SSE error path.""" + try: + pinned = await resolve_or_get_pinned_llm_config_id( + session, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + selected_llm_config_id=selected_llm_config_id, + requires_image_input=requires_image_input, + ) + ot.add_event( + "model.pin.resolved", + { + "pin.requested_id": requested_llm_config_id, + "pin.resolved_id": pinned.resolved_llm_config_id, + "pin.requires_image_input": requires_image_input, + }, + ) + return AutoPinResult( + llm_config_id=pinned.resolved_llm_config_id, error=None + ) + except ValueError as pin_error: + # The "no vision-capable cfg" path raises a ValueError whose message + # we map to the friendly image-input SSE error so the user sees the + # same message regardless of whether the gate fired in the resolver or + # in ``llm_capability.assert_vision_capability_for_image_turn``. + is_vision_failure = ( + requires_image_input and "vision-capable" in str(pin_error) + ) + error_code = ( + "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT" + if is_vision_failure + else "SERVER_ERROR" + ) + error_kind: Literal["user_error", "server_error"] = ( + "user_error" if is_vision_failure else "server_error" + ) + if is_vision_failure: + ot.add_event("quota.denied", {"quota.code": error_code}) + return AutoPinResult( + llm_config_id=None, error=(str(pin_error), error_code, error_kind) + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py new file mode 100644 index 000000000..c860e517e --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py @@ -0,0 +1,95 @@ +"""Build and emit the first ``thinking-1`` step for a new-chat turn. + +The step title and "Processing X" items are derived from what the user sent +(text snippet, image count, mentioned doc titles) so the FE can render a +meaningful placeholder while the agent stream warms up. + +``thinking-1`` is the canonical id for this step — every subsequent +``thinking-N`` produced by ``stream_agent_events`` folds into the same +singleton ``data-thinking-steps`` part on the FE. +""" + +from __future__ import annotations + +from collections.abc import Iterator +from dataclasses import dataclass +from typing import Any + +from app.db import SurfsenseDocsDocument +from app.services.new_streaming_service import VercelStreamingService + + +@dataclass +class InitialThinkingStep: + """Resolved fields passed both into the SSE frame and the builder hook. + + ``items`` is the bullet list under the step title; ``title`` is the + one-line step header. ``step_id`` is hard-coded ``thinking-1`` so the FE + Timeline can de-duplicate against the prior assistant message on resume. + """ + + step_id: str + title: str + items: list[str] + + +def build_initial_thinking_step( + *, + user_query: str, + user_image_data_urls: list[str] | None, + mentioned_surfsense_docs: list[SurfsenseDocsDocument], +) -> InitialThinkingStep: + if mentioned_surfsense_docs: + title = "Analyzing referenced content" + action_verb = "Analyzing" + else: + title = "Understanding your request" + action_verb = "Processing" + + processing_parts: list[str] = [] + if user_query.strip(): + query_text = user_query[:80] + ("..." if len(user_query) > 80 else "") + processing_parts.append(query_text) + elif user_image_data_urls: + processing_parts.append(f"[{len(user_image_data_urls)} image(s)]") + else: + processing_parts.append("(message)") + + if mentioned_surfsense_docs: + doc_names: list[str] = [] + for doc in mentioned_surfsense_docs: + t = doc.title + if len(t) > 30: + t = t[:27] + "..." + doc_names.append(t) + if len(doc_names) == 1: + processing_parts.append(f"[{doc_names[0]}]") + else: + processing_parts.append(f"[{len(doc_names)} docs]") + + items = [f"{action_verb}: {' '.join(processing_parts)}"] + return InitialThinkingStep(step_id="thinking-1", title=title, items=items) + + +def iter_initial_thinking_step_frame( + step: InitialThinkingStep, + *, + streaming_service: VercelStreamingService, + content_builder: Any | None, +) -> Iterator[str]: + """Drive both the SSE emission and the builder hook for the initial step. + + The FE folds this step into the same singleton ``data-thinking-steps`` part + as everything the agent stream emits later, so we mirror that fold + server-side by driving the builder lifecycle ourselves. + """ + if content_builder is not None: + content_builder.on_thinking_step( + step.step_id, step.title, "in_progress", step.items + ) + yield streaming_service.format_thinking_step( + step_id=step.step_id, + title=step.title, + status="in_progress", + items=step.items, + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py new file mode 100644 index 000000000..fb171c244 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py @@ -0,0 +1,264 @@ +r"""Assemble the LangGraph ``input_state`` for the new-chat turn. + +Pipeline: + + 1. **History bootstrap** — only for cloned chats with no LangGraph checkpoint + yet; flips the per-thread ``needs_history_bootstrap`` flag back to False + once the rows are loaded. + 2. **Mentioned SurfSense docs** — eager-load chunks so the formatter has the + full content without a second roundtrip. + 3. **Recent reports** — top 3 by id desc with non-null content, so the LLM + can resolve ``report_id`` for versioning without spelunking history. + 4. **@-mention resolve** (cloud mode) — substitute ``@title`` tokens in the + query with canonical ``\`/documents/...\``` paths the LLM expects. + 5. **Context block render** — XML-wrap surfsense docs + reports, prepend to + the rewritten query, optionally prefix with display name for SEARCH_SPACE + visibility. + 6. **HumanMessage** — multimodal content if images are attached. + +Returns the assembled ``input_state`` dict plus side-channel data the +orchestrator needs downstream (``accepted_folder_ids`` for runtime context; +``mentioned_surfsense_docs`` for the initial thinking step). +""" + +from __future__ import annotations + +import logging +from dataclasses import dataclass +from typing import Any + +from langchain_core.messages import HumanMessage +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.future import select +from sqlalchemy.orm import selectinload + +from app.agents.new_chat.filesystem_selection import FilesystemMode +from app.agents.new_chat.mention_resolver import resolve_mentions, substitute_in_text +from app.db import ( + ChatVisibility, + NewChatThread, + Report, + SurfsenseDocsDocument, +) +from app.tasks.chat.streaming.context.mentioned_docs import ( + format_mentioned_surfsense_docs_as_context, +) +from app.utils.content_utils import bootstrap_history_from_db +from app.utils.user_message_multimodal import build_human_message_content + +logger = logging.getLogger(__name__) + + +@dataclass +class NewChatInputState: + """Everything ``build_new_chat_input_state`` produces. + + ``input_state`` is fed straight to the agent. ``accepted_folder_ids`` + feeds the runtime context (the resolver may have dropped some chips). + ``mentioned_surfsense_docs`` is consumed by the initial thinking-step + builder for the FE placeholder before the agent stream starts. + """ + + input_state: dict[str, Any] + accepted_folder_ids: list[int] + mentioned_surfsense_docs: list[SurfsenseDocsDocument] + + +async def build_new_chat_input_state( + session: AsyncSession, + *, + chat_id: int, + search_space_id: int, + user_query: str, + user_image_data_urls: list[str] | None, + mentioned_document_ids: list[int] | None, + mentioned_surfsense_doc_ids: list[int] | None, + mentioned_folder_ids: list[int] | None, + mentioned_documents: list[dict[str, Any]] | None, + needs_history_bootstrap: bool, + thread_visibility: ChatVisibility, + current_user_display_name: str | None, + filesystem_mode: str, + request_id: str | None, + turn_id: str, +) -> NewChatInputState: + langchain_messages: list[Any] = [] + + if needs_history_bootstrap: + langchain_messages = await bootstrap_history_from_db( + session, chat_id, thread_visibility=thread_visibility + ) + thread_result = await session.execute( + select(NewChatThread).filter(NewChatThread.id == chat_id) + ) + thread = thread_result.scalars().first() + if thread: + thread.needs_history_bootstrap = False + await session.commit() + + mentioned_surfsense_docs: list[SurfsenseDocsDocument] = [] + if mentioned_surfsense_doc_ids: + result = await session.execute( + select(SurfsenseDocsDocument) + .options(selectinload(SurfsenseDocsDocument.chunks)) + .filter(SurfsenseDocsDocument.id.in_(mentioned_surfsense_doc_ids)) + ) + mentioned_surfsense_docs = list(result.scalars().all()) + + # Top 3 reports keyed by id desc (newest first) with content present, + # surfaced inline so the LLM resolves ``report_id`` for versioning without + # digging through conversation history. + recent_reports_result = await session.execute( + select(Report) + .filter( + Report.thread_id == chat_id, + Report.content.isnot(None), + ) + .order_by(Report.id.desc()) + .limit(3) + ) + recent_reports = list(recent_reports_result.scalars().all()) + + agent_user_query, accepted_folder_ids = await _resolve_mentions_for_query( + session, + search_space_id=search_space_id, + user_query=user_query, + filesystem_mode=filesystem_mode, + mentioned_document_ids=mentioned_document_ids, + mentioned_surfsense_doc_ids=mentioned_surfsense_doc_ids, + mentioned_folder_ids=mentioned_folder_ids, + mentioned_documents=mentioned_documents, + ) + + final_query = _render_query_with_context( + agent_user_query=agent_user_query, + mentioned_surfsense_docs=mentioned_surfsense_docs, + recent_reports=recent_reports, + ) + + if thread_visibility == ChatVisibility.SEARCH_SPACE and current_user_display_name: + final_query = f"**[{current_user_display_name}]:** {final_query}" + + human_content = build_human_message_content( + final_query, list(user_image_data_urls or ()) + ) + langchain_messages.append(HumanMessage(content=human_content)) + + input_state = { + "messages": langchain_messages, + "search_space_id": search_space_id, + "request_id": request_id or "unknown", + "turn_id": turn_id, + } + + return NewChatInputState( + input_state=input_state, + accepted_folder_ids=accepted_folder_ids, + mentioned_surfsense_docs=mentioned_surfsense_docs, + ) + + +async def _resolve_mentions_for_query( + session: AsyncSession, + *, + search_space_id: int, + user_query: str, + filesystem_mode: str, + mentioned_document_ids: list[int] | None, + mentioned_surfsense_doc_ids: list[int] | None, + mentioned_folder_ids: list[int] | None, + mentioned_documents: list[dict[str, Any]] | None, +) -> tuple[str, list[int]]: + r"""Resolve @-mention chips and rewrite the user query to canonical paths. + + Cloud mode only: local-folder mode keeps the legacy ``@title`` text path + (mention support there is a follow-up task — the path scheme is + mount-rooted and the picker UI both need separate work). + + The substitution lands in the returned ``agent_user_query`` ONLY — the + original ``user_query`` (with ``@title`` tokens) flows untouched into + ``persist_user_turn`` so chip rendering on reload still works + (``UserTextPart`` → ``parseMentionSegments`` matches ``@title``, not + ``\`/documents/...\```). It also feeds the human-readable surfaces — SSE + "Processing X" status, auto thread title, memory seed — which all want + what the user typed. + """ + agent_user_query = user_query + accepted_folder_ids: list[int] = [] + + has_any_mention = bool( + mentioned_document_ids + or mentioned_surfsense_doc_ids + or mentioned_folder_ids + or mentioned_documents + ) + if filesystem_mode != FilesystemMode.CLOUD.value or not has_any_mention: + return agent_user_query, accepted_folder_ids + + from app.schemas.new_chat import MentionedDocumentInfo + + chip_objs: list[MentionedDocumentInfo] | None = None + if mentioned_documents: + chip_objs = [] + for raw in mentioned_documents: + if isinstance(raw, MentionedDocumentInfo): + chip_objs.append(raw) + continue + try: + chip_objs.append(MentionedDocumentInfo.model_validate(raw)) + except Exception: + logger.debug( + "stream_new_chat: dropping malformed mention chip %r", raw + ) + + resolved = await resolve_mentions( + session, + search_space_id=search_space_id, + mentioned_documents=chip_objs, + mentioned_document_ids=mentioned_document_ids, + mentioned_surfsense_doc_ids=mentioned_surfsense_doc_ids, + mentioned_folder_ids=mentioned_folder_ids, + ) + agent_user_query = substitute_in_text(user_query, resolved.token_to_path) + accepted_folder_ids = resolved.mentioned_folder_ids + return agent_user_query, accepted_folder_ids + + +def _render_query_with_context( + *, + agent_user_query: str, + mentioned_surfsense_docs: list[SurfsenseDocsDocument], + recent_reports: list[Report], +) -> str: + """Prepend surfsense-docs + recent-reports XML blocks to the user query.""" + context_parts: list[str] = [] + + if mentioned_surfsense_docs: + context_parts.append( + format_mentioned_surfsense_docs_as_context(mentioned_surfsense_docs) + ) + + if recent_reports: + report_lines: list[str] = [] + for r in recent_reports: + report_lines.append( + f' - report_id={r.id}, title="{r.title}", ' + f'style="{r.report_style or "detailed"}"' + ) + reports_listing = "\n".join(report_lines) + context_parts.append( + "\n" + "Previously generated reports in this conversation:\n" + f"{reports_listing}\n\n" + "If the user wants to MODIFY, REVISE, UPDATE, or ADD to one of " + "these reports, set parent_report_id to the relevant report_id above.\n" + "If the user wants a completely NEW report on a different topic, " + "leave parent_report_id unset.\n" + "" + ) + + if context_parts: + context = "\n\n".join(context_parts) + return f"{context}\n\n{agent_user_query}" + + return agent_user_query diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py new file mode 100644 index 000000000..ff5a56eec --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py @@ -0,0 +1,62 @@ +"""Vision-capability gate for image-bearing turns. + +Capability safety net for explicit (non-auto-pin) selections: a turn carrying +user-uploaded images cannot be routed to a chat config that LiteLLM's +authoritative model map *explicitly* marks as text-only (``supports_vision`` +set to False). The check is intentionally narrow — it only fires when LiteLLM +is *certain* the model can't accept image input; unknown / unmapped / +vision-capable models pass through. + +Without this guard a known-text-only model would 404 at the provider with +``"No endpoints found that support image input"``, surfacing as an opaque +``SERVER_ERROR`` SSE chunk; failing here lets us return a friendly message that +tells the user what to change. +""" + +from __future__ import annotations + +from app.agents.new_chat.llm_config import AgentConfig +from app.observability import otel as ot + + +def check_image_input_capability( + *, + user_image_data_urls: list[str] | None, + agent_config: AgentConfig | None, +) -> tuple[str, str] | None: + """Return ``(user_message, error_code)`` when the gate trips, else ``None``. + + The caller emits one terminal-error SSE frame on a non-``None`` return. + """ + if not (user_image_data_urls and agent_config is not None): + return None + + from app.services.provider_capabilities import is_known_text_only_chat_model + + agent_litellm_params = agent_config.litellm_params or {} + agent_base_model = ( + agent_litellm_params.get("base_model") + if isinstance(agent_litellm_params, dict) + else None + ) + if not is_known_text_only_chat_model( + provider=agent_config.provider, + model_name=agent_config.model_name, + base_model=agent_base_model, + custom_provider=agent_config.custom_provider, + ): + return None + + model_label = agent_config.config_name or agent_config.model_name or "model" + ot.add_event( + "quota.denied", {"quota.code": "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT"} + ) + return ( + ( + f"The selected model ({model_label}) does not support " + "image input. Switch to a vision-capable model " + "(e.g. GPT-4o, Claude, Gemini) or remove the image " + "attachment and try again." + ), + "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT", + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/persistence_spawn.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/persistence_spawn.py new file mode 100644 index 000000000..9ea5d2ad6 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/persistence_spawn.py @@ -0,0 +1,129 @@ +"""Concurrent persistence tasks spawned right after the initial validation gate. + +These run *during* the rest of the pre-stream setup so we don't serialize +their latency against agent construction. Awaiting them at the SSE message-id +yield sites preserves the ghost-thread protection (the user-row INSERT must +succeed before any LLM streaming begins). + +The ``set_ai_responding`` flag flip runs fully fire-and-forget on its own +shielded session — failures only delay the "AI is responding…" UI flag, not +the response itself. +""" + +from __future__ import annotations + +import asyncio +import logging +from typing import Any +from uuid import UUID + +from app.db import shielded_async_session +from app.services.chat_session_state_service import set_ai_responding +from app.tasks.chat.persistence import ( + persist_assistant_shell, + persist_user_turn, +) + +logger = logging.getLogger(__name__) + + +def spawn_set_ai_responding_bg( + *, + chat_id: int, + user_id: str | None, + background_tasks: set[asyncio.Task[Any]], +) -> None: + """Fire-and-forget: flip the per-thread AI-responding flag on its own session. + + Errors are swallowed and logged — the worst case is a stale UI flag, which + is preferable to delaying the SSE stream behind a flag write. + """ + if not user_id: + return + + async def _bg_set_ai_responding() -> None: + try: + async with shielded_async_session() as s: + await set_ai_responding(s, chat_id, UUID(user_id)) + except Exception: + logger.warning( + "set_ai_responding failed (chat_id=%s)", + chat_id, + exc_info=True, + ) + + t = asyncio.create_task(_bg_set_ai_responding()) + background_tasks.add(t) + t.add_done_callback(background_tasks.discard) + + +def spawn_persist_user_task( + *, + chat_id: int, + user_id: str | None, + turn_id: str, + user_query: str, + user_image_data_urls: list[str] | None, + mentioned_documents: list[dict[str, Any]] | None, + background_tasks: set[asyncio.Task[Any]], +) -> asyncio.Task[int | None]: + """Spawn the user-row INSERT; await at the user-message-id yield site.""" + task = asyncio.create_task( + persist_user_turn( + chat_id=chat_id, + user_id=user_id, + turn_id=turn_id, + user_query=user_query, + user_image_data_urls=user_image_data_urls, + mentioned_documents=mentioned_documents, + ) + ) + background_tasks.add(task) + task.add_done_callback(background_tasks.discard) + return task + + +def spawn_persist_assistant_shell_task( + *, + chat_id: int, + user_id: str | None, + turn_id: str, + background_tasks: set[asyncio.Task[Any]], +) -> asyncio.Task[int | None]: + """Spawn the assistant-shell INSERT; await at the assistant-message-id yield site.""" + task = asyncio.create_task( + persist_assistant_shell( + chat_id=chat_id, + user_id=user_id, + turn_id=turn_id, + ) + ) + background_tasks.add(task) + task.add_done_callback(background_tasks.discard) + return task + + +async def await_persist_task( + task: asyncio.Task[int | None] | None, + *, + chat_id: int, + turn_id: str, + log_label: str, +) -> int | None: + """Join a spawned persistence task with ``shield`` + uniform error handling. + + ``shield`` keeps the DB write alive if the SSE generator is cancelled by + client disconnect mid-await. Returns ``None`` on failure; the caller + abort-paths the turn with a friendly error SSE. + """ + if task is None: + return None + try: + return await asyncio.shield(task) + except asyncio.CancelledError: + raise + except Exception: + logger.exception( + "%s failed (chat_id=%s, turn_id=%s)", log_label, chat_id, turn_id + ) + return None diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py new file mode 100644 index 000000000..1f11be1fe --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py @@ -0,0 +1,38 @@ +"""Build the per-invocation ``SurfSenseContextSchema`` for a new-chat turn. + +Carries the per-turn read inputs that middlewares read via +``runtime.context.*`` instead of from their ``__init__`` closures, so the same +compiled-agent instance can serve multiple turns with different +mention lists / request ids / turn ids without rebuilding the graph. +""" + +from __future__ import annotations + +from app.agents.new_chat.context import SurfSenseContextSchema + + +def build_new_chat_runtime_context( + *, + search_space_id: int, + mentioned_document_ids: list[int] | None, + accepted_folder_ids: list[int], + mentioned_folder_ids: list[int] | None, + request_id: str | None, + turn_id: str, +) -> SurfSenseContextSchema: + """``mentioned_document_ids`` is consumed by ``KnowledgePriorityMiddleware``. + + ``accepted_folder_ids`` (post-resolve) wins over the raw + ``mentioned_folder_ids`` from the request: the resolver drops chips that + pointed at deleted folders or folders the caller can't see, so middlewares + only get authorized ids. + """ + return SurfSenseContextSchema( + search_space_id=search_space_id, + mentioned_document_ids=list(mentioned_document_ids or []), + mentioned_folder_ids=list( + accepted_folder_ids or mentioned_folder_ids or [] + ), + request_id=request_id, + turn_id=turn_id, + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py new file mode 100644 index 000000000..11312110f --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py @@ -0,0 +1,237 @@ +"""Background thread-title generation (first-response only). + +The first assistant response in a thread gets a short auto-generated title +inserted into ``new_chat_threads.title``. We: + + 1. Spawn the generation as an ``asyncio.Task`` so it runs in parallel with + the agent stream (no extra TTFT). + 2. Probe inside the task (on its own shielded session) whether this is + actually the first response — newer turns short-circuit to ``None``. + 3. Inject the resulting ``thread-title-update`` SSE frame on the first agent + event after the task completes (mid-stream interlock), or right before + the finish frames (post-stream join) if the task hadn't finished yet. + +Usage tokens come directly off the response (LiteLLM's async callback fires +via fire-and-forget ``create_task``, so the ``TokenTrackingCallback`` would +run too late). We also blank the per-task accumulator so the late callback +doesn't double-count. +""" + +from __future__ import annotations + +import asyncio +import logging +from typing import TYPE_CHECKING, Any + +from sqlalchemy.future import select + +from app.db import NewChatMessage, NewChatThread, shielded_async_session +from app.prompts import TITLE_GENERATION_PROMPT +from app.services.new_streaming_service import VercelStreamingService + +if TYPE_CHECKING: + from app.agents.new_chat.llm_config import AgentConfig + from app.services.token_tracking_service import TokenAccumulator + + +logger = logging.getLogger(__name__) + + +def spawn_title_task( + *, + chat_id: int, + user_query: str, + user_image_data_urls: list[str] | None, + assistant_message_id: int | None, + llm: Any, + agent_config: AgentConfig | None, +) -> asyncio.Task[tuple[str | None, dict | None]] | None: + """Spawn ``_generate_title``; returns ``None`` when prerequisites aren't met. + + Title gen is gated on a real ``assistant_message_id`` so a stream that + aborts before persistence can never leave a thread with a title and no + anchoring rows. + """ + if assistant_message_id is None: + return None + return asyncio.create_task( + _generate_title( + chat_id=chat_id, + user_query=user_query, + user_image_data_urls=user_image_data_urls, + assistant_message_id=assistant_message_id, + llm=llm, + agent_config=agent_config, + ) + ) + + +async def _generate_title( + *, + chat_id: int, + user_query: str, + user_image_data_urls: list[str] | None, + assistant_message_id: int, + llm: Any, + agent_config: AgentConfig | None, +) -> tuple[str | None, dict | None]: + """Probe is-first-response, then call ``acompletion``. Returns ``(title, usage)``.""" + try: + from litellm import acompletion + + from app.services.llm_router_service import LLMRouterService + from app.services.provider_api_base import resolve_api_base + from app.services.token_tracking_service import _turn_accumulator + + # Excludes this turn's own assistant row (pre-written by + # ``persist_assistant_shell``) — without the ``!=`` filter the gate + # would false-negative on every turn after the first. + try: + async with shielded_async_session() as probe_session: + probe_result = await probe_session.execute( + select(NewChatMessage.id) + .filter( + NewChatMessage.thread_id == chat_id, + NewChatMessage.role == "assistant", + NewChatMessage.id != assistant_message_id, + ) + .limit(1) + ) + is_first_response = probe_result.scalars().first() is None + except Exception: + logger.warning( + "[TitleGen] first-response probe failed (chat_id=%s)", + chat_id, + exc_info=True, + ) + return None, None + + if not is_first_response: + return None, None + + _turn_accumulator.set(None) + + title_seed = user_query.strip() or ( + f"[{len(user_image_data_urls or [])} image(s)]" + if user_image_data_urls + else "" + ) + prompt = TITLE_GENERATION_PROMPT.replace( + "{user_query}", title_seed[:500] or "(message)" + ) + messages = [{"role": "user", "content": prompt}] + + if getattr(llm, "model", None) == "auto": + router = LLMRouterService.get_router() + response = await router.acompletion(model="auto", messages=messages) + else: + # Apply the same ``api_base`` cascade chat / vision / image-gen + # call sites use so we never inherit ``litellm.api_base`` + # (commonly set by ``AZURE_OPENAI_ENDPOINT``) when the chat + # config itself ships an empty ``api_base``. Without this the + # title-gen on an OpenRouter chat config would 404 against the + # inherited Azure endpoint — see ``provider_api_base`` for the + # same bug repro on the image-gen / vision paths. + raw_model = getattr(llm, "model", "") or "" + provider_prefix = ( + raw_model.split("/", 1)[0] if "/" in raw_model else None + ) + provider_value = ( + agent_config.provider if agent_config is not None else None + ) + title_api_base = resolve_api_base( + provider=provider_value, + provider_prefix=provider_prefix, + config_api_base=getattr(llm, "api_base", None), + ) + response = await acompletion( + model=raw_model, + messages=messages, + api_key=getattr(llm, "api_key", None), + api_base=title_api_base, + ) + + usage_info = None + usage = getattr(response, "usage", None) + if usage: + raw_model = getattr(llm, "model", "") or "" + model_name = ( + raw_model.split("/", 1)[-1] + if "/" in raw_model + else (raw_model or response.model or "unknown") + ) + usage_info = { + "model": model_name, + "prompt_tokens": getattr(usage, "prompt_tokens", 0) or 0, + "completion_tokens": getattr(usage, "completion_tokens", 0) or 0, + "total_tokens": getattr(usage, "total_tokens", 0) or 0, + } + + raw_title = response.choices[0].message.content.strip() + if raw_title and len(raw_title) <= 100: + return raw_title.strip("\"'"), usage_info + return None, usage_info + except Exception: + logger.exception("[TitleGen] _generate_title failed") + return None, None + + +async def maybe_emit_title_update( + *, + title_task: asyncio.Task[tuple[str | None, dict | None]] | None, + title_emitted: bool, + chat_id: int, + accumulator: TokenAccumulator, + streaming_service: VercelStreamingService, +): + """Inject one ``thread-title-update`` SSE if the task completed. + + Yields the SSE frame (when applicable). Returns nothing; the orchestrator + flips ``title_emitted`` itself after iterating so we don't fight Python's + nonlocal-in-generator semantics. + """ + if title_task is None or title_emitted or not title_task.done(): + return + generated_title, title_usage = title_task.result() + if title_usage: + accumulator.add(**title_usage) + if generated_title: + async with shielded_async_session() as title_session: + title_thread_result = await title_session.execute( + select(NewChatThread).filter(NewChatThread.id == chat_id) + ) + title_thread = title_thread_result.scalars().first() + if title_thread: + title_thread.title = generated_title + await title_session.commit() + yield streaming_service.format_thread_title_update(chat_id, generated_title) + + +async def await_pending_title_update( + *, + title_task: asyncio.Task[tuple[str | None, dict | None]] | None, + title_emitted: bool, + chat_id: int, + accumulator: TokenAccumulator, + streaming_service: VercelStreamingService, +): + """If the task hadn't completed during the stream, await it now and emit. + + Used right before the finish frames in the success path. Mirror of + ``maybe_emit_title_update`` but unconditionally awaits. + """ + if title_task is None or title_emitted: + return + generated_title, title_usage = await title_task + if title_usage: + accumulator.add(**title_usage) + if generated_title: + async with shielded_async_session() as title_session: + title_thread_result = await title_session.execute( + select(NewChatThread).filter(NewChatThread.id == chat_id) + ) + title_thread = title_thread_result.scalars().first() + if title_thread: + title_thread.title = generated_title + await title_session.commit() + yield streaming_service.format_thread_title_update(chat_id, generated_title) From b2a08885887c995034bdb759a43105f326737e47 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:49:55 +0200 Subject: [PATCH 062/198] refactor(chat): add streaming/flows/new_chat/orchestrator.stream_new_chat Slim composition root for the new-chat streaming flow. Sequences: 1. validate inputs and load the LLM bundle (negative id => YAML) 2. open the OTEL chat_request span; set agent_mode tag 3. spawn the four pre-stream DB writes (set-ai-responding, persist user turn, persist assistant shell, first-assistant probe) 4. reserve premium quota (with free-fallback retry on denial) 5. build connector + checkpointer + agent + input_state 6. emit first frames (message-start, step-start, initial thinking step) 7. spawn the background title generator 8. run the shared stream_loop with a flow-local _recover closure that reroutes to the next auto-pin config on provider 429s 9. finalize: emit terminal title/token frames, shielded assistant finalize, release-or-finalize premium quota, close session, GC, record OTEL outcome Public entry-point flows/new_chat/__init__ re-exports stream_new_chat. Existing wiring (routes, tests) still imports the legacy function from app.tasks.chat.stream_new_chat. Cutover is a later commit. --- .../chat/streaming/flows/new_chat/__init__.py | 12 + .../streaming/flows/new_chat/orchestrator.py | 868 ++++++++++++++++++ 2 files changed, 880 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/__init__.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/__init__.py new file mode 100644 index 000000000..566d5e0d9 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/__init__.py @@ -0,0 +1,12 @@ +"""New-chat streaming flow. + +The public entry point ``stream_new_chat`` is the slim coroutine in +``orchestrator.py`` that composes the per-concern modules in this folder and +the building blocks under ``flows/shared/``. +""" + +from __future__ import annotations + +from app.tasks.chat.streaming.flows.new_chat.orchestrator import stream_new_chat + +__all__ = ["stream_new_chat"] diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py new file mode 100644 index 000000000..bca72b5ea --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py @@ -0,0 +1,868 @@ +"""``stream_new_chat`` — public entry point for a fresh chat turn. + +Slim composition layer over the per-concern modules in this folder and the +building blocks under ``flows/shared/``. Each phase corresponds to a numbered +block in the surrounding code so the on-the-wire ordering stays explicit: + + 1. Validation / config — auto-pin, LLM bundle, capability, premium reserve. + 2. Concurrent persistence + pre-stream setup — spawn DB writes, build the + connector, fetch the checkpointer, build the agent. + 3. Input assembly — history bootstrap, mentions, surfsense docs, reports. + 4. First SSE frames — message_start, start_step, turn-info, turn-status. + 5. Persistence join + message-id frames (ghost-thread protection). + 6. Initial thinking step + title task + runtime context. + 7. Stream loop with in-stream rate-limit recovery + mid-stream title emit. + 8. Finalize — premium debit, token-usage SSE, finish frames. + 9. Exception branch — classify, emit terminal error, finish frames. + 10. Finally — premium release, session close, assistant finalize, GC, span. +""" + +from __future__ import annotations + +import asyncio +import contextlib +import logging +import time +from collections.abc import AsyncGenerator +from functools import partial +from typing import Any, Literal + +import anyio + +from app.agents.multi_agent_chat import create_multi_agent_chat_deep_agent +from app.agents.new_chat.chat_deepagent import create_surfsense_deep_agent +from app.agents.new_chat.filesystem_selection import FilesystemMode, FilesystemSelection +from app.agents.new_chat.middleware.busy_mutex import end_turn +from app.config import config as _app_config +from app.db import ChatVisibility, async_session_maker +from app.observability import otel as ot +from app.services.new_streaming_service import VercelStreamingService +from app.tasks.chat.content_builder import AssistantContentBuilder +from app.tasks.chat.streaming.agent.builder import build_main_agent_for_thread +from app.tasks.chat.streaming.contract.file_contract import log_file_contract +from app.tasks.chat.streaming.errors.emitter import emit_stream_terminal_error +from app.tasks.chat.streaming.flows.new_chat.auto_pin import resolve_initial_auto_pin +from app.tasks.chat.streaming.flows.new_chat.initial_thinking_step import ( + build_initial_thinking_step, + iter_initial_thinking_step_frame, +) +from app.tasks.chat.streaming.flows.new_chat.input_state import ( + build_new_chat_input_state, +) +from app.tasks.chat.streaming.flows.new_chat.llm_capability import ( + check_image_input_capability, +) +from app.tasks.chat.streaming.flows.new_chat.persistence_spawn import ( + await_persist_task, + spawn_persist_assistant_shell_task, + spawn_persist_user_task, + spawn_set_ai_responding_bg, +) +from app.tasks.chat.streaming.flows.new_chat.runtime_context import ( + build_new_chat_runtime_context, +) +from app.tasks.chat.streaming.flows.new_chat.title_gen import ( + await_pending_title_update, + maybe_emit_title_update, + spawn_title_task, +) +from app.tasks.chat.streaming.flows.shared.assistant_finalize import ( + finalize_assistant_message, +) +from app.tasks.chat.streaming.flows.shared.finalize_emit import iter_token_usage_frame +from app.tasks.chat.streaming.flows.shared.finally_cleanup import ( + close_session_and_clear_ai_responding, + run_gc_pass, +) +from app.tasks.chat.streaming.flows.shared.first_frames import ( + iter_final_frames, + iter_initial_frames, +) +from app.tasks.chat.streaming.flows.shared.llm_bundle import load_llm_bundle +from app.tasks.chat.streaming.flows.shared.pre_stream_setup import ( + get_chat_checkpointer, + setup_connector_and_firecrawl, +) +from app.tasks.chat.streaming.flows.shared.premium_quota import ( + PremiumReservation, + finalize_premium, + needs_premium_quota, + release_premium, + reserve_premium, +) +from app.tasks.chat.streaming.flows.shared.rate_limit_recovery import ( + can_recover_provider_rate_limit, + log_rate_limit_recovered, + reroute_to_next_auto_pin, +) +from app.tasks.chat.streaming.flows.shared.span import ( + close_chat_request_span, + open_chat_request_span, + set_agent_mode, +) +from app.tasks.chat.streaming.flows.shared.stream_loop import run_stream_loop +from app.tasks.chat.streaming.flows.shared.terminal_error import ( + handle_terminal_exception, +) +from app.tasks.chat.streaming.shared.stream_result import StreamResult +from app.utils.perf import get_perf_logger, log_system_snapshot + +logger = logging.getLogger(__name__) +_perf_log = get_perf_logger() + +# Holds spawned background tasks (set_ai_responding, persist_user, persist_asst) +# so the GC doesn't drop them before they finish. Kept at module level so it +# survives across turns within one process. +_background_tasks: set[asyncio.Task] = set() + + +async def stream_new_chat( + user_query: str, + search_space_id: int, + chat_id: int, + user_id: str | None = None, + llm_config_id: int = -1, + mentioned_document_ids: list[int] | None = None, + mentioned_surfsense_doc_ids: list[int] | None = None, + mentioned_folder_ids: list[int] | None = None, + mentioned_documents: list[dict[str, Any]] | None = None, + checkpoint_id: str | None = None, + needs_history_bootstrap: bool = False, + thread_visibility: ChatVisibility | None = None, + current_user_display_name: str | None = None, + disabled_tools: list[str] | None = None, + filesystem_selection: FilesystemSelection | None = None, + request_id: str | None = None, + user_image_data_urls: list[str] | None = None, + flow: Literal["new", "regenerate"] = "new", +) -> AsyncGenerator[str, None]: + """Stream a new chat turn using the SurfSense deep agent. + + Uses the Vercel AI SDK Data Stream Protocol (SSE). ``chat_id`` is the + LangGraph thread id (durable conversation memory via the checkpointer). + Manages its own database session so cleanup runs even when Starlette + cancels the task on client disconnect. + """ + streaming_service = VercelStreamingService() + stream_result = StreamResult() + _t_total = time.perf_counter() + fs_mode = filesystem_selection.mode.value if filesystem_selection else "cloud" + fs_platform = ( + filesystem_selection.client_platform.value if filesystem_selection else "web" + ) + stream_result.request_id = request_id + stream_result.turn_id = f"{chat_id}:{int(time.time() * 1000)}" + stream_result.filesystem_mode = fs_mode + stream_result.client_platform = fs_platform + + chat_agent_mode = "unknown" + chat_outcome = "success" + chat_error_category: str | None = None + chat_span_cm, chat_span = open_chat_request_span( + chat_id=chat_id, + search_space_id=search_space_id, + flow=flow, + request_id=request_id, + turn_id=stream_result.turn_id, + filesystem_mode=fs_mode, + client_platform=fs_platform, + agent_mode=chat_agent_mode, + ) + log_file_contract("turn_start", stream_result) + _perf_log.info( + "[stream_new_chat] filesystem_mode=%s client_platform=%s", + fs_mode, + fs_platform, + ) + log_system_snapshot("stream_new_chat_START") + + from app.services.token_tracking_service import start_turn + + accumulator = start_turn() + + premium_reservation: PremiumReservation | None = None + busy_error_raised = False + + emit_stream_error = partial( + emit_stream_terminal_error, + streaming_service=streaming_service, + flow=flow, + request_id=request_id, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + ) + + session = async_session_maker() + # Declared at function scope so SSE-yield join points and the finally + # clause see them on every exit path. + persist_user_task: asyncio.Task[int | None] | None = None + persist_asst_task: asyncio.Task[int | None] | None = None + try: + spawn_set_ai_responding_bg( + chat_id=chat_id, user_id=user_id, background_tasks=_background_tasks + ) + + # --- Block 1: LLM config + capability --- + + requested_llm_config_id = llm_config_id + requires_image_input = bool(user_image_data_urls) + + _t0 = time.perf_counter() + pin_result = await resolve_initial_auto_pin( + session, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + selected_llm_config_id=llm_config_id, + requires_image_input=requires_image_input, + requested_llm_config_id=requested_llm_config_id, + ) + if pin_result.error is not None: + message, error_code, error_kind = pin_result.error + yield emit_stream_error( + message=message, error_kind=error_kind, error_code=error_code + ) + yield streaming_service.format_done() + return + llm_config_id = pin_result.llm_config_id # type: ignore[assignment] + + llm, agent_config, llm_load_error = await load_llm_bundle( + session, config_id=llm_config_id, search_space_id=search_space_id + ) + if llm_load_error: + yield emit_stream_error( + message=llm_load_error, + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + _perf_log.info( + "[stream_new_chat] LLM config loaded in %.3fs (config_id=%s)", + time.perf_counter() - _t0, + llm_config_id, + ) + + capability_error = check_image_input_capability( + user_image_data_urls=user_image_data_urls, agent_config=agent_config + ) + if capability_error is not None: + message, error_code = capability_error + yield emit_stream_error( + message=message, + error_kind="user_error", + error_code=error_code, + ) + yield streaming_service.format_done() + return + + if needs_premium_quota(agent_config, user_id): + premium_reservation = await reserve_premium( + agent_config=agent_config, user_id=user_id # type: ignore[arg-type] + ) + if not premium_reservation.allowed: + ot.add_event("quota.denied", {"quota.code": "PREMIUM_QUOTA_EXHAUSTED"}) + if requested_llm_config_id == 0: + pin_fallback = await resolve_initial_auto_pin( + session, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + selected_llm_config_id=0, + requires_image_input=requires_image_input, + requested_llm_config_id=requested_llm_config_id, + ) + if pin_fallback.error is not None: + message, error_code, error_kind = pin_fallback.error + yield emit_stream_error( + message=message, + error_kind=error_kind, + error_code=error_code, + ) + yield streaming_service.format_done() + return + llm_config_id = pin_fallback.llm_config_id # type: ignore[assignment] + ot.add_event( + "model.repin", + { + "repin.reason": "premium_quota_exhausted", + "repin.to_config_id": llm_config_id, + }, + ) + llm, agent_config, llm_load_error = await load_llm_bundle( + session, + config_id=llm_config_id, + search_space_id=search_space_id, + ) + if llm_load_error: + yield emit_stream_error( + message=llm_load_error, + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + premium_reservation = None + # Re-route to free fallback logged via the structured + # stream-error logger so cost/analytics see the auto-switch. + from app.tasks.chat.streaming.errors.classifier import ( + log_chat_stream_error, + ) + + log_chat_stream_error( + flow=flow, + error_kind="premium_quota_exhausted", + error_code="PREMIUM_QUOTA_EXHAUSTED", + severity="info", + is_expected=True, + request_id=request_id, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + message=( + "Premium quota exhausted on pinned model; " + "auto-fallback switched to a free model" + ), + extra={ + "fallback_config_id": llm_config_id, + "auto_fallback": True, + }, + ) + else: + yield emit_stream_error( + message=( + "Buy more tokens to continue with this model, or " + "switch to a free model" + ), + error_kind="premium_quota_exhausted", + error_code="PREMIUM_QUOTA_EXHAUSTED", + severity="info", + is_expected=True, + extra={ + "resolved_config_id": llm_config_id, + "auto_fallback": False, + }, + ) + yield streaming_service.format_done() + return + + if not llm: + yield emit_stream_error( + message="Failed to create LLM instance", + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + + # --- Block 2: Spawn concurrent persistence; build pre-stream setup --- + + persist_user_task = spawn_persist_user_task( + chat_id=chat_id, + user_id=user_id, + turn_id=stream_result.turn_id, + user_query=user_query, + user_image_data_urls=user_image_data_urls, + mentioned_documents=mentioned_documents, + background_tasks=_background_tasks, + ) + persist_asst_task = spawn_persist_assistant_shell_task( + chat_id=chat_id, + user_id=user_id, + turn_id=stream_result.turn_id, + background_tasks=_background_tasks, + ) + + _t0 = time.perf_counter() + connector_service, firecrawl_api_key = await setup_connector_and_firecrawl( + session, search_space_id=search_space_id + ) + _perf_log.info( + "[stream_new_chat] Connector service + firecrawl key in %.3fs", + time.perf_counter() - _t0, + ) + + _t0 = time.perf_counter() + checkpointer = await get_chat_checkpointer() + _perf_log.info( + "[stream_new_chat] Checkpointer ready in %.3fs", time.perf_counter() - _t0 + ) + + visibility = thread_visibility or ChatVisibility.PRIVATE + use_multi_agent = bool(_app_config.MULTI_AGENT_CHAT_ENABLED) + chat_agent_mode = "multi" if use_multi_agent else "single" + set_agent_mode(chat_span, chat_agent_mode) + + _t0 = time.perf_counter() + agent_factory = ( + create_multi_agent_chat_deep_agent + if use_multi_agent + else create_surfsense_deep_agent + ) + # Build the agent inline. Provider 429s surface through the in-stream + # recovery loop below, which repins the thread to an eligible + # alternative config and rebuilds the agent before the user sees any + # output. + agent = await build_main_agent_for_thread( + agent_factory, + llm=llm, + search_space_id=search_space_id, + db_session=session, + connector_service=connector_service, + checkpointer=checkpointer, + user_id=user_id, + thread_id=chat_id, + agent_config=agent_config, + firecrawl_api_key=firecrawl_api_key, + thread_visibility=visibility, + filesystem_selection=filesystem_selection, + disabled_tools=disabled_tools, + mentioned_document_ids=mentioned_document_ids, + ) + _perf_log.info( + "[stream_new_chat] Agent created in %.3fs", time.perf_counter() - _t0 + ) + + # --- Block 3: Input assembly --- + + _t0 = time.perf_counter() + assembled = await build_new_chat_input_state( + session, + chat_id=chat_id, + search_space_id=search_space_id, + user_query=user_query, + user_image_data_urls=user_image_data_urls, + mentioned_document_ids=mentioned_document_ids, + mentioned_surfsense_doc_ids=mentioned_surfsense_doc_ids, + mentioned_folder_ids=mentioned_folder_ids, + mentioned_documents=mentioned_documents, + needs_history_bootstrap=needs_history_bootstrap, + thread_visibility=visibility, + current_user_display_name=current_user_display_name, + filesystem_mode=fs_mode, + request_id=request_id, + turn_id=stream_result.turn_id, + ) + input_state = assembled.input_state + accepted_folder_ids = assembled.accepted_folder_ids + mentioned_surfsense_docs = assembled.mentioned_surfsense_docs + _perf_log.info( + "[stream_new_chat] History bootstrap + doc/report queries in %.3fs", + time.perf_counter() - _t0, + ) + + # All pre-streaming DB reads done. Commit to release the transaction + # and its ACCESS SHARE locks so we don't block DDL (e.g. migrations) + # for the entire LLM streaming duration. Tools that need DB access + # during streaming start their own short-lived transactions (or use + # isolated sessions). + await session.commit() + # Detach heavy ORM objects (documents with chunks, reports, etc.) + # from the session identity map now that we've extracted what we + # need. Without this they accumulate in memory for the entire + # streaming duration (which can be several minutes). + session.expunge_all() + + _perf_log.info( + "[stream_new_chat] Total pre-stream setup in %.3fs (chat_id=%s)", + time.perf_counter() - _t_total, + chat_id, + ) + + configurable: dict[str, Any] = { + "thread_id": str(chat_id), + "request_id": request_id or "unknown", + "turn_id": stream_result.turn_id, + } + if checkpoint_id: + configurable["checkpoint_id"] = checkpoint_id + + config = { + "configurable": configurable, + # Effectively uncapped, matching the agent-level ``with_config`` + # default in ``chat_deepagent.create_agent`` and the unbounded + # ``while(true)`` in OpenCode's ``session/processor.ts``. Real + # circuit-breakers live in middleware (``DoomLoopMiddleware``, + # plus ``enable_tool_call_limit`` / ``enable_model_call_limit``). + # The original 25 (and our previous 80 bump) hit users on + # legitimate multi-tool plans. + "recursion_limit": 10_000, + } + + # --- Block 4: First SSE frames --- + + for sse in iter_initial_frames(streaming_service, turn_id=stream_result.turn_id): + yield sse + + # --- Block 5: Persistence join + message-id frames --- + + user_message_id = await await_persist_task( + persist_user_task, + chat_id=chat_id, + turn_id=stream_result.turn_id, + log_label="persist_user_task", + ) + if user_message_id is None: + yield emit_stream_error( + message="We couldn't save your message. Please try again in a moment.", + error_kind="server_error", + error_code="MESSAGE_PERSIST_FAILED", + ) + for sse in iter_final_frames(streaming_service): + yield sse + return + + # Emit canonical user message id BEFORE any LLM streaming so the FE + # can rename its optimistic ``msg-user-XXX`` placeholder to + # ``msg-{user_message_id}`` and unlock features gated on a real DB id + # (comments, edit-from-this-message). See B4 in the + # ``sse-based_message_id_handshake`` plan. + yield streaming_service.format_data( + "user-message-id", + {"message_id": user_message_id, "turn_id": stream_result.turn_id}, + ) + + assistant_message_id = await await_persist_task( + persist_asst_task, + chat_id=chat_id, + turn_id=stream_result.turn_id, + log_label="persist_asst_task", + ) + if assistant_message_id is None: + # Genuine DB failure — abort the turn rather than stream into a + # void. The user row is already persisted so the legacy + # ghost-thread gate isn't reopened. + yield emit_stream_error( + message=( + "We couldn't initialize the assistant message. Please try again." + ), + error_kind="server_error", + error_code="MESSAGE_PERSIST_FAILED", + ) + for sse in iter_final_frames(streaming_service): + yield sse + return + + yield streaming_service.format_data( + "assistant-message-id", + {"message_id": assistant_message_id, "turn_id": stream_result.turn_id}, + ) + + stream_result.assistant_message_id = assistant_message_id + stream_result.content_builder = AssistantContentBuilder() + + # --- Block 6: Initial thinking step + title task + runtime context --- + + initial_step = build_initial_thinking_step( + user_query=user_query, + user_image_data_urls=user_image_data_urls, + mentioned_surfsense_docs=mentioned_surfsense_docs, + ) + for sse in iter_initial_thinking_step_frame( + initial_step, + streaming_service=streaming_service, + content_builder=stream_result.content_builder, + ): + yield sse + + initial_step_id = initial_step.step_id + initial_step_title = initial_step.title + initial_step_items = initial_step.items + # Drop the heavy ORM objects + the container that holds them so they + # aren't retained for the entire streaming duration. ``input_state`` + # already carries the langchain_messages list independently. + del assembled, mentioned_surfsense_docs + + title_task = spawn_title_task( + chat_id=chat_id, + user_query=user_query, + user_image_data_urls=user_image_data_urls, + assistant_message_id=assistant_message_id, + llm=llm, + agent_config=agent_config, + ) + title_emitted = False + + runtime_context = build_new_chat_runtime_context( + search_space_id=search_space_id, + mentioned_document_ids=mentioned_document_ids, + accepted_folder_ids=accepted_folder_ids, + mentioned_folder_ids=mentioned_folder_ids, + request_id=request_id, + turn_id=stream_result.turn_id, + ) + + # --- Block 7: Stream loop --- + + _t_stream_start = time.perf_counter() + runtime_rate_limit_recovered = False + + def _on_first_event() -> None: + _perf_log.info( + "[stream_new_chat] First agent event in %.3fs (time since stream start), " + "%.3fs (total since request start) (chat_id=%s)", + time.perf_counter() - _t_stream_start, + time.perf_counter() - _t_total, + chat_id, + ) + + async def _recover(exc: BaseException, first_event_seen: bool): + nonlocal llm_config_id, llm, agent_config, runtime_rate_limit_recovered + nonlocal title_task + if not can_recover_provider_rate_limit( + exc, + first_event_seen=first_event_seen, + runtime_rate_limit_recovered=runtime_rate_limit_recovered, + requested_llm_config_id=requested_llm_config_id, + current_llm_config_id=llm_config_id, + ): + return None + runtime_rate_limit_recovered = True + previous_config_id = llm_config_id + llm_config_id = await reroute_to_next_auto_pin( + session, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + current_llm_config_id=llm_config_id, + requires_image_input=requires_image_input, + ) + new_llm, new_agent_config, llm_load_err = await load_llm_bundle( + session, config_id=llm_config_id, search_space_id=search_space_id + ) + if llm_load_err: + # Re-raise the original so the terminal-error path classifies + # it correctly (don't swallow as "config load error"). + return None + llm = new_llm + agent_config = new_agent_config + + # Title gen used the initial llm object. After a runtime repin we + # keep the stream focused on response recovery and skip title gen + # for this turn. + if title_task is not None and not title_task.done(): + title_task.cancel() + title_task = None + + _t_rebuild = time.perf_counter() + new_agent = await build_main_agent_for_thread( + agent_factory, + llm=llm, + search_space_id=search_space_id, + db_session=session, + connector_service=connector_service, + checkpointer=checkpointer, + user_id=user_id, + thread_id=chat_id, + agent_config=agent_config, + firecrawl_api_key=firecrawl_api_key, + thread_visibility=visibility, + filesystem_selection=filesystem_selection, + disabled_tools=disabled_tools, + mentioned_document_ids=mentioned_document_ids, + ) + _perf_log.info( + "[stream_new_chat] Runtime rate-limit recovery repinned " + "config_id=%s -> %s and rebuilt agent in %.3fs", + previous_config_id, + llm_config_id, + time.perf_counter() - _t_rebuild, + ) + log_rate_limit_recovered( + flow=flow, + request_id=request_id, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + previous_config_id=previous_config_id, + new_config_id=llm_config_id, + ) + return new_agent + + async for sse in run_stream_loop( + agent=agent, + streaming_service=streaming_service, + config=config, + input_data=input_state, + stream_result=stream_result, + step_prefix="thinking", + initial_step_id=initial_step_id, + initial_step_title=initial_step_title, + initial_step_items=initial_step_items, + fallback_commit_search_space_id=search_space_id, + fallback_commit_created_by_id=user_id, + fallback_commit_filesystem_mode=( + filesystem_selection.mode if filesystem_selection else FilesystemMode.CLOUD + ), + fallback_commit_thread_id=chat_id, + runtime_context=runtime_context, + content_builder=stream_result.content_builder, + recover=_recover, + on_first_event=_on_first_event, + ): + yield sse + # Inject the title update mid-stream as soon as the background + # task finishes; gated so we emit at most once. + async for title_sse in maybe_emit_title_update( + title_task=title_task, + title_emitted=title_emitted, + chat_id=chat_id, + accumulator=accumulator, + streaming_service=streaming_service, + ): + yield title_sse + title_emitted = True + # Account for the case where the task completed but produced no + # title — flip the flag anyway so we don't keep checking it. + if ( + title_task is not None + and title_task.done() + and not title_emitted + ): + title_emitted = True + + _perf_log.info( + "[stream_new_chat] Agent stream completed in %.3fs (chat_id=%s)", + time.perf_counter() - _t_stream_start, + chat_id, + ) + log_system_snapshot("stream_new_chat_END") + + # --- Block 8: Finalize --- + + if stream_result.is_interrupted: + ot.add_event("chat.interrupted", {"chat.flow": flow}) + if title_task is not None and not title_task.done(): + title_task.cancel() + for sse in iter_token_usage_frame( + streaming_service, + accumulator=accumulator, + log_label="interrupted new_chat", + ): + yield sse + yield streaming_service.format_finish_step() + yield streaming_service.format_finish() + yield streaming_service.format_done() + return + + async for title_sse in await_pending_title_update( + title_task=title_task, + title_emitted=title_emitted, + chat_id=chat_id, + accumulator=accumulator, + streaming_service=streaming_service, + ): + yield title_sse + + # Finalize premium credit debit with the actual provider cost reported + # by LiteLLM, summed across every call in the turn. Mirrors the + # pre-cost behaviour of "premium turn → all calls count" so free + # sub-agent calls during a premium turn still contribute to the bill + # (they're $0 in practice anyway). + if premium_reservation is not None and user_id: + await finalize_premium( + reservation=premium_reservation, + user_id=user_id, + accumulator=accumulator, + ) + premium_reservation = None + + for sse in iter_token_usage_frame( + streaming_service, accumulator=accumulator, log_label="normal new_chat" + ): + yield sse + + for sse in iter_final_frames(streaming_service): + yield sse + + except Exception as exc: + frames, summary = handle_terminal_exception( + exc, + flow=flow, + flow_label="chat", + log_prefix="stream_new_chat", + streaming_service=streaming_service, + request_id=request_id, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + chat_span=chat_span, + ) + if summary["busy_error_raised"]: + busy_error_raised = True + chat_outcome = summary["chat_outcome"] + chat_error_category = summary["chat_error_category"] + for sse in frames: + yield sse + + finally: + # Shield the ENTIRE async cleanup from anyio cancel-scope cancellation. + # Starlette's BaseHTTPMiddleware uses anyio task groups; on client + # disconnect, it cancels the scope with level-triggered cancellation + # — every unshielded ``await`` would raise CancelledError immediately. + # Without this the very first ``await`` (session.rollback) would + # raise, ``except Exception`` wouldn't catch it (CancelledError is a + # BaseException), and the rest of cleanup — including session.close() + # — would never run. + with anyio.CancelScope(shield=True): + # Authoritative fallback cleanup for lock/cancel state. Middleware + # teardown can be skipped on some client-abort paths. + end_turn(str(chat_id)) + + if premium_reservation is not None and user_id: + await release_premium( + reservation=premium_reservation, user_id=user_id + ) + + await close_session_and_clear_ai_responding(session, chat_id) + + await finalize_assistant_message( + stream_result=stream_result, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + accumulator=accumulator, + log_prefix="stream_new_chat", + ) + + # Persist any sandbox-produced files to local storage so they remain + # downloadable after the Daytona sandbox auto-deletes. + if stream_result and stream_result.sandbox_files: + with contextlib.suppress(Exception): + from app.agents.new_chat.sandbox import ( + is_sandbox_enabled, + persist_and_delete_sandbox, + ) + + if is_sandbox_enabled(): + with anyio.CancelScope(shield=True): + await persist_and_delete_sandbox( + chat_id, stream_result.sandbox_files + ) + + # ``aafter_agent`` doesn't fire on ``interrupt()`` or early bailout. + # Skip on ``BusyError`` (caller never acquired the lock). + if not busy_error_raised: + with contextlib.suppress(Exception): + end_turn(str(chat_id)) + _perf_log.info( + "[stream_new_chat] end_turn cleanup (chat_id=%s)", chat_id + ) + + # Break circular refs held by the agent graph, tools, and LLM + # wrappers so the GC can reclaim them in a single pass. + agent = llm = connector_service = None # noqa: F841 + input_state = stream_result = None # noqa: F841 + session = None # noqa: F841 + + run_gc_pass(log_prefix="stream_new_chat", chat_id=chat_id) + close_chat_request_span( + span_cm=chat_span_cm, + span=chat_span, + chat_outcome=chat_outcome, + chat_agent_mode=chat_agent_mode, + flow=flow, + chat_error_category=chat_error_category, + duration_seconds=time.perf_counter() - _t_total, + ) From 885d4acda921ff8b8c0cb10171cf63bcffbc5845 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:50:03 +0200 Subject: [PATCH 063/198] refactor(chat): add streaming/flows/resume_chat/ per-concern leaf modules Three focused modules used by the upcoming resume-chat orchestrator: * runtime_context: build_resume_chat_runtime_context assembles the SurfSenseContextSchema for a resume turn (handles empty mention lists, since resume requests do not carry fresh @-mentions). * assistant_shell: persist_resume_assistant_shell writes a fresh assistant row for the resumed turn so the post-stream finalize has a target. * resume_routing: build_resume_routing collects the pending interrupts across paused subagents and slices the flat list of ResumeDecision[] into the correct (thread, subagent) buckets so LangGraph routes each decision back to the right paused tool call. Add-only; no orchestrator yet (next commit). --- .../flows/resume_chat/assistant_shell.py | 31 +++++++++ .../flows/resume_chat/resume_routing.py | 65 +++++++++++++++++++ .../flows/resume_chat/runtime_context.py | 23 +++++++ 3 files changed, 119 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/assistant_shell.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/runtime_context.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/assistant_shell.py b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/assistant_shell.py new file mode 100644 index 000000000..2f34387f8 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/assistant_shell.py @@ -0,0 +1,31 @@ +"""Pre-write a fresh assistant row for this resume turn. + +The original (interrupted) ``stream_new_chat`` invocation already persisted +its own assistant row anchored to a different ``turn_id``; resume allocates a +new ``turn_id`` (per-request, see ``orchestrator``) so we need a separate row +keyed on the same ``(thread_id, turn_id, ASSISTANT)`` invariant. + +Idempotent against migration 141's partial unique index — recovers the +existing id on retry. + +Resume does NOT emit ``data-user-message-id``: the user row is from the +original interrupted turn (different ``turn_id``) and is never re-persisted +here. See B5 in the ``sse-based_message_id_handshake`` plan. +""" + +from __future__ import annotations + +from app.tasks.chat.persistence import persist_assistant_shell + + +async def persist_resume_assistant_shell( + *, + chat_id: int, + user_id: str | None, + turn_id: str, +) -> int | None: + return await persist_assistant_shell( + chat_id=chat_id, + user_id=user_id, + turn_id=turn_id, + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py new file mode 100644 index 000000000..300fbc9bd --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py @@ -0,0 +1,65 @@ +"""Route a flat ``decisions`` list back to the right paused subagent. + +Each pending interrupt is stamped with its originating ``tool_call_id`` (see +``checkpointed_subagent_middleware.propagation``) so the resume slicer can +re-target each ``HumanReview`` decision at the right ``tool_call_id``. + +LangGraph rejects scalar ``Command(resume=...)`` when multiple interrupts are +pending (parallel HITL); the mapped form works for the single-pause case too, +so we always use it. +""" + +from __future__ import annotations + +import logging +from dataclasses import dataclass +from typing import Any + +from app.utils.perf import get_perf_logger + +_perf_log = get_perf_logger() +logger = logging.getLogger(__name__) + + +@dataclass +class ResumeRoutingPayload: + """Resolved per-``tool_call_id`` resume slices + the lg-shaped resume map.""" + + routed_resume_value: dict[str, Any] + lg_resume_map: dict[str, Any] + + +async def build_resume_routing( + agent: Any, + *, + chat_id: int, + decisions: list[dict], +) -> ResumeRoutingPayload: + """Read parent_state, collect pending tool-calls, slice decisions, build map. + + The middleware reads its per-``tool_call_id`` resume slice from the + ``surfsense_resume_value`` configurable; parallel siblings each pop their + own entry so they never race. + """ + from app.agents.multi_agent_chat.middleware.main_agent.checkpointed_subagent_middleware.resume_routing import ( + build_lg_resume_map, + collect_pending_tool_calls, + slice_decisions_by_tool_call, + ) + + parent_state = await agent.aget_state( + {"configurable": {"thread_id": str(chat_id)}} + ) + pending = collect_pending_tool_calls(parent_state) + _perf_log.info( + "[hitl_route] resume_entry chat_id=%s decisions=%d pending_subagents=%d", + chat_id, + len(decisions), + len(pending), + ) + routed_resume_value = slice_decisions_by_tool_call(decisions, pending) + lg_resume_map = build_lg_resume_map(parent_state, routed_resume_value) + return ResumeRoutingPayload( + routed_resume_value=routed_resume_value, + lg_resume_map=lg_resume_map, + ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/runtime_context.py b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/runtime_context.py new file mode 100644 index 000000000..59d5d8ca7 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/runtime_context.py @@ -0,0 +1,23 @@ +"""Build the per-invocation ``SurfSenseContextSchema`` for a resume turn. + +Resume doesn't carry new ``mentioned_document_ids`` (those are seeded by the +original turn). We still build the context so future middleware extensions +can rely on ``runtime.context`` always being populated. +""" + +from __future__ import annotations + +from app.agents.new_chat.context import SurfSenseContextSchema + + +def build_resume_chat_runtime_context( + *, + search_space_id: int, + request_id: str | None, + turn_id: str, +) -> SurfSenseContextSchema: + return SurfSenseContextSchema( + search_space_id=search_space_id, + request_id=request_id, + turn_id=turn_id, + ) From cf0085575ca0275f134dbc04f30896b33fa5a50a Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:50:09 +0200 Subject: [PATCH 064/198] refactor(chat): add streaming/flows/resume_chat/orchestrator + flows public API Slim composition root for the resume-chat streaming flow. Mirrors the new_chat orchestrator but specialized for resumed turns: * no fresh user turn, no title generation, no image-capability gate * persists a fresh assistant shell for the resumed turn * applies build_resume_routing to dispatch user decisions to the correct paused subagent before invoking the agent * shares the same stream_loop + flow-local _recover closure for in- stream provider rate-limit recovery Also lands flows/__init__.py, which becomes the public chat-flow API: from app.tasks.chat.streaming.flows import stream_new_chat, stream_resume_chat Existing wiring (routes, contract test) still imports from the legacy app.tasks.chat.stream_new_chat module. Cutover is the next phase. --- .../tasks/chat/streaming/flows/__init__.py | 17 + .../streaming/flows/resume_chat/__init__.py | 12 + .../flows/resume_chat/orchestrator.py | 629 ++++++++++++++++++ 3 files changed, 658 insertions(+) create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/__init__.py create mode 100644 surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/__init__.py b/surfsense_backend/app/tasks/chat/streaming/flows/__init__.py new file mode 100644 index 000000000..522db2fad --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/__init__.py @@ -0,0 +1,17 @@ +"""Top-level streaming flows: ``new_chat`` and ``resume_chat`` orchestrators. + +Re-exports the public entry points so callers can write:: + + from app.tasks.chat.streaming.flows import stream_new_chat, stream_resume_chat + +The orchestrators themselves live under ``new_chat/orchestrator.py`` and +``resume_chat/orchestrator.py`` (slim composition of the per-concern modules in +each flow folder and the building blocks in ``shared/``). +""" + +from __future__ import annotations + +from app.tasks.chat.streaming.flows.new_chat import stream_new_chat +from app.tasks.chat.streaming.flows.resume_chat import stream_resume_chat + +__all__ = ["stream_new_chat", "stream_resume_chat"] diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/__init__.py b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/__init__.py new file mode 100644 index 000000000..ed0683e19 --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/__init__.py @@ -0,0 +1,12 @@ +"""Resume-chat streaming flow. + +Public entry point ``stream_resume_chat`` is the slim coroutine in +``orchestrator.py`` that composes the per-concern modules in this folder and +the building blocks under ``flows/shared/``. +""" + +from __future__ import annotations + +from app.tasks.chat.streaming.flows.resume_chat.orchestrator import stream_resume_chat + +__all__ = ["stream_resume_chat"] diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py new file mode 100644 index 000000000..b67ac987e --- /dev/null +++ b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py @@ -0,0 +1,629 @@ +"""``stream_resume_chat`` — public entry point for a HITL resume turn. + +Slim composition layer over the per-concern modules in this folder and the +building blocks under ``flows/shared/``. Mirrors ``stream_new_chat`` but: + + * No user-message persistence (the original turn already wrote it). + * No mentions / surfsense-doc / report context assembly (seeded by original). + * No title generation (only fires on first-response). + * Synchronous ``persist_assistant_shell`` call (we have no other in-flight + pre-stream work to overlap it with). + * ``input_data`` is a ``Command(resume=lg_resume_map)`` instead of a + LangChain message list. +""" + +from __future__ import annotations + +import contextlib +import gc +import logging +import sys +import time +import uuid as _uuid +from collections.abc import AsyncGenerator +from functools import partial +from typing import Any +from uuid import UUID + +import anyio + +from app.agents.multi_agent_chat import create_multi_agent_chat_deep_agent +from app.agents.new_chat.chat_deepagent import create_surfsense_deep_agent +from app.agents.new_chat.filesystem_selection import FilesystemMode, FilesystemSelection +from app.agents.new_chat.middleware.busy_mutex import end_turn +from app.config import config as _app_config +from app.db import ChatVisibility, async_session_maker, shielded_async_session +from app.observability import otel as ot +from app.services.chat_session_state_service import set_ai_responding +from app.services.new_streaming_service import VercelStreamingService +from app.tasks.chat.content_builder import AssistantContentBuilder +from app.tasks.chat.streaming.agent.builder import build_main_agent_for_thread +from app.tasks.chat.streaming.contract.file_contract import log_file_contract +from app.tasks.chat.streaming.errors.emitter import emit_stream_terminal_error +from app.tasks.chat.streaming.flows.resume_chat.assistant_shell import ( + persist_resume_assistant_shell, +) +from app.tasks.chat.streaming.flows.resume_chat.resume_routing import ( + build_resume_routing, +) +from app.tasks.chat.streaming.flows.resume_chat.runtime_context import ( + build_resume_chat_runtime_context, +) +from app.tasks.chat.streaming.flows.shared.assistant_finalize import ( + finalize_assistant_message, +) +from app.tasks.chat.streaming.flows.shared.finalize_emit import iter_token_usage_frame +from app.tasks.chat.streaming.flows.shared.finally_cleanup import ( + close_session_and_clear_ai_responding, + run_gc_pass, +) +from app.tasks.chat.streaming.flows.shared.first_frames import ( + iter_final_frames, + iter_initial_frames, +) +from app.tasks.chat.streaming.flows.shared.llm_bundle import load_llm_bundle +from app.tasks.chat.streaming.flows.shared.pre_stream_setup import ( + get_chat_checkpointer, + setup_connector_and_firecrawl, +) +from app.tasks.chat.streaming.flows.shared.premium_quota import ( + PremiumReservation, + finalize_premium, + needs_premium_quota, + release_premium, + reserve_premium, +) +from app.tasks.chat.streaming.flows.shared.rate_limit_recovery import ( + can_recover_provider_rate_limit, + log_rate_limit_recovered, + reroute_to_next_auto_pin, +) +from app.tasks.chat.streaming.flows.shared.span import ( + close_chat_request_span, + open_chat_request_span, + set_agent_mode, +) +from app.tasks.chat.streaming.flows.shared.stream_loop import run_stream_loop +from app.tasks.chat.streaming.flows.shared.terminal_error import ( + handle_terminal_exception, +) +from app.tasks.chat.streaming.shared.stream_result import StreamResult +from app.tasks.chat.streaming.shared.utils import resume_step_prefix +from app.utils.perf import get_perf_logger, log_system_snapshot + +logger = logging.getLogger(__name__) +_perf_log = get_perf_logger() + + +async def stream_resume_chat( + chat_id: int, + search_space_id: int, + decisions: list[dict], + user_id: str | None = None, + llm_config_id: int = -1, + thread_visibility: ChatVisibility | None = None, + filesystem_selection: FilesystemSelection | None = None, + request_id: str | None = None, + disabled_tools: list[str] | None = None, +) -> AsyncGenerator[str, None]: + """Resume a paused HITL turn with the user's decisions. + + Mirrors ``stream_new_chat`` except for the resume-specific routing of + ``decisions`` to per-``tool_call_id`` slices (``build_resume_routing``). + """ + streaming_service = VercelStreamingService() + stream_result = StreamResult() + _t_total = time.perf_counter() + fs_mode = filesystem_selection.mode.value if filesystem_selection else "cloud" + fs_platform = ( + filesystem_selection.client_platform.value if filesystem_selection else "web" + ) + stream_result.request_id = request_id + stream_result.turn_id = f"{chat_id}:{int(time.time() * 1000)}" + stream_result.filesystem_mode = fs_mode + stream_result.client_platform = fs_platform + + chat_agent_mode = "unknown" + chat_outcome = "success" + chat_error_category: str | None = None + chat_span_cm, chat_span = open_chat_request_span( + chat_id=chat_id, + search_space_id=search_space_id, + flow="resume", + request_id=request_id, + turn_id=stream_result.turn_id, + filesystem_mode=fs_mode, + client_platform=fs_platform, + agent_mode=chat_agent_mode, + ) + log_file_contract("turn_start", stream_result) + _perf_log.info( + "[stream_resume] filesystem_mode=%s client_platform=%s", + fs_mode, + fs_platform, + ) + + from app.services.token_tracking_service import start_turn + + accumulator = start_turn() + + premium_reservation: PremiumReservation | None = None + busy_error_raised = False + + emit_stream_error = partial( + emit_stream_terminal_error, + streaming_service=streaming_service, + flow="resume", + request_id=request_id, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + ) + + session = async_session_maker() + try: + if user_id: + await set_ai_responding(session, chat_id, UUID(user_id)) + + requested_llm_config_id = llm_config_id + + # --- LLM config --- + + _t0 = time.perf_counter() + try: + from app.services.auto_model_pin_service import ( + resolve_or_get_pinned_llm_config_id, + ) + + pinned = await resolve_or_get_pinned_llm_config_id( + session, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + selected_llm_config_id=llm_config_id, + ) + llm_config_id = pinned.resolved_llm_config_id + ot.add_event( + "model.pin.resolved", + { + "pin.requested_id": requested_llm_config_id, + "pin.resolved_id": llm_config_id, + "pin.requires_image_input": False, + }, + ) + except ValueError as pin_error: + yield emit_stream_error( + message=str(pin_error), + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + + llm, agent_config, llm_load_error = await load_llm_bundle( + session, config_id=llm_config_id, search_space_id=search_space_id + ) + if llm_load_error: + yield emit_stream_error( + message=llm_load_error, + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + _perf_log.info( + "[stream_resume] LLM config loaded in %.3fs", time.perf_counter() - _t0 + ) + + if needs_premium_quota(agent_config, user_id): + premium_reservation = await reserve_premium( + agent_config=agent_config, user_id=user_id # type: ignore[arg-type] + ) + if not premium_reservation.allowed: + ot.add_event( + "quota.denied", {"quota.code": "PREMIUM_QUOTA_EXHAUSTED"} + ) + if requested_llm_config_id == 0: + try: + pinned_fb = await resolve_or_get_pinned_llm_config_id( + session, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + selected_llm_config_id=0, + force_repin_free=True, + ) + llm_config_id = pinned_fb.resolved_llm_config_id + ot.add_event( + "model.repin", + { + "repin.reason": "premium_quota_exhausted", + "repin.to_config_id": llm_config_id, + }, + ) + except ValueError as pin_error: + yield emit_stream_error( + message=str(pin_error), + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + llm, agent_config, llm_load_error = await load_llm_bundle( + session, + config_id=llm_config_id, + search_space_id=search_space_id, + ) + if llm_load_error: + yield emit_stream_error( + message=llm_load_error, + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + premium_reservation = None + from app.tasks.chat.streaming.errors.classifier import ( + log_chat_stream_error, + ) + + log_chat_stream_error( + flow="resume", + error_kind="premium_quota_exhausted", + error_code="PREMIUM_QUOTA_EXHAUSTED", + severity="info", + is_expected=True, + request_id=request_id, + thread_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + message=( + "Premium quota exhausted on pinned model; " + "auto-fallback switched to a free model" + ), + extra={ + "fallback_config_id": llm_config_id, + "auto_fallback": True, + }, + ) + else: + yield emit_stream_error( + message=( + "Buy more tokens to continue with this model, or " + "switch to a free model" + ), + error_kind="premium_quota_exhausted", + error_code="PREMIUM_QUOTA_EXHAUSTED", + severity="info", + is_expected=True, + extra={ + "resolved_config_id": llm_config_id, + "auto_fallback": False, + }, + ) + yield streaming_service.format_done() + return + + if not llm: + yield emit_stream_error( + message="Failed to create LLM instance", + error_kind="server_error", + error_code="SERVER_ERROR", + ) + yield streaming_service.format_done() + return + + # --- Pre-stream setup --- + + _t0 = time.perf_counter() + connector_service, firecrawl_api_key = await setup_connector_and_firecrawl( + session, search_space_id=search_space_id + ) + _perf_log.info( + "[stream_resume] Connector service + firecrawl key in %.3fs", + time.perf_counter() - _t0, + ) + + _t0 = time.perf_counter() + checkpointer = await get_chat_checkpointer() + _perf_log.info( + "[stream_resume] Checkpointer ready in %.3fs", time.perf_counter() - _t0 + ) + + visibility = thread_visibility or ChatVisibility.PRIVATE + use_multi_agent = bool(_app_config.MULTI_AGENT_CHAT_ENABLED) + chat_agent_mode = "multi" if use_multi_agent else "single" + set_agent_mode(chat_span, chat_agent_mode) + + _t0 = time.perf_counter() + agent_factory = ( + create_multi_agent_chat_deep_agent + if use_multi_agent + else create_surfsense_deep_agent + ) + agent = await build_main_agent_for_thread( + agent_factory, + llm=llm, + search_space_id=search_space_id, + db_session=session, + connector_service=connector_service, + checkpointer=checkpointer, + user_id=user_id, + thread_id=chat_id, + agent_config=agent_config, + firecrawl_api_key=firecrawl_api_key, + thread_visibility=visibility, + filesystem_selection=filesystem_selection, + disabled_tools=disabled_tools, + ) + _perf_log.info( + "[stream_resume] Agent created in %.3fs", time.perf_counter() - _t0 + ) + + # Release the transaction before streaming (same rationale as stream_new_chat). + await session.commit() + session.expunge_all() + + _perf_log.info( + "[stream_resume] Total pre-stream setup in %.3fs (chat_id=%s)", + time.perf_counter() - _t_total, + chat_id, + ) + + # --- Resume routing --- + + from langgraph.types import Command + + routing = await build_resume_routing( + agent, chat_id=chat_id, decisions=decisions + ) + + config = { + "configurable": { + "thread_id": str(chat_id), + "request_id": request_id or "unknown", + "turn_id": stream_result.turn_id, + # Per-``tool_call_id`` resume slices read by + # ``SurfSenseCheckpointedSubAgentMiddleware``. Parallel + # siblings each pop their own entry, so they never race. + "surfsense_resume_value": routing.routed_resume_value, + }, + # Same rationale as ``stream_new_chat``: effectively uncapped to + # mirror the agent default and OpenCode's session loop. Doom-loop + # / call-limit middleware enforce the real ceiling. + "recursion_limit": 10_000, + } + + # --- First SSE frames --- + + for sse in iter_initial_frames(streaming_service, turn_id=stream_result.turn_id): + yield sse + + # --- Assistant-shell persistence + id frame --- + + assistant_message_id = await persist_resume_assistant_shell( + chat_id=chat_id, + user_id=user_id, + turn_id=stream_result.turn_id, + ) + if assistant_message_id is None: + yield emit_stream_error( + message=( + "We couldn't initialize the assistant message. Please try again." + ), + error_kind="server_error", + error_code="MESSAGE_PERSIST_FAILED", + ) + for sse in iter_final_frames(streaming_service): + yield sse + return + + yield streaming_service.format_data( + "assistant-message-id", + {"message_id": assistant_message_id, "turn_id": stream_result.turn_id}, + ) + + stream_result.assistant_message_id = assistant_message_id + stream_result.content_builder = AssistantContentBuilder() + + runtime_context = build_resume_chat_runtime_context( + search_space_id=search_space_id, + request_id=request_id, + turn_id=stream_result.turn_id, + ) + + # --- Stream loop --- + + _t_stream_start = time.perf_counter() + runtime_rate_limit_recovered = False + + def _on_first_event() -> None: + _perf_log.info( + "[stream_resume] First agent event in %.3fs (stream), %.3fs (total) (chat_id=%s)", + time.perf_counter() - _t_stream_start, + time.perf_counter() - _t_total, + chat_id, + ) + + async def _recover(exc: BaseException, first_event_seen: bool): + nonlocal llm_config_id, llm, agent_config, runtime_rate_limit_recovered + if not can_recover_provider_rate_limit( + exc, + first_event_seen=first_event_seen, + runtime_rate_limit_recovered=runtime_rate_limit_recovered, + requested_llm_config_id=requested_llm_config_id, + current_llm_config_id=llm_config_id, + ): + return None + runtime_rate_limit_recovered = True + previous_config_id = llm_config_id + llm_config_id = await reroute_to_next_auto_pin( + session, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + current_llm_config_id=llm_config_id, + requires_image_input=False, + ) + new_llm, new_agent_config, llm_load_err = await load_llm_bundle( + session, config_id=llm_config_id, search_space_id=search_space_id + ) + if llm_load_err: + return None + llm = new_llm + agent_config = new_agent_config + + _t_rebuild = time.perf_counter() + new_agent = await build_main_agent_for_thread( + agent_factory, + llm=llm, + search_space_id=search_space_id, + db_session=session, + connector_service=connector_service, + checkpointer=checkpointer, + user_id=user_id, + thread_id=chat_id, + agent_config=agent_config, + firecrawl_api_key=firecrawl_api_key, + thread_visibility=visibility, + filesystem_selection=filesystem_selection, + disabled_tools=disabled_tools, + ) + _perf_log.info( + "[stream_resume] Runtime rate-limit recovery repinned " + "config_id=%s -> %s and rebuilt agent in %.3fs", + previous_config_id, + llm_config_id, + time.perf_counter() - _t_rebuild, + ) + log_rate_limit_recovered( + flow="resume", + request_id=request_id, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + previous_config_id=previous_config_id, + new_config_id=llm_config_id, + ) + return new_agent + + async for sse in run_stream_loop( + agent=agent, + streaming_service=streaming_service, + config=config, + input_data=Command(resume=routing.lg_resume_map), + stream_result=stream_result, + step_prefix=resume_step_prefix(stream_result.turn_id), + fallback_commit_search_space_id=search_space_id, + fallback_commit_created_by_id=user_id, + fallback_commit_filesystem_mode=( + filesystem_selection.mode if filesystem_selection else FilesystemMode.CLOUD + ), + fallback_commit_thread_id=chat_id, + runtime_context=runtime_context, + content_builder=stream_result.content_builder, + recover=_recover, + on_first_event=_on_first_event, + ): + yield sse + + _perf_log.info( + "[stream_resume] Agent stream completed in %.3fs (chat_id=%s)", + time.perf_counter() - _t_stream_start, + chat_id, + ) + + # --- Finalize --- + + if stream_result.is_interrupted: + ot.add_event("chat.interrupted", {"chat.flow": "resume"}) + for sse in iter_token_usage_frame( + streaming_service, + accumulator=accumulator, + log_label="interrupted resume_chat", + ): + yield sse + yield streaming_service.format_finish_step() + yield streaming_service.format_finish() + yield streaming_service.format_done() + return + + if premium_reservation is not None and user_id: + await finalize_premium( + reservation=premium_reservation, + user_id=user_id, + accumulator=accumulator, + ) + premium_reservation = None + + for sse in iter_token_usage_frame( + streaming_service, accumulator=accumulator, log_label="normal resume_chat" + ): + yield sse + + for sse in iter_final_frames(streaming_service): + yield sse + + except Exception as exc: + frames, summary = handle_terminal_exception( + exc, + flow="resume", + flow_label="resume", + log_prefix="stream_resume_chat", + streaming_service=streaming_service, + request_id=request_id, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + chat_span=chat_span, + ) + if summary["busy_error_raised"]: + busy_error_raised = True + chat_outcome = summary["chat_outcome"] + chat_error_category = summary["chat_error_category"] + for sse in frames: + yield sse + + finally: + with anyio.CancelScope(shield=True): + end_turn(str(chat_id)) + + if premium_reservation is not None and user_id: + await release_premium( + reservation=premium_reservation, user_id=user_id + ) + + await close_session_and_clear_ai_responding(session, chat_id) + + await finalize_assistant_message( + stream_result=stream_result, + chat_id=chat_id, + search_space_id=search_space_id, + user_id=user_id, + accumulator=accumulator, + log_prefix="stream_resume", + ) + + # Release the lock from the original interrupted turn or any + # re-interrupt/bailout. Skip on ``BusyError`` (lock not held here). + if not busy_error_raised: + with contextlib.suppress(Exception): + end_turn(str(chat_id)) + _perf_log.info( + "[stream_resume] end_turn cleanup (chat_id=%s)", chat_id + ) + + agent = llm = connector_service = None # noqa: F841 + stream_result = None # noqa: F841 + session = None # noqa: F841 + + run_gc_pass(log_prefix="stream_resume", chat_id=chat_id) + close_chat_request_span( + span_cm=chat_span_cm, + span=chat_span, + chat_outcome=chat_outcome, + chat_agent_mode=chat_agent_mode, + flow="resume", + chat_error_category=chat_error_category, + duration_seconds=time.perf_counter() - _t_total, + ) From cfdad85058083dd822655611ae1390e4873d587a Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Mon, 25 May 2026 21:50:18 +0200 Subject: [PATCH 065/198] test(chat): add parity tests for streaming/flows/ parallel refactor Adds 34 tests under tests/unit/tasks/chat/streaming/ that cover the new flows tree against the legacy stream_new_chat.py module to gate the upcoming cutover. Coverage: * Public entry points: stream_new_chat and stream_resume_chat are async generator functions whose parameter signatures (name, kind, annotation, default) match the legacy versions one-for-one. Uses a normalized-annotation comparison so PEP-563 vs eager-annotation representation differences are tolerated. * Extracted helpers: image-capability gate, runtime-context builders for new-chat and resume-chat, LLM-bundle dispatcher, premium-quota needs check + reservation dataclass, rate-limit recovery truth table, persistence-spawn registration/self-unregistration, await helpers. * SSE frame iterators: iter_initial_frames + iter_final_frames emit the canonical sequence; iter_token_usage_frame skips on None. * Initial thinking step: 4 parametrized branches (text, image-only, empty, mentioned-docs), long-query truncation, many-docs collapse. These tests are scaffolding for the cutover and will be removed once the legacy module is deleted. --- .../test_parallel_refactor_parity.py | 582 ++++++++++++++++++ 1 file changed, 582 insertions(+) create mode 100644 surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py diff --git a/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py b/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py new file mode 100644 index 000000000..eb24b4df8 --- /dev/null +++ b/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py @@ -0,0 +1,582 @@ +"""Parity gate for the parallel refactor of ``stream_new_chat.py``. + +The new tree under ``app.tasks.chat.streaming.flows`` is built side-by-side with +the legacy monolithic ``app.tasks.chat.stream_new_chat`` so we can cut over +atomically. This file pins externally-observable behaviour at module +boundaries so a divergence between the two trees fails loudly *before* the +cutover. + +What we verify: + + 1. **Signature parity** — ``stream_new_chat`` / ``stream_resume_chat`` from + the new tree have the same call signature as the originals. + 2. **Helper extraction parity** — the SRP modules in ``flows/`` produce the + same outputs as the inline code in the legacy file for representative + inputs (initial thinking step, image-capability gate, runtime context, + SSE frame sequences, token-usage frame shape, persistence guards). + 3. **Wrapper delegation** — wrappers like ``load_llm_bundle`` / + ``can_recover_provider_rate_limit`` exist and are addressable. + +Delete this file along with ``stream_new_chat.py`` once the cutover is done +(see the parent refactor plan). +""" + +from __future__ import annotations + +import asyncio +import inspect +from dataclasses import dataclass +from typing import Any +from unittest.mock import AsyncMock, patch + +import pytest + +from app.agents.new_chat.context import SurfSenseContextSchema +from app.services.new_streaming_service import VercelStreamingService + +from app.tasks.chat.stream_new_chat import ( + stream_new_chat as old_stream_new_chat, + stream_resume_chat as old_stream_resume_chat, +) +from app.tasks.chat.streaming.flows import ( + stream_new_chat as new_stream_new_chat, + stream_resume_chat as new_stream_resume_chat, +) +from app.tasks.chat.streaming.flows.new_chat.initial_thinking_step import ( + build_initial_thinking_step, +) +from app.tasks.chat.streaming.flows.new_chat.llm_capability import ( + check_image_input_capability, +) +from app.tasks.chat.streaming.flows.new_chat.persistence_spawn import ( + await_persist_task, + spawn_persist_assistant_shell_task, + spawn_persist_user_task, + spawn_set_ai_responding_bg, +) +from app.tasks.chat.streaming.flows.new_chat.runtime_context import ( + build_new_chat_runtime_context, +) +from app.tasks.chat.streaming.flows.resume_chat.runtime_context import ( + build_resume_chat_runtime_context, +) +from app.tasks.chat.streaming.flows.shared.finalize_emit import iter_token_usage_frame +from app.tasks.chat.streaming.flows.shared.first_frames import ( + iter_final_frames, + iter_initial_frames, +) +from app.tasks.chat.streaming.flows.shared.llm_bundle import load_llm_bundle +from app.tasks.chat.streaming.flows.shared.premium_quota import ( + PremiumReservation, + needs_premium_quota, +) +from app.tasks.chat.streaming.flows.shared.rate_limit_recovery import ( + can_recover_provider_rate_limit, +) + +pytestmark = pytest.mark.unit + + +# --------------------------------------------------------------------- signature + + +def _normalize_annotation(ann: Any) -> str: + """Compare-friendly form for an annotation. + + The legacy ``stream_new_chat.py`` does NOT use ``from __future__ import + annotations``, so its annotations are evaluated at import time and come + back as type objects / typing generics. The new tree DOES use it, so its + annotations are PEP-563 strings. + + Both reprs describe the same types — strip the module prefixes / typing + namespace + the ```` wrapper so we compare the canonical + declared form. + """ + if ann is inspect.Signature.empty: + return "" + raw = ann if isinstance(ann, str) else repr(ann) + cleaned = ( + raw.replace("typing.", "") + .replace("collections.abc.", "") + .replace("app.db.", "") + .replace("app.agents.new_chat.filesystem_selection.", "") + .replace("app.agents.new_chat.context.", "") + ) + # Unwrap ```` → ``int`` (legacy-side type objects). + if cleaned.startswith(""): + cleaned = cleaned[len("")] + return cleaned + + +def _normalize_sig(sig: inspect.Signature) -> list[tuple[str, Any, str]]: + return [ + (p.name, p.default, _normalize_annotation(p.annotation)) + for p in sig.parameters.values() + ] + + +def test_stream_new_chat_signature_matches_legacy() -> None: + old = inspect.signature(old_stream_new_chat) + new = inspect.signature(new_stream_new_chat) + assert _normalize_sig(new) == _normalize_sig(old) + assert _normalize_annotation(new.return_annotation) == _normalize_annotation( + old.return_annotation + ) + + +def test_stream_resume_chat_signature_matches_legacy() -> None: + old = inspect.signature(old_stream_resume_chat) + new = inspect.signature(new_stream_resume_chat) + assert _normalize_sig(new) == _normalize_sig(old) + assert _normalize_annotation(new.return_annotation) == _normalize_annotation( + old.return_annotation + ) + + +def test_orchestrators_are_async_generator_functions() -> None: + assert inspect.isasyncgenfunction(new_stream_new_chat) + assert inspect.isasyncgenfunction(new_stream_resume_chat) + + +# ------------------------------------------------------------ initial thinking + + +@dataclass +class _FakeSurfsenseDoc: + """Stand-in for ``SurfsenseDocsDocument`` with just the field we read.""" + + title: str + + +@pytest.mark.parametrize( + "user_query, image_urls, docs, expected_title, expected_action", + [ + ("hello world", None, [], "Understanding your request", "Processing"), + ("", ["data:image/png;base64,AAA"], [], "Understanding your request", "Processing"), + ("", None, [], "Understanding your request", "Processing"), + ( + "doc question", + None, + [_FakeSurfsenseDoc(title="My Doc")], + "Analyzing referenced content", + "Analyzing", + ), + ], +) +def test_initial_thinking_step_branches( + user_query: str, + image_urls: list[str] | None, + docs: list[Any], + expected_title: str, + expected_action: str, +) -> None: + step = build_initial_thinking_step( + user_query=user_query, + user_image_data_urls=image_urls, + mentioned_surfsense_docs=docs, # type: ignore[arg-type] + ) + assert step.step_id == "thinking-1" + assert step.title == expected_title + assert len(step.items) == 1 + assert step.items[0].startswith(f"{expected_action}: ") + + +def test_initial_thinking_step_truncates_long_query() -> None: + long_query = "x" * 200 + step = build_initial_thinking_step( + user_query=long_query, + user_image_data_urls=None, + mentioned_surfsense_docs=[], + ) + # 80-char truncation + ellipsis, sandwiched after "Processing: ". + assert "..." in step.items[0] + item = step.items[0] + payload = item[len("Processing: ") :] + assert payload.startswith("x" * 80) and payload.endswith("...") + + +def test_initial_thinking_step_collapses_many_doc_names() -> None: + docs = [_FakeSurfsenseDoc(title=f"Doc {i}") for i in range(5)] + step = build_initial_thinking_step( + user_query="q", + user_image_data_urls=None, + mentioned_surfsense_docs=docs, # type: ignore[arg-type] + ) + assert "[5 docs]" in step.items[0] + + +# ------------------------------------------------------------ capability gate + + +def test_image_capability_passes_without_images() -> None: + assert check_image_input_capability( + user_image_data_urls=None, agent_config=None + ) is None + + +def test_image_capability_passes_when_capability_unknown() -> None: + """Unknown / unmapped models are not blocked — only models LiteLLM has + *explicitly* marked text-only trip the gate.""" + + class _AgentConfig: + provider = "openrouter" + model_name = "unknown-mystery-model" + custom_provider = None + config_name = "Unknown" + litellm_params: dict[str, Any] = {} + + with patch( + "app.services.provider_capabilities.is_known_text_only_chat_model", + return_value=False, + ): + assert ( + check_image_input_capability( + user_image_data_urls=["data:image/png;base64,AAA"], + agent_config=_AgentConfig(), # type: ignore[arg-type] + ) + is None + ) + + +def test_image_capability_blocks_known_text_only_models() -> None: + class _AgentConfig: + provider = "openai" + model_name = "gpt-3.5-turbo" + custom_provider = None + config_name = "GPT-3.5" + litellm_params: dict[str, Any] = {"base_model": "gpt-3.5-turbo"} + + with patch( + "app.services.provider_capabilities.is_known_text_only_chat_model", + return_value=True, + ): + result = check_image_input_capability( + user_image_data_urls=["data:image/png;base64,AAA"], + agent_config=_AgentConfig(), # type: ignore[arg-type] + ) + assert result is not None + message, error_code = result + assert error_code == "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT" + assert "GPT-3.5" in message + + +# ---------------------------------------------------------------- runtime ctx + + +def test_new_chat_runtime_context_prefers_accepted_folder_ids() -> None: + ctx = build_new_chat_runtime_context( + search_space_id=7, + mentioned_document_ids=[1, 2], + accepted_folder_ids=[10], + mentioned_folder_ids=[20, 30], + request_id="req", + turn_id="t1", + ) + assert isinstance(ctx, SurfSenseContextSchema) + assert ctx.search_space_id == 7 + assert list(ctx.mentioned_document_ids) == [1, 2] + assert list(ctx.mentioned_folder_ids) == [10] + assert ctx.request_id == "req" + assert ctx.turn_id == "t1" + + +def test_new_chat_runtime_context_falls_back_to_mentioned_folder_ids() -> None: + ctx = build_new_chat_runtime_context( + search_space_id=7, + mentioned_document_ids=None, + accepted_folder_ids=[], + mentioned_folder_ids=[20, 30], + request_id=None, + turn_id="t2", + ) + assert list(ctx.mentioned_folder_ids) == [20, 30] + + +def test_resume_chat_runtime_context_empty_mention_lists() -> None: + ctx = build_resume_chat_runtime_context( + search_space_id=42, request_id="req-r", turn_id="t-r" + ) + assert ctx.search_space_id == 42 + assert ctx.request_id == "req-r" + assert ctx.turn_id == "t-r" + + +# ---------------------------------------------------------------- SSE frames + + +def test_iter_initial_frames_emits_canonical_sequence() -> None: + svc = VercelStreamingService() + frames = list(iter_initial_frames(svc, turn_id="42:1700000000000")) + # Exactly 4 frames: message_start, start_step, turn-info (turn_id), turn-status (busy). + assert len(frames) == 4 + assert "42:1700000000000" in frames[2] + assert '"status":"busy"' in frames[3] or '"status": "busy"' in frames[3] + + +def test_iter_final_frames_emits_idle_then_finish_done() -> None: + svc = VercelStreamingService() + frames = list(iter_final_frames(svc)) + assert len(frames) == 4 + assert '"status":"idle"' in frames[0] or '"status": "idle"' in frames[0] + + +# ----------------------------------------------------------- token usage frame + + +class _FakeAccumulator: + """Minimal stand-in covering only the fields ``iter_token_usage_frame`` reads.""" + + def __init__(self, summary: Any = None) -> None: + self._summary = summary + self.calls = [1, 2, 3] + self.grand_total = 100 + self.total_cost_micros = 50_000 + self.total_prompt_tokens = 60 + self.total_completion_tokens = 40 + + def per_message_summary(self) -> Any: + return self._summary + + def serialized_calls(self) -> list[Any]: + return list(self.calls) + + +def test_token_usage_frame_skipped_when_no_summary() -> None: + svc = VercelStreamingService() + frames = list( + iter_token_usage_frame( + svc, + accumulator=_FakeAccumulator(summary=None), # type: ignore[arg-type] + log_label="parity-empty", + ) + ) + assert frames == [] + + +def test_token_usage_frame_emitted_when_summary_present() -> None: + svc = VercelStreamingService() + frames = list( + iter_token_usage_frame( + svc, + accumulator=_FakeAccumulator(summary=[{"m": "x", "t": 100}]), # type: ignore[arg-type] + log_label="parity-populated", + ) + ) + assert len(frames) == 1 + # Field shape on the wire is fixed by the FE; assert each surfaces. + payload = frames[0] + for key in ( + '"prompt_tokens":60', + '"completion_tokens":40', + '"total_tokens":100', + '"cost_micros":50000', + ): + assert key in payload.replace(" ", "") + + +# ------------------------------------------------------------------ llm_bundle + + +def test_load_llm_bundle_routes_negative_id_to_yaml_loader() -> None: + async def _run() -> tuple[Any, Any, str | None]: + with ( + patch( + "app.tasks.chat.streaming.flows.shared.llm_bundle.load_global_llm_config_by_id", + return_value=None, + ), + ): + return await load_llm_bundle( + session=AsyncMock(), # type: ignore[arg-type] + config_id=-1, + search_space_id=7, + ) + + llm, agent_config, error = asyncio.run(_run()) + assert llm is None + assert agent_config is None + assert error is not None and "id -1" in error + + +def test_load_llm_bundle_routes_nonnegative_id_to_db_loader() -> None: + async def _run() -> tuple[Any, Any, str | None]: + with ( + patch( + "app.tasks.chat.streaming.flows.shared.llm_bundle.load_agent_config", + new=AsyncMock(return_value=None), + ), + ): + return await load_llm_bundle( + session=AsyncMock(), # type: ignore[arg-type] + config_id=12, + search_space_id=7, + ) + + llm, agent_config, error = asyncio.run(_run()) + assert llm is None + assert agent_config is None + assert error is not None and "id 12" in error + + +# ----------------------------------------------------------------- premium quota + + +def test_needs_premium_quota_requires_user_and_premium_flag() -> None: + class _AgentConfig: + is_premium = True + + class _NonPremium: + is_premium = False + + assert needs_premium_quota(_AgentConfig(), "user-1") is True # type: ignore[arg-type] + assert needs_premium_quota(_AgentConfig(), None) is False # type: ignore[arg-type] + assert needs_premium_quota(_NonPremium(), "user-1") is False # type: ignore[arg-type] + assert needs_premium_quota(None, "user-1") is False + + +def test_premium_reservation_dataclass_shape() -> None: + # Sanity: the dataclass exists and carries the fields the orchestrator uses. + r = PremiumReservation(request_id="abc", reserved_micros=100, allowed=True) + assert r.request_id == "abc" + assert r.reserved_micros == 100 + assert r.allowed is True + + +# ----------------------------------------------------------- rate-limit guard + + +@pytest.mark.parametrize( + "first_event_seen, recovered, requested_id, current_id, expected", + [ + (False, False, 0, -1, True), + # Already recovered: no second pass. + (False, True, 0, -1, False), + # User explicitly picked a config: don't silently switch. + (False, False, 5, -1, False), + # Already on a database-backed (positive) id. + (False, False, 0, 7, False), + # User has already seen output: silent rebuild not possible. + (True, False, 0, -1, False), + ], +) +def test_can_recover_provider_rate_limit_truth_table( + first_event_seen: bool, + recovered: bool, + requested_id: int, + current_id: int, + expected: bool, +) -> None: + # Use a known rate-limit-shaped exception so the helper's last condition + # is satisfied; the guard only short-circuits to False when one of the + # *other* preconditions fails. + exc = Exception('{"error":{"type":"rate_limit_error","message":"slow"}}') + assert ( + can_recover_provider_rate_limit( + exc, + first_event_seen=first_event_seen, + runtime_rate_limit_recovered=recovered, + requested_llm_config_id=requested_id, + current_llm_config_id=current_id, + ) + is expected + ) + + +def test_can_recover_provider_rate_limit_rejects_non_rate_limit_exception() -> None: + assert ( + can_recover_provider_rate_limit( + ValueError("not a rate limit"), + first_event_seen=False, + runtime_rate_limit_recovered=False, + requested_llm_config_id=0, + current_llm_config_id=-1, + ) + is False + ) + + +# --------------------------------------------------------- persistence spawn + + +def test_spawn_set_ai_responding_bg_noop_without_user_id() -> None: + async def _run() -> set[asyncio.Task]: + background: set[asyncio.Task] = set() + spawn_set_ai_responding_bg( + chat_id=1, user_id=None, background_tasks=background + ) + return background + + bg = asyncio.run(_run()) + assert bg == set() + + +def test_spawn_persist_user_task_registers_and_self_unregisters() -> None: + async def _run() -> tuple[int, int]: + background: set[asyncio.Task] = set() + with patch( + "app.tasks.chat.streaming.flows.new_chat.persistence_spawn.persist_user_turn", + new=AsyncMock(return_value=99), + ): + task = spawn_persist_user_task( + chat_id=1, + user_id="u", + turn_id="t", + user_query="hi", + user_image_data_urls=None, + mentioned_documents=None, + background_tasks=background, + ) + size_before_await = len(background) + result = await asyncio.shield(task) + # Give the done-callback one event-loop tick to run. + await asyncio.sleep(0) + return size_before_await, result # type: ignore[return-value] + + size_before, result = asyncio.run(_run()) + assert size_before == 1 + assert result == 99 + + +def test_spawn_persist_assistant_shell_task_registers() -> None: + async def _run() -> int | None: + background: set[asyncio.Task] = set() + with patch( + "app.tasks.chat.streaming.flows.new_chat.persistence_spawn.persist_assistant_shell", + new=AsyncMock(return_value=42), + ): + task = spawn_persist_assistant_shell_task( + chat_id=1, + user_id="u", + turn_id="t", + background_tasks=background, + ) + return await asyncio.shield(task) + + assert asyncio.run(_run()) == 42 + + +def test_await_persist_task_returns_none_on_failure() -> None: + async def _run() -> int | None: + async def _boom() -> int: + raise RuntimeError("DB down") + + task = asyncio.create_task(_boom()) + return await await_persist_task( + task, + chat_id=1, + turn_id="t", + log_label="parity-failure", + ) + + assert asyncio.run(_run()) is None + + +def test_await_persist_task_returns_none_for_none_input() -> None: + async def _run() -> int | None: + return await await_persist_task( + None, + chat_id=1, + turn_id="t", + log_label="parity-none", + ) + + assert asyncio.run(_run()) is None From 6cf0f0736674ad5a0c69cc7029ec2056d32dbae0 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 26 May 2026 02:38:23 +0530 Subject: [PATCH 066/198] refactor(auto-updater): remove native install dialog and streamline update notification handling --- surfsense_desktop/src/modules/auto-updater.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/surfsense_desktop/src/modules/auto-updater.ts b/surfsense_desktop/src/modules/auto-updater.ts index 4745fa24b..d140a1a2a 100644 --- a/surfsense_desktop/src/modules/auto-updater.ts +++ b/surfsense_desktop/src/modules/auto-updater.ts @@ -70,23 +70,6 @@ function notifyRenderersUpdateDownloaded(info: UpdateInfo): void { } } -async function showNativeInstallDialog(autoUpdater: AutoUpdater, info: UpdateInfo): Promise { - const { response } = await dialog.showMessageBox({ - type: 'info', - buttons: ['Restart', 'Later'], - defaultId: 0, - title: 'Update Ready', - message: `Version ${info.version} has been downloaded. Restart to apply the update.`, - }); - - if (response === 0) { - trackEvent('desktop_update_install_accepted', { new_version: info.version }); - autoUpdater.quitAndInstall(); - } else { - trackEvent('desktop_update_install_deferred', { new_version: info.version }); - } -} - export function installDownloadedUpdate(): void { const autoUpdater = getAutoUpdater(); trackEvent('desktop_update_install_accepted', { source: 'renderer_prompt' }); @@ -154,7 +137,7 @@ export async function checkForUpdatesManually(): Promise { }; const onDownloaded = (info: UpdateInfo) => { cleanup(); - void showNativeInstallDialog(autoUpdater, info); + notifyRenderersUpdateDownloaded(info); resolve('downloaded'); }; const onError = (err: Error) => { From 98697f1dd6e9a4f534c860ede20e4b5f118a740c Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 26 May 2026 03:11:06 +0530 Subject: [PATCH 067/198] feat(auto-updater, menu): implement update state management and enhance menu options for updates --- surfsense_desktop/src/modules/auto-updater.ts | 60 ++++++++++++++----- surfsense_desktop/src/modules/menu.ts | 53 +++++++++++++--- 2 files changed, 88 insertions(+), 25 deletions(-) diff --git a/surfsense_desktop/src/modules/auto-updater.ts b/surfsense_desktop/src/modules/auto-updater.ts index d140a1a2a..b318b737d 100644 --- a/surfsense_desktop/src/modules/auto-updater.ts +++ b/surfsense_desktop/src/modules/auto-updater.ts @@ -17,8 +17,32 @@ type UpdateInfo = { version: string; }; +type UpdateMenuState = + | { status: 'idle' } + | { status: 'downloading'; version: string } + | { status: 'ready'; version: string }; + let listenersRegistered = false; -let manualUpdateCheckInProgress = false; +let updateMenuState: UpdateMenuState = { status: 'idle' }; +const updateMenuStateListeners = new Set<(state: UpdateMenuState) => void>(); + +export function getUpdateMenuState(): UpdateMenuState { + return updateMenuState; +} + +export function onUpdateMenuStateChange(listener: (state: UpdateMenuState) => void): () => void { + updateMenuStateListeners.add(listener); + return () => { + updateMenuStateListeners.delete(listener); + }; +} + +function setUpdateMenuState(state: UpdateMenuState): void { + updateMenuState = state; + for (const listener of updateMenuStateListeners) { + listener(state); + } +} function getAutoUpdater(): AutoUpdater { const { autoUpdater } = require('electron-updater'); @@ -35,6 +59,7 @@ function configureAutoUpdater(autoUpdater: AutoUpdater): void { autoUpdater.on('update-available', (info: UpdateInfo) => { console.log(`Update available: ${info.version}`); + setUpdateMenuState({ status: 'downloading', version: info.version }); trackEvent('desktop_update_available', { current_version: version, new_version: info.version, @@ -43,16 +68,20 @@ function configureAutoUpdater(autoUpdater: AutoUpdater): void { autoUpdater.on('update-downloaded', (info: UpdateInfo) => { console.log(`Update downloaded: ${info.version}`); + setUpdateMenuState({ status: 'ready', version: info.version }); trackEvent('desktop_update_downloaded', { current_version: version, new_version: info.version, }); - if (!manualUpdateCheckInProgress) { - notifyRenderersUpdateDownloaded(info); - } + notifyRenderersUpdateDownloaded(info); + }); + + autoUpdater.on('update-not-available', () => { + setUpdateMenuState({ status: 'idle' }); }); autoUpdater.on('error', (err: Error) => { + setUpdateMenuState({ status: 'idle' }); console.log('Auto-updater: update check skipped —', err.message?.split('\n')[0]); trackEvent('desktop_update_error', { message: err.message?.split('\n')[0], @@ -92,6 +121,13 @@ export function setupAutoUpdater(): void { } export async function checkForUpdatesManually(): Promise { + const currentState = getUpdateMenuState(); + if (currentState.status === 'ready') { + installDownloadedUpdate(); + return; + } + if (currentState.status === 'downloading') return; + if (!app.isPackaged) { await dialog.showMessageBox({ type: 'info', @@ -115,29 +151,20 @@ export async function checkForUpdatesManually(): Promise { configureAutoUpdater(autoUpdater); try { - manualUpdateCheckInProgress = true; const result = await new Promise<'not-available' | 'downloaded'>((resolve, reject) => { const cleanup = () => { - manualUpdateCheckInProgress = false; autoUpdater.removeListener('update-available', onAvailable); autoUpdater.removeListener('update-not-available', onNotAvailable); autoUpdater.removeListener('update-downloaded', onDownloaded); autoUpdater.removeListener('error', onError); }; - const onAvailable = (info: UpdateInfo) => { - void dialog.showMessageBox({ - type: 'info', - title: 'Update Available', - message: `Version ${info.version} is available and will download in the background.`, - }); - }; + const onAvailable = () => {}; const onNotAvailable = () => { cleanup(); resolve('not-available'); }; - const onDownloaded = (info: UpdateInfo) => { + const onDownloaded = () => { cleanup(); - notifyRenderersUpdateDownloaded(info); resolve('downloaded'); }; const onError = (err: Error) => { @@ -151,6 +178,7 @@ export async function checkForUpdatesManually(): Promise { autoUpdater.once('error', onError); autoUpdater.checkForUpdates().catch((err: Error) => { cleanup(); + setUpdateMenuState({ status: 'idle' }); reject(err); }); }); @@ -163,7 +191,7 @@ export async function checkForUpdatesManually(): Promise { }); } } catch (err) { - manualUpdateCheckInProgress = false; + setUpdateMenuState({ status: 'idle' }); await dialog.showMessageBox({ type: 'error', title: 'Update Check Failed', diff --git a/surfsense_desktop/src/modules/menu.ts b/surfsense_desktop/src/modules/menu.ts index 40e19398c..2753aaacd 100644 --- a/surfsense_desktop/src/modules/menu.ts +++ b/surfsense_desktop/src/modules/menu.ts @@ -1,12 +1,39 @@ import { app, Menu, shell } from 'electron'; -import { checkForUpdatesManually } from './auto-updater'; +import { + checkForUpdatesManually, + getUpdateMenuState, + installDownloadedUpdate, + onUpdateMenuStateChange, +} from './auto-updater'; -const checkForUpdatesItem: Electron.MenuItemConstructorOptions = { - label: 'Check for Updates...', - click: () => { - void checkForUpdatesManually(); - }, -}; +let updateMenuListenerRegistered = false; + +function getUpdateMenuItem(): Electron.MenuItemConstructorOptions { + const state = getUpdateMenuState(); + + if (state.status === 'downloading') { + return { + label: 'Downloading...', + enabled: false, + }; + } + + if (state.status === 'ready') { + return { + label: 'Install and Restart', + click: () => { + installDownloadedUpdate(); + }, + }; + } + + return { + label: 'Check for Updates...', + click: () => { + void checkForUpdatesManually(); + }, + }; +} const privacyPolicyItem: Electron.MenuItemConstructorOptions = { label: 'Privacy Policy', @@ -23,14 +50,22 @@ const termsOfServiceItem: Electron.MenuItemConstructorOptions = { }; export function setupMenu(): void { + if (!updateMenuListenerRegistered) { + updateMenuListenerRegistered = true; + onUpdateMenuStateChange(() => { + setupMenu(); + }); + } + const isMac = process.platform === 'darwin'; + const updateMenuItem = getUpdateMenuItem(); const template: Electron.MenuItemConstructorOptions[] = [ ...(isMac ? [{ label: app.name, submenu: [ { role: 'about' as const }, - checkForUpdatesItem, + updateMenuItem, { type: 'separator' as const }, { role: 'services' as const }, { type: 'separator' as const }, @@ -51,7 +86,7 @@ export function setupMenu(): void { submenu: [ ...(!isMac ? [ - checkForUpdatesItem, + updateMenuItem, { type: 'separator' as const }, ] : []), From bf4e60d22467d66477acae9257c4bfc8bca83f65 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 26 May 2026 03:55:16 +0530 Subject: [PATCH 068/198] feat(menu, dev-tools): enhance menu with view options and enable dev tools in development mode --- surfsense_desktop/src/modules/menu.ts | 21 ++++++++++++++++++- surfsense_desktop/src/modules/quick-ask.ts | 3 ++- .../screen-capture/screen-region-picker.ts | 3 ++- .../modules/screen-capture/window-picker.ts | 3 ++- surfsense_desktop/src/modules/window.ts | 1 + 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/surfsense_desktop/src/modules/menu.ts b/surfsense_desktop/src/modules/menu.ts index 2753aaacd..629d88a04 100644 --- a/surfsense_desktop/src/modules/menu.ts +++ b/surfsense_desktop/src/modules/menu.ts @@ -58,7 +58,23 @@ export function setupMenu(): void { } const isMac = process.platform === 'darwin'; + const isDev = !app.isPackaged; const updateMenuItem = getUpdateMenuItem(); + const viewSubmenu: Electron.MenuItemConstructorOptions[] = [ + { role: 'reload' as const }, + { role: 'forceReload' as const }, + ...(isDev + ? [ + { role: 'toggleDevTools' as const }, + ] + : []), + { type: 'separator' as const }, + { role: 'resetZoom' as const }, + { role: 'zoomIn' as const }, + { role: 'zoomOut' as const }, + { type: 'separator' as const }, + { role: 'togglefullscreen' as const }, + ]; const template: Electron.MenuItemConstructorOptions[] = [ ...(isMac ? [{ @@ -79,7 +95,10 @@ export function setupMenu(): void { : []), { role: 'fileMenu' as const }, { role: 'editMenu' as const }, - { role: 'viewMenu' as const }, + { + label: 'View', + submenu: viewSubmenu, + }, { role: 'windowMenu' as const }, { role: 'help' as const, diff --git a/surfsense_desktop/src/modules/quick-ask.ts b/surfsense_desktop/src/modules/quick-ask.ts index d7602f470..0807e2e08 100644 --- a/surfsense_desktop/src/modules/quick-ask.ts +++ b/surfsense_desktop/src/modules/quick-ask.ts @@ -1,4 +1,4 @@ -import { BrowserWindow, clipboard, globalShortcut, ipcMain, screen, shell } from 'electron'; +import { app, BrowserWindow, clipboard, globalShortcut, ipcMain, screen, shell } from 'electron'; import path from 'path'; import { IPC_CHANNELS } from '../ipc/channels'; import { checkAccessibilityPermission, getFrontmostApp, simulateCopy, simulatePaste } from './platform'; @@ -51,6 +51,7 @@ function createQuickAskWindow(x: number, y: number): BrowserWindow { contextIsolation: true, nodeIntegration: false, sandbox: true, + devTools: !app.isPackaged, }, show: false, skipTaskbar: true, diff --git a/surfsense_desktop/src/modules/screen-capture/screen-region-picker.ts b/surfsense_desktop/src/modules/screen-capture/screen-region-picker.ts index fd771b0f7..0cfc92297 100644 --- a/surfsense_desktop/src/modules/screen-capture/screen-region-picker.ts +++ b/surfsense_desktop/src/modules/screen-capture/screen-region-picker.ts @@ -1,4 +1,4 @@ -import { BrowserWindow, desktopCapturer, nativeImage, screen } from 'electron'; +import { app, BrowserWindow, desktopCapturer, nativeImage, screen } from 'electron'; import path from 'path'; import { IPC_CHANNELS } from '../../ipc/channels'; function fitNativeImageToWorkArea(img: Electron.NativeImage, display: Electron.Display): Electron.NativeImage { @@ -261,6 +261,7 @@ export function pickScreenRegion(opts?: { windowDataUrl?: string }): Promise { contextIsolation: true, nodeIntegration: false, sandbox: true, + devTools: !app.isPackaged, }, }); diff --git a/surfsense_desktop/src/modules/window.ts b/surfsense_desktop/src/modules/window.ts index bcae1799c..42011d089 100644 --- a/surfsense_desktop/src/modules/window.ts +++ b/surfsense_desktop/src/modules/window.ts @@ -53,6 +53,7 @@ export function createMainWindow(initialPath = '/dashboard'): BrowserWindow { nodeIntegration: false, sandbox: true, webviewTag: false, + devTools: !app.isPackaged, }, show: false, ...(isMac From 7276210403653f62aa52e5c9c006b6fb3dedf48e Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 26 May 2026 11:57:50 +0530 Subject: [PATCH 069/198] feat(auto-updater, ui): rename DesktopUpdatePrompt with DesktopUpdateToast --- surfsense_web/app/layout.tsx | 4 ++-- .../{DesktopUpdatePrompt.tsx => desktop-update-toast.tsx} | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) rename surfsense_web/components/desktop/{DesktopUpdatePrompt.tsx => desktop-update-toast.tsx} (92%) diff --git a/surfsense_web/app/layout.tsx b/surfsense_web/app/layout.tsx index 4bb15c607..eef03d463 100644 --- a/surfsense_web/app/layout.tsx +++ b/surfsense_web/app/layout.tsx @@ -3,7 +3,7 @@ import "./globals.css"; import { RootProvider } from "fumadocs-ui/provider/next"; import { Roboto } from "next/font/google"; import { AnnouncementToastProvider } from "@/components/announcements/AnnouncementToastProvider"; -import { DesktopUpdatePrompt } from "@/components/desktop/DesktopUpdatePrompt"; +import { DesktopUpdateToast } from "@/components/desktop/desktop-update-toast"; import { GlobalLoadingProvider } from "@/components/providers/GlobalLoadingProvider"; import { I18nProvider } from "@/components/providers/I18nProvider"; import { PostHogProvider } from "@/components/providers/PostHogProvider"; @@ -155,7 +155,7 @@ export default function RootLayout({ {children} - + diff --git a/surfsense_web/components/desktop/DesktopUpdatePrompt.tsx b/surfsense_web/components/desktop/desktop-update-toast.tsx similarity index 92% rename from surfsense_web/components/desktop/DesktopUpdatePrompt.tsx rename to surfsense_web/components/desktop/desktop-update-toast.tsx index 091121141..367190709 100644 --- a/surfsense_web/components/desktop/DesktopUpdatePrompt.tsx +++ b/surfsense_web/components/desktop/desktop-update-toast.tsx @@ -5,12 +5,12 @@ import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; -type UpdatePromptState = { +type UpdateToastState = { version: string; }; -export function DesktopUpdatePrompt() { - const [update, setUpdate] = useState(null); +export function DesktopUpdateToast() { + const [update, setUpdate] = useState(null); useEffect(() => { const api = window.electronAPI; @@ -71,7 +71,7 @@ export function DesktopUpdatePrompt() { variant="ghost" size="icon" className="absolute right-2 top-2 size-7 text-muted-foreground hover:bg-transparent hover:text-foreground" - aria-label="Dismiss update prompt" + aria-label="Dismiss update toast" onClick={() => setUpdate(null)} > From baba31ab43107b4071537acd576606dd7523279c Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 26 May 2026 12:23:41 +0530 Subject: [PATCH 070/198] feat(tray): refactor context menu creation for improved screenshot functionality and quit option --- surfsense_desktop/src/modules/tray.ts | 41 ++++++++++++++++++--------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/surfsense_desktop/src/modules/tray.ts b/surfsense_desktop/src/modules/tray.ts index f0221fe53..e71168f6e 100644 --- a/surfsense_desktop/src/modules/tray.ts +++ b/surfsense_desktop/src/modules/tray.ts @@ -10,6 +10,30 @@ let tray: Tray | null = null; let registeredGeneralAssist: string | null = null; let registeredScreenshotAssist: string | null = null; +function buildContextMenu(screenshotAccelerator: string): Menu { + return Menu.buildFromTemplate([ + { label: 'Open SurfSense', click: () => showMainWindow('tray_menu') }, + { + label: 'Take Screenshot\u2026', + accelerator: screenshotAccelerator || undefined, + click: () => { + trackEvent('desktop_tray_screenshot_clicked'); + void Promise.resolve(runScreenshotAssistShortcut()).catch((err) => { + console.error('[tray] Screenshot Assist failed:', err); + }); + }, + }, + { type: 'separator' }, + { + label: 'Quit', + click: () => { + trackEvent('desktop_tray_quit_clicked'); + app.exit(0); + }, + }, + ]); +} + function getTrayIcon(): NativeImage { const iconName = process.platform === 'darwin' @@ -59,22 +83,10 @@ export async function createTray(): Promise { tray = new Tray(getTrayIcon()); tray.setToolTip('SurfSense'); - const contextMenu = Menu.buildFromTemplate([ - { label: 'Open SurfSense', click: () => showMainWindow('tray_menu') }, - { type: 'separator' }, - { - label: 'Quit', - click: () => { - trackEvent('desktop_tray_quit_clicked'); - app.exit(0); - }, - }, - ]); - - tray.setContextMenu(contextMenu); + const shortcuts = await getShortcuts(); + tray.setContextMenu(buildContextMenu(shortcuts.screenshotAssist)); tray.on('double-click', () => showMainWindow('tray_click')); - const shortcuts = await getShortcuts(); registeredGeneralAssist = registerOne( null, shortcuts.generalAssist, @@ -107,6 +119,7 @@ export async function reregisterScreenshotAssist(): Promise { runScreenshotAssistShortcut, 'Screenshot Assist' ); + tray?.setContextMenu(buildContextMenu(shortcuts.screenshotAssist)); } export function destroyTray(): void { From 0d65a2e4e3b70fef0776487a13f7640624a81860 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 26 May 2026 13:37:59 +0530 Subject: [PATCH 071/198] feat(web): implement composer suggestion popover and integrate with document mention picker --- .../components/assistant-ui/thread.tsx | 35 +- .../new-chat/composer-suggestion-popup.tsx | 158 ++++++++ .../new-chat/document-mention-picker.tsx | 374 ++++++++---------- .../components/new-chat/prompt-picker.tsx | 135 +++---- 4 files changed, 389 insertions(+), 313 deletions(-) create mode 100644 surfsense_web/components/new-chat/composer-suggestion-popup.tsx diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index c4f6fed05..990382bfd 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -65,6 +65,7 @@ import { } from "@/components/assistant-ui/inline-mention-editor"; import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; import { UserMessage } from "@/components/assistant-ui/user-message"; +import { ComposerSuggestionPopoverContent } from "@/components/new-chat/composer-suggestion-popup"; import { DocumentMentionPicker, type DocumentMentionPickerRef, @@ -90,6 +91,7 @@ import { DropdownMenuSubTrigger, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { Popover, PopoverAnchor } from "@/components/ui/popover"; import { Skeleton } from "@/components/ui/skeleton"; import { Switch } from "@/components/ui/switch"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; @@ -533,6 +535,11 @@ const Composer: FC = () => { } }, [showDocumentPopover]); + const handleDocumentPopoverOpenChange = useCallback((open: boolean) => { + setShowDocumentPopover(open); + if (!open) setMentionQuery(""); + }, []); + const handleActionTrigger = useCallback((query: string) => { setShowPromptPicker(true); setActionQuery(query); @@ -545,6 +552,11 @@ const Composer: FC = () => { } }, [showPromptPicker]); + const handlePromptPickerOpenChange = useCallback((open: boolean) => { + setShowPromptPicker(open); + if (!open) setActionQuery(""); + }, []); + const handleActionSelect = useCallback( (action: { name: string; prompt: string; mode: "transform" | "explore" }) => { let userText = editorRef.current?.getText() ?? ""; @@ -723,8 +735,9 @@ const Composer: FC = () => { currentUserId={currentUser?.id ?? null} members={members ?? []} /> - {showDocumentPopover && ( -
+ + + { initialSelectedDocuments={mentionedDocuments} externalSearch={mentionQuery} /> -
- )} - {showPromptPicker && ( -
+ + + + + { }} externalSearch={actionQuery} /> -
- )} + +
) { + return ( + { + event.preventDefault(); + onOpenAutoFocus?.(event); + }} + onCloseAutoFocus={(event) => { + event.preventDefault(); + onCloseAutoFocus?.(event); + }} + className={cn( + "w-[280px] overflow-hidden rounded-xl border border-popover-border bg-popover p-0 text-popover-foreground shadow-2xl sm:w-[320px]", + className + )} + {...props} + /> + ); +} + +const ComposerSuggestionList = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +ComposerSuggestionList.displayName = "ComposerSuggestionList"; + +function ComposerSuggestionGroup({ className, ...props }: React.HTMLAttributes) { + return
; +} + +function ComposerSuggestionGroupHeading({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ); +} + +const ComposerSuggestionItem = React.forwardRef< + HTMLButtonElement, + Omit, "variant"> & { + icon?: React.ReactNode; + selected?: boolean; + muted?: boolean; + } +>(({ className, children, icon, selected, muted, disabled, ...props }, ref) => ( + +)); +ComposerSuggestionItem.displayName = "ComposerSuggestionItem"; + +function ComposerSuggestionSeparator({ className, ...props }: React.ComponentProps) { + return ( +
+ +
+ ); +} + +function ComposerSuggestionMessage({ + className, + children, + variant = "muted", +}: React.HTMLAttributes & { variant?: "muted" | "destructive" }) { + return ( +
+

+ {children} +

+
+ ); +} + +function ComposerSuggestionSkeleton() { + return ( +
+
+ +
+ {["a", "b", "c", "d", "e"].map((id, index) => ( +
= 3 && "hidden sm:flex" + )} + > + + + + + + +
+ ))} +
+ ); +} + +export { + ComposerSuggestionPopoverContent, + ComposerSuggestionList, + ComposerSuggestionGroup, + ComposerSuggestionGroupHeading, + ComposerSuggestionItem, + ComposerSuggestionSeparator, + ComposerSuggestionMessage, + ComposerSuggestionSkeleton, +}; diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index c3b907266..d0f7fb67c 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -17,13 +17,20 @@ import { FOLDER_MENTION_DOCUMENT_TYPE, type MentionedDocumentInfo, } from "@/atoms/chat/mentioned-documents.atom"; -import { Button } from "@/components/ui/button"; -import { Skeleton } from "@/components/ui/skeleton"; +import { + ComposerSuggestionGroup, + ComposerSuggestionGroupHeading, + ComposerSuggestionItem, + ComposerSuggestionList, + ComposerSuggestionMessage, + ComposerSuggestionSeparator, + ComposerSuggestionSkeleton, +} from "@/components/new-chat/composer-suggestion-popup"; +import { Spinner } from "@/components/ui/spinner"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import type { Document, SearchDocumentTitlesResponse } from "@/contracts/types/document.types"; import { documentsApiService } from "@/lib/apis/documents-api.service"; import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; -import { cn } from "@/lib/utils"; import { queries } from "@/zero/queries"; export interface DocumentMentionPickerRef { @@ -427,221 +434,160 @@ export const DocumentMentionPicker = forwardRef< ); return ( -
- {/* Scrollable document list with responsive height */} -
- {actualLoading ? ( -
-
- + {actualLoading ? ( + + ) : actualDocuments.length > 0 || folderMentions.length > 0 ? ( + + {/* SurfSense Documentation */} + {surfsenseDocsList.length > 0 && ( + <> + SurfSense Docs + {surfsenseDocsList.map((doc) => { + const mention: MentionedDocumentInfo = { + id: doc.id, + title: doc.title, + document_type: doc.document_type, + kind: "doc", + }; + const docKey = getMentionDocKey(mention); + const isAlreadySelected = selectedKeys.has(docKey); + const selectableIndex = selectableMentions.findIndex( + (m) => getMentionDocKey(m) === docKey + ); + const isHighlighted = !isAlreadySelected && selectableIndex === highlightedIndex; + + return ( + { + if (el && selectableIndex >= 0) itemRefs.current.set(selectableIndex, el); + else if (selectableIndex >= 0) itemRefs.current.delete(selectableIndex); + }} + icon={getConnectorIcon(doc.document_type)} + selected={isHighlighted} + disabled={isAlreadySelected} + onClick={() => !isAlreadySelected && handleSelectMention(mention)} + onMouseEnter={() => { + if (!isAlreadySelected && selectableIndex >= 0) { + setHighlightedIndex(selectableIndex); + } + }} + > + + {doc.title} + + + ); + })} + + )} + + {/* User Documents */} + {userDocsList.length > 0 && ( + <> + {surfsenseDocsList.length > 0 && } + Your Documents + {userDocsList.map((doc) => { + const mention: MentionedDocumentInfo = { + id: doc.id, + title: doc.title, + document_type: doc.document_type, + kind: "doc", + }; + const docKey = getMentionDocKey(mention); + const isAlreadySelected = selectedKeys.has(docKey); + const selectableIndex = selectableMentions.findIndex( + (m) => getMentionDocKey(m) === docKey + ); + const isHighlighted = !isAlreadySelected && selectableIndex === highlightedIndex; + + return ( + { + if (el && selectableIndex >= 0) itemRefs.current.set(selectableIndex, el); + else if (selectableIndex >= 0) itemRefs.current.delete(selectableIndex); + }} + icon={getConnectorIcon(doc.document_type)} + selected={isHighlighted} + disabled={isAlreadySelected} + onClick={() => !isAlreadySelected && handleSelectMention(mention)} + onMouseEnter={() => { + if (!isAlreadySelected && selectableIndex >= 0) { + setHighlightedIndex(selectableIndex); + } + }} + > + + {doc.title} + + + ); + })} + + )} + + {/* Folders — single source of truth is Zero (same store + that powers the documents sidebar). Selecting a + folder inserts a folder chip whose path the agent + can walk with ``ls`` / ``find_documents``. */} + {folderMentions.length > 0 && ( + <> + {(surfsenseDocsList.length > 0 || userDocsList.length > 0) && ( + + )} + Folders + {folderMentions.map((folder) => { + const folderKey = getMentionDocKey(folder); + const isAlreadySelected = selectedKeys.has(folderKey); + const selectableIndex = selectableMentions.findIndex( + (m) => getMentionDocKey(m) === folderKey + ); + const isHighlighted = !isAlreadySelected && selectableIndex === highlightedIndex; + + return ( + { + if (el && selectableIndex >= 0) itemRefs.current.set(selectableIndex, el); + else if (selectableIndex >= 0) itemRefs.current.delete(selectableIndex); + }} + icon={} + selected={isHighlighted} + disabled={isAlreadySelected} + onClick={() => !isAlreadySelected && handleSelectMention(folder)} + onMouseEnter={() => { + if (!isAlreadySelected && selectableIndex >= 0) { + setHighlightedIndex(selectableIndex); + } + }} + > + + {folder.title} + + + ); + })} + + )} + + {/* Pagination loading indicator */} + {isLoadingMore && ( +
+
- {["a", "b", "c", "d", "e"].map((id, i) => ( -
= 3 && "hidden sm:flex" - )} - > - - - - - - -
- ))} -
- ) : actualDocuments.length > 0 || folderMentions.length > 0 ? ( -
- {/* SurfSense Documentation */} - {surfsenseDocsList.length > 0 && ( - <> -
- SurfSense Docs -
- {surfsenseDocsList.map((doc) => { - const mention: MentionedDocumentInfo = { - id: doc.id, - title: doc.title, - document_type: doc.document_type, - kind: "doc", - }; - const docKey = getMentionDocKey(mention); - const isAlreadySelected = selectedKeys.has(docKey); - const selectableIndex = selectableMentions.findIndex( - (m) => getMentionDocKey(m) === docKey - ); - const isHighlighted = !isAlreadySelected && selectableIndex === highlightedIndex; - - return ( - - ); - })} - - )} - - {/* User Documents */} - {userDocsList.length > 0 && ( - <> - {surfsenseDocsList.length > 0 && ( -
- )} -
- Your Documents -
- {userDocsList.map((doc) => { - const mention: MentionedDocumentInfo = { - id: doc.id, - title: doc.title, - document_type: doc.document_type, - kind: "doc", - }; - const docKey = getMentionDocKey(mention); - const isAlreadySelected = selectedKeys.has(docKey); - const selectableIndex = selectableMentions.findIndex( - (m) => getMentionDocKey(m) === docKey - ); - const isHighlighted = !isAlreadySelected && selectableIndex === highlightedIndex; - - return ( - - ); - })} - - )} - - {/* Folders — single source of truth is Zero (same store - that powers the documents sidebar). Selecting a - folder inserts a folder chip whose path the agent - can walk with ``ls`` / ``find_documents``. */} - {folderMentions.length > 0 && ( - <> - {(surfsenseDocsList.length > 0 || userDocsList.length > 0) && ( -
- )} -
Folders
- {folderMentions.map((folder) => { - const folderKey = getMentionDocKey(folder); - const isAlreadySelected = selectedKeys.has(folderKey); - const selectableIndex = selectableMentions.findIndex( - (m) => getMentionDocKey(m) === folderKey - ); - const isHighlighted = !isAlreadySelected && selectableIndex === highlightedIndex; - - return ( - - ); - })} - - )} - - {/* Pagination loading indicator */} - {isLoadingMore && ( -
-
-
- )} -
- ) : ( -
-

No matching documents

-
- )} -
-
+ )} + + ) : ( + No matching documents + )} + ); }); diff --git a/surfsense_web/components/new-chat/prompt-picker.tsx b/surfsense_web/components/new-chat/prompt-picker.tsx index 1cb9f80f5..b8fba5b61 100644 --- a/surfsense_web/components/new-chat/prompt-picker.tsx +++ b/surfsense_web/components/new-chat/prompt-picker.tsx @@ -15,9 +15,15 @@ import { } from "react"; import { promptsAtom } from "@/atoms/prompts/prompts-query.atoms"; -import { Button } from "@/components/ui/button"; -import { Skeleton } from "@/components/ui/skeleton"; -import { cn } from "@/lib/utils"; +import { + ComposerSuggestionGroup, + ComposerSuggestionGroupHeading, + ComposerSuggestionItem, + ComposerSuggestionList, + ComposerSuggestionMessage, + ComposerSuggestionSeparator, + ComposerSuggestionSkeleton, +} from "@/components/new-chat/composer-suggestion-popup"; export interface PromptPickerRef { selectHighlighted: () => void; @@ -119,91 +125,48 @@ export const PromptPicker = forwardRef(funct ); return ( -
-
- {isLoading ? ( -
-
- -
- {["a", "b", "c", "d", "e"].map((id, i) => ( -
= 3 && "hidden sm:flex" - )} - > - - - - - - -
- ))} -
- ) : isError ? ( -
-

Failed to load prompts

-
- ) : filtered.length === 0 ? ( -
-

No matching prompts

-
- ) : ( -
-
- Saved Prompts -
- {filtered.map((action, index) => ( - - ))} - -
- -
- )} -
-
+ {action.name} + + ))} + + + { + if (el) itemRefs.current.set(createPromptIndex, el); + else itemRefs.current.delete(createPromptIndex); + }} + icon={} + muted + selected={highlightedIndex === createPromptIndex} + onClick={() => handleSelect(createPromptIndex)} + onMouseEnter={() => setHighlightedIndex(createPromptIndex)} + > + Create prompt + + + )} + ); }); From d4459748384dd6ce9c5e9bce80d9ffa5ac6673c3 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 26 May 2026 14:36:46 +0530 Subject: [PATCH 072/198] feat(web): enhance inline mention editor and thread components with suggestion trigger info and anchor rect --- .../assistant-ui/inline-mention-editor.tsx | 54 ++++++- .../components/assistant-ui/thread.tsx | 141 ++++++++++++++---- .../new-chat/composer-suggestion-popup.tsx | 6 +- 3 files changed, 162 insertions(+), 39 deletions(-) diff --git a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx index 2c8ad6263..f67fb36dc 100644 --- a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx +++ b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx @@ -47,6 +47,17 @@ export type MentionChipInput = { kind?: MentionKind; }; +export type SuggestionAnchorRect = { + left: number; + top: number; + bottom: number; +}; + +export type SuggestionTriggerInfo = { + query: string; + anchorRect: SuggestionAnchorRect | null; +}; + export interface InlineMentionEditorRef { focus: () => void; clear: () => void; @@ -73,9 +84,9 @@ export interface InlineMentionEditorRef { interface InlineMentionEditorProps { placeholder?: string; - onMentionTrigger?: (query: string) => void; + onMentionTrigger?: (trigger: SuggestionTriggerInfo) => void; onMentionClose?: () => void; - onActionTrigger?: (query: string) => void; + onActionTrigger?: (trigger: SuggestionTriggerInfo) => void; onActionClose?: () => void; onSubmit?: () => void; onChange?: (text: string, docs: MentionedDocument[]) => void; @@ -299,6 +310,36 @@ function scanActiveTrigger(text: string, cursor: number) { return { triggerChar, query }; } +function rectToAnchor(rect: DOMRect): SuggestionAnchorRect { + return { + left: rect.left, + top: rect.top, + bottom: rect.bottom, + }; +} + +function getSelectionAnchorRect(root: HTMLElement | null): SuggestionAnchorRect | null { + if (!root || typeof window === "undefined") return null; + + const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0 || !selection.anchorNode) return null; + if (!root.contains(selection.anchorNode)) return null; + + const range = selection.getRangeAt(0).cloneRange(); + const rect = range.getClientRects()[0] ?? range.getBoundingClientRect(); + if (rect.width > 0 || rect.height > 0) return rectToAnchor(rect); + + if (range.collapsed && range.startContainer.nodeType === Node.TEXT_NODE && range.startOffset > 0) { + const fallbackRange = range.cloneRange(); + fallbackRange.setStart(range.startContainer, range.startOffset - 1); + fallbackRange.setEnd(range.startContainer, range.startOffset); + const fallbackRect = fallbackRange.getClientRects()[0] ?? fallbackRange.getBoundingClientRect(); + if (fallbackRect.width > 0 || fallbackRect.height > 0) return rectToAnchor(fallbackRect); + } + + return null; +} + export const InlineMentionEditor = forwardRef( ( { @@ -360,14 +401,19 @@ export const InlineMentionEditor = forwardRef + ); +} + +function getComposerSuggestionAnchorPoint( + triggerRect: SuggestionAnchorRect | null, + side: "top" | "bottom" +): ComposerSuggestionAnchorPoint | null { + if (!triggerRect) return null; + return { + left: triggerRect.left, + top: side === "bottom" ? triggerRect.bottom : triggerRect.top, + }; +} + export const Thread: FC = () => { return ; }; @@ -411,6 +441,8 @@ const Composer: FC = () => { const [showPromptPicker, setShowPromptPicker] = useState(false); const [mentionQuery, setMentionQuery] = useState(""); const [actionQuery, setActionQuery] = useState(""); + const [suggestionAnchorPoint, setSuggestionAnchorPoint] = + useState(null); const editorRef = useRef(null); const prevMentionedDocsRef = useRef>(new Map()); const documentPickerRef = useRef(null); @@ -491,6 +523,7 @@ const Composer: FC = () => { lastSeenSlideoutTickRef.current = slideoutOpenedTick; setShowDocumentPopover(false); setMentionQuery(""); + setSuggestionAnchorPoint(null); }, [slideoutOpenedTick]); // Sync editor text into assistant-ui's composer and mirror the chip @@ -523,38 +556,65 @@ const Composer: FC = () => { [aui, setMentionedDocuments] ); - const handleMentionTrigger = useCallback((query: string) => { + const handleMentionTrigger = useCallback((trigger: SuggestionTriggerInfo) => { + const anchorPoint = getComposerSuggestionAnchorPoint(trigger.anchorRect, "top"); + if (!anchorPoint) { + setShowDocumentPopover(false); + setMentionQuery(""); + setSuggestionAnchorPoint(null); + return; + } + setSuggestionAnchorPoint(anchorPoint); setShowDocumentPopover(true); - setMentionQuery(query); + setMentionQuery(trigger.query); }, []); const handleMentionClose = useCallback(() => { if (showDocumentPopover) { setShowDocumentPopover(false); setMentionQuery(""); + setSuggestionAnchorPoint(null); } }, [showDocumentPopover]); const handleDocumentPopoverOpenChange = useCallback((open: boolean) => { setShowDocumentPopover(open); - if (!open) setMentionQuery(""); + if (!open) { + setMentionQuery(""); + setSuggestionAnchorPoint(null); + } }, []); - const handleActionTrigger = useCallback((query: string) => { + const handleActionTrigger = useCallback((trigger: SuggestionTriggerInfo) => { + const anchorPoint = getComposerSuggestionAnchorPoint( + trigger.anchorRect, + clipboardInitialText ? "bottom" : "top" + ); + if (!anchorPoint) { + setShowPromptPicker(false); + setActionQuery(""); + setSuggestionAnchorPoint(null); + return; + } + setSuggestionAnchorPoint(anchorPoint); setShowPromptPicker(true); - setActionQuery(query); - }, []); + setActionQuery(trigger.query); + }, [clipboardInitialText]); const handleActionClose = useCallback(() => { if (showPromptPicker) { setShowPromptPicker(false); setActionQuery(""); + setSuggestionAnchorPoint(null); } }, [showPromptPicker]); const handlePromptPickerOpenChange = useCallback((open: boolean) => { setShowPromptPicker(open); - if (!open) setActionQuery(""); + if (!open) { + setActionQuery(""); + setSuggestionAnchorPoint(null); + } }, []); const handleActionSelect = useCallback( @@ -573,6 +633,7 @@ const Composer: FC = () => { aui.composer().setText(finalPrompt); setShowPromptPicker(false); setActionQuery(""); + setSuggestionAnchorPoint(null); }, [actionQuery, aui] ); @@ -588,6 +649,7 @@ const Composer: FC = () => { aui.composer().setText(finalPrompt); setShowPromptPicker(false); setActionQuery(""); + setSuggestionAnchorPoint(null); setClipboardInitialText(undefined); }, [clipboardInitialText, electronAPI, aui] @@ -616,6 +678,7 @@ const Composer: FC = () => { e.preventDefault(); setShowPromptPicker(false); setActionQuery(""); + setSuggestionAnchorPoint(null); return; } } @@ -639,6 +702,7 @@ const Composer: FC = () => { e.preventDefault(); setShowDocumentPopover(false); setMentionQuery(""); + setSuggestionAnchorPoint(null); return; } } @@ -699,6 +763,7 @@ const Composer: FC = () => { // Atom is reconciled by ``handleEditorChange`` via the editor's // onChange — no second write path here. setMentionQuery(""); + setSuggestionAnchorPoint(null); }, []); useEffect(() => { @@ -736,34 +801,44 @@ const Composer: FC = () => { members={members ?? []} /> - - - { - setShowDocumentPopover(false); - setMentionQuery(""); - }} - initialSelectedDocuments={mentionedDocuments} - externalSearch={mentionQuery} - /> - + {suggestionAnchorPoint ? ( + <> + + + { + setShowDocumentPopover(false); + setMentionQuery(""); + setSuggestionAnchorPoint(null); + }} + initialSelectedDocuments={mentionedDocuments} + externalSearch={mentionQuery} + /> + + + ) : null} - - - { - setShowPromptPicker(false); - setActionQuery(""); - }} - externalSearch={actionQuery} - /> - + {suggestionAnchorPoint ? ( + <> + + + { + setShowPromptPicker(false); + setActionQuery(""); + setSuggestionAnchorPoint(null); + }} + externalSearch={actionQuery} + /> + + + ) : null}
{ event.preventDefault(); onOpenAutoFocus?.(event); @@ -28,7 +30,7 @@ function ComposerSuggestionPopoverContent({ onCloseAutoFocus?.(event); }} className={cn( - "w-[280px] overflow-hidden rounded-xl border border-popover-border bg-popover p-0 text-popover-foreground shadow-2xl sm:w-[320px]", + "w-[280px] overflow-hidden rounded-md border border-popover-border bg-popover p-0 text-popover-foreground shadow-md sm:w-[320px]", className )} {...props} From 9cd3de9ec1ba1e2fc1331feefc163ac4be5aeb49 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 26 May 2026 16:50:40 +0530 Subject: [PATCH 073/198] feat(web): update mention components to use Button component and enhance styling --- .../components/assistant-ui/inline-mention-editor.tsx | 11 +++++++---- .../components/assistant-ui/mention-chip.tsx | 9 ++++++--- surfsense_web/components/assistant-ui/thread.tsx | 6 +++--- .../components/new-chat/composer-suggestion-popup.tsx | 3 +++ 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx index f67fb36dc..67466532e 100644 --- a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx +++ b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx @@ -21,6 +21,7 @@ import { useRef, } from "react"; import { FOLDER_MENTION_DOCUMENT_TYPE } from "@/atoms/chat/mentioned-documents.atom"; +import { Button } from "@/components/ui/button"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import type { Document } from "@/contracts/types/document.types"; import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; @@ -160,8 +161,10 @@ const MentionElement: FC> = ({ )} {ctx ? ( - + + ) : null} diff --git a/surfsense_web/components/assistant-ui/mention-chip.tsx b/surfsense_web/components/assistant-ui/mention-chip.tsx index 7197fccdc..526d4b8b4 100644 --- a/surfsense_web/components/assistant-ui/mention-chip.tsx +++ b/surfsense_web/components/assistant-ui/mention-chip.tsx @@ -1,6 +1,7 @@ "use client"; import type { MouseEventHandler, ReactNode } from "react"; +import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; @@ -60,13 +61,15 @@ export function MentionChip({ const isInteractive = Boolean(onClick) && !disabled; const chip = ( - + ); if (!tooltip) return chip; diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 39e1b4360..72fb2690b 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -564,7 +564,7 @@ const Composer: FC = () => { setSuggestionAnchorPoint(null); return; } - setSuggestionAnchorPoint(anchorPoint); + setSuggestionAnchorPoint((current) => current ?? anchorPoint); setShowDocumentPopover(true); setMentionQuery(trigger.query); }, []); @@ -596,7 +596,7 @@ const Composer: FC = () => { setSuggestionAnchorPoint(null); return; } - setSuggestionAnchorPoint(anchorPoint); + setSuggestionAnchorPoint((current) => current ?? anchorPoint); setShowPromptPicker(true); setActionQuery(trigger.query); }, [clipboardInitialText]); @@ -866,7 +866,7 @@ const Composer: FC = () => { onDocumentRemove={handleDocumentRemove} onSubmit={handleSubmit} onKeyDown={handleKeyDown} - className="min-h-[24px]" + className="min-h-[24px] **:data-slate-placeholder:font-semibold" />
diff --git a/surfsense_web/components/new-chat/composer-suggestion-popup.tsx b/surfsense_web/components/new-chat/composer-suggestion-popup.tsx index d3e7b75bb..fd46d8ee7 100644 --- a/surfsense_web/components/new-chat/composer-suggestion-popup.tsx +++ b/surfsense_web/components/new-chat/composer-suggestion-popup.tsx @@ -14,6 +14,7 @@ function ComposerSuggestionPopoverContent({ collisionPadding = 12, onOpenAutoFocus, onCloseAutoFocus, + style, ...props }: React.ComponentProps) { return ( @@ -31,8 +32,10 @@ function ComposerSuggestionPopoverContent({ }} className={cn( "w-[280px] overflow-hidden rounded-md border border-popover-border bg-popover p-0 text-popover-foreground shadow-md sm:w-[320px]", + "data-[state=open]:!animate-none data-[state=closed]:!animate-none data-[state=open]:!duration-0 data-[state=closed]:!duration-0", className )} + style={{ ...style, animation: "none" }} {...props} /> ); From 701ae800b48fba44bd3443014737c0a31479985d Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 26 May 2026 18:27:02 +0530 Subject: [PATCH 074/198] feat(web): refactor sign-in button and composer components to use Button component --- surfsense_web/components/assistant-ui/thread.tsx | 9 +++++---- surfsense_web/components/auth/sign-in-button.tsx | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 72fb2690b..a68560d23 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -866,7 +866,7 @@ const Composer: FC = () => { onDocumentRemove={handleDocumentRemove} onSubmit={handleSubmit} onKeyDown={handleKeyDown} - className="min-h-[24px] **:data-slate-placeholder:font-semibold" + className="min-h-[24px] **:data-slate-placeholder:font-normal" />
@@ -1121,9 +1121,10 @@ const ComposerAction: FC = ({ isBlockedByOtherUser = false >
- + { if (isGoogleAuth) { return ( - + ); } From a41b16b73e29758c5ab4722787d49c3bedc81f14 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 26 May 2026 21:11:53 +0530 Subject: [PATCH 075/198] feat(web): enhance chat context and mention handling with connector support --- .../app/agents/new_chat/context.py | 2 + .../app/agents/new_chat/mention_resolver.py | 2 +- .../app/routes/new_chat_routes.py | 14 + surfsense_backend/app/schemas/new_chat.py | 21 +- .../app/tasks/chat/persistence.py | 22 +- .../app/tasks/chat/stream_new_chat.py | 30 + .../new-chat/[[...chat_id]]/page.tsx | 72 +- .../atoms/chat/mentioned-documents.atom.ts | 50 +- .../assistant-ui/inline-mention-editor.tsx | 26 +- .../components/assistant-ui/thread.tsx | 45 +- .../components/assistant-ui/user-message.tsx | 19 +- .../layout/ui/sidebar/DocumentsSidebar.tsx | 5 +- .../new-chat/document-mention-picker.tsx | 792 +++++++++--------- .../use-composer-suggestion-navigator.ts | 120 +++ surfsense_web/lib/chat/mention-doc-key.ts | 2 +- 15 files changed, 773 insertions(+), 449 deletions(-) create mode 100644 surfsense_web/components/new-chat/use-composer-suggestion-navigator.ts diff --git a/surfsense_backend/app/agents/new_chat/context.py b/surfsense_backend/app/agents/new_chat/context.py index a20a43a66..1b3ea3d20 100644 --- a/surfsense_backend/app/agents/new_chat/context.py +++ b/surfsense_backend/app/agents/new_chat/context.py @@ -64,6 +64,8 @@ class SurfSenseContextSchema: search_space_id: int | None = None mentioned_document_ids: list[int] = field(default_factory=list) mentioned_folder_ids: list[int] = field(default_factory=list) + mentioned_connector_ids: list[int] = field(default_factory=list) + mentioned_connectors: list[dict[str, object]] = field(default_factory=list) file_operation_contract: FileOperationContractState | None = None turn_id: str | None = None request_id: str | None = None diff --git a/surfsense_backend/app/agents/new_chat/mention_resolver.py b/surfsense_backend/app/agents/new_chat/mention_resolver.py index 00bb7e71f..6a025b947 100644 --- a/surfsense_backend/app/agents/new_chat/mention_resolver.py +++ b/surfsense_backend/app/agents/new_chat/mention_resolver.py @@ -134,7 +134,7 @@ async def resolve_mentions( kind = chip.kind if kind == "folder": chip_folder_ids.append(chip.id) - else: + elif kind == "doc": chip_doc_ids.append(chip.id) chip_titles_by_id[(kind, chip.id)] = chip.title diff --git a/surfsense_backend/app/routes/new_chat_routes.py b/surfsense_backend/app/routes/new_chat_routes.py index 44fc1c392..fb4d5a049 100644 --- a/surfsense_backend/app/routes/new_chat_routes.py +++ b/surfsense_backend/app/routes/new_chat_routes.py @@ -1771,6 +1771,11 @@ async def handle_new_chat( if request.mentioned_documents else None ) + mentioned_connectors_payload = ( + [doc.model_dump() for doc in request.mentioned_connectors] + if request.mentioned_connectors + else None + ) return StreamingResponse( stream_new_chat( @@ -1782,6 +1787,8 @@ async def handle_new_chat( mentioned_document_ids=request.mentioned_document_ids, mentioned_surfsense_doc_ids=request.mentioned_surfsense_doc_ids, mentioned_folder_ids=request.mentioned_folder_ids, + mentioned_connector_ids=request.mentioned_connector_ids, + mentioned_connectors=mentioned_connectors_payload, mentioned_documents=mentioned_documents_payload, needs_history_bootstrap=thread.needs_history_bootstrap, thread_visibility=thread.visibility, @@ -2258,6 +2265,11 @@ async def regenerate_response( if request.mentioned_documents else None ) + mentioned_connectors_payload = ( + [doc.model_dump() for doc in request.mentioned_connectors] + if request.mentioned_connectors + else None + ) try: async for chunk in stream_new_chat( user_query=str(user_query_to_use), @@ -2268,6 +2280,8 @@ async def regenerate_response( mentioned_document_ids=request.mentioned_document_ids, mentioned_surfsense_doc_ids=request.mentioned_surfsense_doc_ids, mentioned_folder_ids=request.mentioned_folder_ids, + mentioned_connector_ids=request.mentioned_connector_ids, + mentioned_connectors=mentioned_connectors_payload, mentioned_documents=mentioned_documents_payload, checkpoint_id=target_checkpoint_id, needs_history_bootstrap=thread.needs_history_bootstrap, diff --git a/surfsense_backend/app/schemas/new_chat.py b/surfsense_backend/app/schemas/new_chat.py index c5315cce5..c721f495e 100644 --- a/surfsense_backend/app/schemas/new_chat.py +++ b/surfsense_backend/app/schemas/new_chat.py @@ -218,17 +218,20 @@ class MentionedDocumentInfo(BaseModel): id: int title: str = Field(..., min_length=1, max_length=500) document_type: str = Field(..., min_length=1, max_length=100) - kind: Literal["doc", "folder"] = Field( + kind: Literal["doc", "folder", "connector"] = Field( default="doc", description=( "Discriminator for the chip's referent: ``doc`` is a " "knowledge-base ``Document`` row, ``folder`` is a " - "knowledge-base ``Folder`` row. Folders carry the sentinel " + "knowledge-base ``Folder`` row, and ``connector`` is a " + "concrete connected account. Folders carry the sentinel " "``document_type='FOLDER'`` to keep the frontend dedup key " "``(kind:document_type:id)`` from colliding doc and folder " "ids that happen to share an integer value." ), ) + connector_type: str | None = Field(default=None, max_length=100) + account_name: str | None = Field(default=None, max_length=255) class NewChatRequest(BaseModel): @@ -266,6 +269,18 @@ class NewChatRequest(BaseModel): "a mentioned-documents part." ), ) + mentioned_connector_ids: list[int] | None = Field( + default=None, + description="Optional concrete connector account IDs the user @-mentioned.", + ) + mentioned_connectors: list[MentionedDocumentInfo] | None = Field( + default=None, + description=( + "Display/context metadata for selected connector accounts. " + "Kept separate from document/folder id arrays so tools can " + "prefer the exact account the user selected." + ), + ) disabled_tools: list[str] | None = ( None # Optional list of tool names the user has disabled from the UI ) @@ -335,6 +350,8 @@ class RegenerateRequest(BaseModel): "new user message. None means no chip metadata." ), ) + mentioned_connector_ids: list[int] | None = None + mentioned_connectors: list[MentionedDocumentInfo] | None = None disabled_tools: list[str] | None = None filesystem_mode: Literal["cloud", "desktop_local_folder"] = "cloud" client_platform: Literal["web", "desktop"] = "web" diff --git a/surfsense_backend/app/tasks/chat/persistence.py b/surfsense_backend/app/tasks/chat/persistence.py index 37be50705..07266cf69 100644 --- a/surfsense_backend/app/tasks/chat/persistence.py +++ b/surfsense_backend/app/tasks/chat/persistence.py @@ -137,15 +137,19 @@ def _build_user_content( if doc_id is None or title is None or document_type is None: continue kind_raw = doc.get("kind", "doc") - kind = kind_raw if kind_raw in ("doc", "folder") else "doc" - normalized.append( - { - "id": doc_id, - "title": str(title), - "document_type": str(document_type), - "kind": kind, - } - ) + kind = kind_raw if kind_raw in ("doc", "folder", "connector") else "doc" + item = { + "id": doc_id, + "title": str(title), + "document_type": str(document_type), + "kind": kind, + } + if kind == "connector": + connector_type = doc.get("connector_type") or document_type + account_name = doc.get("account_name") or title + item["connector_type"] = str(connector_type) + item["account_name"] = str(account_name) + normalized.append(item) if normalized: parts.append({"type": "mentioned-documents", "documents": normalized}) return parts diff --git a/surfsense_backend/app/tasks/chat/stream_new_chat.py b/surfsense_backend/app/tasks/chat/stream_new_chat.py index fee50d72d..81c801959 100644 --- a/surfsense_backend/app/tasks/chat/stream_new_chat.py +++ b/surfsense_backend/app/tasks/chat/stream_new_chat.py @@ -839,6 +839,8 @@ async def stream_new_chat( mentioned_document_ids: list[int] | None = None, mentioned_surfsense_doc_ids: list[int] | None = None, mentioned_folder_ids: list[int] | None = None, + mentioned_connector_ids: list[int] | None = None, + mentioned_connectors: list[dict[str, Any]] | None = None, mentioned_documents: list[dict[str, Any]] | None = None, checkpoint_id: str | None = None, needs_history_bootstrap: bool = False, @@ -1385,6 +1387,32 @@ async def stream_new_chat( format_mentioned_surfsense_docs_as_context(mentioned_surfsense_docs) ) + if mentioned_connectors: + connector_lines = [] + for connector in mentioned_connectors: + if not isinstance(connector, dict): + continue + connector_id = connector.get("id") + connector_type = connector.get("connector_type") or connector.get( + "document_type" + ) + account_name = connector.get("account_name") or connector.get("title") + if connector_id is None or connector_type is None: + continue + connector_lines.append( + f' - connector_id={connector_id}, connector_type="{connector_type}", ' + f'account="{account_name or ""}"' + ) + if connector_lines: + context_parts.append( + "\n" + "The user selected these exact connector accounts with @. " + "For read, write, or HITL tool calls involving these services, " + "prefer the matching connector_id instead of guessing from available accounts:\n" + + "\n".join(connector_lines) + + "\n" + ) + # Surface report IDs prominently so the LLM doesn't have to # retrieve them from old tool responses in conversation history. if recent_reports: @@ -1778,6 +1806,8 @@ async def stream_new_chat( mentioned_folder_ids=list( accepted_folder_ids or mentioned_folder_ids or [] ), + mentioned_connector_ids=list(mentioned_connector_ids or []), + mentioned_connectors=list(mentioned_connectors or []), request_id=request_id, turn_id=stream_result.turn_id, ) diff --git a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx index ecd5ab6b1..8d1f5da46 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx @@ -208,9 +208,11 @@ const MentionedDocumentInfoSchema = z.object({ title: z.string(), document_type: z.string(), kind: z - .union([z.literal("doc"), z.literal("folder")]) + .union([z.literal("doc"), z.literal("folder"), z.literal("connector")]) .optional() .default("doc"), + connector_type: z.string().optional(), + account_name: z.string().optional(), }); const MentionedDocumentsPartSchema = z.object({ @@ -227,7 +229,32 @@ function extractMentionedDocuments(content: unknown): MentionedDocumentInfo[] { for (const part of content) { const result = MentionedDocumentsPartSchema.safeParse(part); if (result.success) { - return result.data.documents; + return result.data.documents.map((doc) => { + if (doc.kind === "connector") { + return { + id: doc.id, + title: doc.title, + document_type: doc.document_type, + kind: "connector", + connector_type: doc.connector_type ?? doc.document_type, + account_name: doc.account_name ?? doc.title, + }; + } + if (doc.kind === "folder") { + return { + id: doc.id, + title: doc.title, + document_type: "FOLDER", + kind: "folder", + }; + } + return { + id: doc.id, + title: doc.title, + document_type: doc.document_type, + kind: "doc", + }; + }); } } @@ -924,7 +951,8 @@ export default function NewChatPage() { hasMentionedDocuments: mentionedDocumentIds.surfsense_doc_ids.length > 0 || mentionedDocumentIds.document_ids.length > 0 || - mentionedDocumentIds.folder_ids.length > 0, + mentionedDocumentIds.folder_ids.length > 0 || + mentionedDocumentIds.connector_ids.length > 0, messageLength: userQuery.length, }); @@ -940,12 +968,7 @@ export default function NewChatPage() { const key = `${doc.kind}:${doc.document_type}:${doc.id}`; if (seenDocKeys.has(key)) continue; seenDocKeys.add(key); - allMentionedDocs.push({ - id: doc.id, - title: doc.title, - document_type: doc.document_type, - kind: doc.kind, - }); + allMentionedDocs.push(doc); } if (allMentionedDocs.length > 0) { @@ -1008,9 +1031,10 @@ export default function NewChatPage() { const hasDocumentIds = mentionedDocumentIds.document_ids.length > 0; const hasSurfsenseDocIds = mentionedDocumentIds.surfsense_doc_ids.length > 0; const hasFolderIds = mentionedDocumentIds.folder_ids.length > 0; + const hasConnectorIds = mentionedDocumentIds.connector_ids.length > 0; // Clear mentioned documents after capturing them - if (hasDocumentIds || hasSurfsenseDocIds || hasFolderIds) { + if (hasDocumentIds || hasSurfsenseDocIds || hasFolderIds || hasConnectorIds) { setMentionedDocuments([]); } @@ -1036,20 +1060,16 @@ export default function NewChatPage() { ? mentionedDocumentIds.surfsense_doc_ids : undefined, mentioned_folder_ids: hasFolderIds ? mentionedDocumentIds.folder_ids : undefined, + mentioned_connector_ids: hasConnectorIds + ? mentionedDocumentIds.connector_ids + : undefined, + mentioned_connectors: hasConnectorIds ? mentionedDocumentIds.connectors : undefined, // Full mention metadata (docs + folders, with // ``kind`` discriminator) so the BE can embed a // ``mentioned-documents`` ContentPart on the // persisted user message (replaces the old FE-side // injection in ``persistUserTurn``). - mentioned_documents: - allMentionedDocs.length > 0 - ? allMentionedDocs.map((d) => ({ - id: d.id, - title: d.title, - document_type: d.document_type, - kind: d.kind, - })) - : undefined, + mentioned_documents: allMentionedDocs.length > 0 ? allMentionedDocs : undefined, disabled_tools: disabledTools.length > 0 ? disabledTools : undefined, ...(userImages.length > 0 ? { user_images: userImages } : {}), }), @@ -1945,6 +1965,7 @@ export default function NewChatPage() { const regenerateFolderIds = sourceMentionedDocs .filter((d) => d.kind === "folder") .map((d) => d.id); + const regenerateConnectors = sourceMentionedDocs.filter((d) => d.kind === "connector"); const requestBody: Record = { search_space_id: searchSpaceId, @@ -1957,19 +1978,16 @@ export default function NewChatPage() { mentioned_surfsense_doc_ids: regenerateSurfsenseDocIds.length > 0 ? regenerateSurfsenseDocIds : undefined, mentioned_folder_ids: regenerateFolderIds.length > 0 ? regenerateFolderIds : undefined, + mentioned_connector_ids: + regenerateConnectors.length > 0 ? regenerateConnectors.map((d) => d.id) : undefined, + mentioned_connectors: + regenerateConnectors.length > 0 ? regenerateConnectors : undefined, // Full mention metadata for the regenerate-specific // source list. Only meaningful for edit (the BE only // re-persists a user row when ``user_query`` is set); // reload reuses the original turn's mentioned_documents. mentioned_documents: - sourceMentionedDocs.length > 0 - ? sourceMentionedDocs.map((d) => ({ - id: d.id, - title: d.title, - document_type: d.document_type, - kind: d.kind, - })) - : undefined, + sourceMentionedDocs.length > 0 ? sourceMentionedDocs : undefined, }; if (isEdit) { requestBody.user_images = editExtras?.userImages ?? []; diff --git a/surfsense_web/atoms/chat/mentioned-documents.atom.ts b/surfsense_web/atoms/chat/mentioned-documents.atom.ts index 9163960f4..9efd2b7fe 100644 --- a/surfsense_web/atoms/chat/mentioned-documents.atom.ts +++ b/surfsense_web/atoms/chat/mentioned-documents.atom.ts @@ -13,18 +13,31 @@ export const FOLDER_MENTION_DOCUMENT_TYPE = "FOLDER"; /** * Display metadata for a single ``@``-mention chip. * - * The ``kind`` discriminator identifies whether the chip is a - * knowledge-base document or a knowledge-base folder. Folders carry - * the sentinel ``document_type === FOLDER_MENTION_DOCUMENT_TYPE`` so - * the editor, picker, and persisted ``mentioned-documents`` content - * part all stay aligned with the backend Pydantic schema. + * Historical name is retained because this atom is already wired into + * chat persistence and sidebar selection. The shape is now the selected + * composer context, not only documents. */ -export interface MentionedDocumentInfo { - id: number; - title: string; - document_type: string; - kind: "doc" | "folder"; -} +export type MentionedDocumentInfo = + | { + id: number; + title: string; + document_type: string; + kind: "doc"; + } + | { + id: number; + title: string; + document_type: typeof FOLDER_MENTION_DOCUMENT_TYPE; + kind: "folder"; + } + | { + id: number; + title: string; + document_type: string; + kind: "connector"; + connector_type: string; + account_name: string; + }; /** * Backwards-compatible doc-only chip shape for legacy callers that @@ -44,7 +57,10 @@ type LegacyDocMention = Pick; export function toMentionedDocumentInfo( input: LegacyDocMention | MentionedDocumentInfo ): MentionedDocumentInfo { - if ("kind" in input && (input.kind === "doc" || input.kind === "folder")) { + if ( + "kind" in input && + (input.kind === "doc" || input.kind === "folder" || input.kind === "connector") + ) { return input; } return { @@ -93,12 +109,22 @@ export const mentionedDocumentIdsAtom = atom((get) => { }); const docs = deduped.filter((m) => m.kind === "doc"); const folders = deduped.filter((m) => m.kind === "folder"); + const connectors = deduped.filter((m) => m.kind === "connector"); return { surfsense_doc_ids: docs .filter((doc) => doc.document_type === "SURFSENSE_DOCS") .map((doc) => doc.id), document_ids: docs.filter((doc) => doc.document_type !== "SURFSENSE_DOCS").map((doc) => doc.id), folder_ids: folders.map((f) => f.id), + connector_ids: connectors.map((c) => c.id), + connectors: connectors.map((c) => ({ + id: c.id, + title: c.title, + document_type: c.document_type, + kind: c.kind, + connector_type: c.connector_type, + account_name: c.account_name, + })), }; }); diff --git a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx index 67466532e..b93ea253d 100644 --- a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx +++ b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx @@ -1,6 +1,6 @@ "use client"; -import { Folder as FolderIcon, X as XIcon } from "lucide-react"; +import { Folder as FolderIcon, Plug as PlugIcon, X as XIcon } from "lucide-react"; import type { NodeEntry, TElement } from "platejs"; import type { PlateElementProps } from "platejs/react"; import { @@ -27,13 +27,15 @@ import type { Document } from "@/contracts/types/document.types"; import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; import { cn } from "@/lib/utils"; -export type MentionKind = "doc" | "folder"; +export type MentionKind = "doc" | "folder" | "connector"; export interface MentionedDocument { id: number; title: string; document_type?: string; kind: MentionKind; + connector_type?: string; + account_name?: string; } /** @@ -46,6 +48,8 @@ export type MentionChipInput = { title: string; document_type?: string; kind?: MentionKind; + connector_type?: string; + account_name?: string; }; export type SuggestionAnchorRect = { @@ -107,6 +111,8 @@ type MentionElementNode = { document_type?: string; /** Discriminator; defaults to ``"doc"`` for legacy nodes. */ kind?: MentionKind; + connector_type?: string; + account_name?: string; statusLabel?: string | null; statusKind?: MentionStatusKind; children: [{ text: "" }]; @@ -146,6 +152,7 @@ const MentionElement: FC> = ({ : "text-amber-700"; const isFolder = element.kind === "folder"; + const isConnector = element.kind === "connector"; const ctx = useContext(MentionEditorContext); return ( @@ -156,6 +163,10 @@ const MentionElement: FC> = ({ {isFolder ? ( + ) : isConnector ? ( + getConnectorIcon(element.connector_type ?? element.document_type ?? "UNKNOWN", "h-3 w-3") ?? ( + + ) ) : ( getConnectorIcon(element.document_type ?? "UNKNOWN", "h-3 w-3") )} @@ -242,6 +253,8 @@ function getMentionedDocuments(value: ComposerValue): MentionedDocument[] { title: node.title, document_type: node.document_type, kind, + connector_type: node.connector_type, + account_name: node.account_name, }; map.set(getMentionDocKey(doc), doc); } @@ -444,13 +457,20 @@ export const InlineMentionEditor = forwardRef { return prev; } } - return docs.map((d) => ({ - id: d.id, - title: d.title, - // Atom requires a string; ``"UNKNOWN"`` matches the - // sentinel ``getMentionDocKey`` and the editor's - // match predicates use. - document_type: d.document_type ?? "UNKNOWN", - kind: d.kind, - })); + return docs.map((d) => { + const documentType = d.document_type ?? "UNKNOWN"; + if (d.kind === "connector") { + return { + id: d.id, + title: d.title, + document_type: documentType, + kind: "connector", + connector_type: d.connector_type ?? documentType, + account_name: d.account_name ?? d.title, + }; + } + if (d.kind === "folder") { + return { + id: d.id, + title: d.title, + document_type: FOLDER_MENTION_DOCUMENT_TYPE, + kind: "folder", + }; + } + return { + id: d.id, + title: d.title, + // Atom requires a string; ``"UNKNOWN"`` matches the + // sentinel ``getMentionDocKey`` and the editor's + // match predicates use. + document_type: documentType, + kind: "doc", + }; + }); }); }, [aui, setMentionedDocuments] @@ -700,6 +722,9 @@ const Composer: FC = () => { } if (e.key === "Escape") { e.preventDefault(); + if (documentPickerRef.current?.goBack()) { + return; + } setShowDocumentPopover(false); setMentionQuery(""); setSuggestionAnchorPoint(null); diff --git a/surfsense_web/components/assistant-ui/user-message.tsx b/surfsense_web/components/assistant-ui/user-message.tsx index d17788c71..3e6dc829a 100644 --- a/surfsense_web/components/assistant-ui/user-message.tsx +++ b/surfsense_web/components/assistant-ui/user-message.tsx @@ -6,7 +6,7 @@ import { useMessagePartText, } from "@assistant-ui/react"; import { useAtomValue, useSetAtom } from "jotai"; -import { CheckIcon, CopyIcon, Folder as FolderIcon, Pencil } from "lucide-react"; +import { CheckIcon, CopyIcon, Folder as FolderIcon, Pencil, Plug } from "lucide-react"; import Image from "next/image"; import { useParams } from "next/navigation"; import { type FC, useCallback, useState } from "react"; @@ -100,8 +100,13 @@ const UserTextPart: FC = () => { return {segment.value}; } const isFolder = segment.doc.kind === "folder"; + const isConnector = segment.doc.kind === "connector"; const icon = isFolder ? ( + ) : isConnector ? ( + getConnectorIcon(segment.doc.connector_type ?? segment.doc.document_type, "size-3.5") ?? ( + + ) ) : ( getConnectorIcon(segment.doc.document_type ?? "UNKNOWN", "size-3.5") ); @@ -110,8 +115,16 @@ const UserTextPart: FC = () => { key={`mention-${getMentionDocKey(segment.doc)}-${segment.start}`} icon={icon} label={segment.doc.title} - tooltip={isFolder ? `Folder: ${segment.doc.title}` : segment.doc.title} - onClick={isFolder ? undefined : () => handleOpenDoc(segment.doc.id, segment.doc.title)} + tooltip={ + isFolder + ? `Folder: ${segment.doc.title}` + : isConnector + ? `Connector account: ${segment.doc.title}` + : segment.doc.title + } + onClick={ + isFolder || isConnector ? undefined : () => handleOpenDoc(segment.doc.id, segment.doc.title) + } className="mx-0.5" /> ); diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx index 503ca239c..ca90ba9b9 100644 --- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx @@ -1063,6 +1063,7 @@ function AuthenticatedDocumentsSidebarBase({ const treeDocMap = new Map(treeDocuments.map((d) => [d.id, d])); return sidebarDocs .filter((doc) => { + if (doc.kind !== "doc") return false; const fullDoc = treeDocMap.get(doc.id); if (!fullDoc) return false; const state = fullDoc.status?.state ?? "ready"; @@ -1124,7 +1125,7 @@ function AuthenticatedDocumentsSidebarBase({ try { await deleteDocumentMutation({ id }); toast.success(t("delete_success") || "Document deleted"); - setSidebarDocs((prev) => prev.filter((d) => d.id !== id)); + setSidebarDocs((prev) => prev.filter((d) => d.kind !== "doc" || d.id !== id)); return true; } catch (e) { console.error("Error deleting document:", e); @@ -1953,7 +1954,7 @@ function AnonymousDocumentsSidebar({ onEditDocument={() => gate("edit documents")} onDeleteDocument={async () => { handleRemoveDoc(); - setSidebarDocs((prev) => prev.filter((d) => d.id !== -1)); + setSidebarDocs((prev) => prev.filter((d) => d.kind !== "doc" || d.id !== -1)); return true; }} onMoveDocument={() => gate("organize documents")} diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index d0f7fb67c..f8a84c51b 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -2,21 +2,35 @@ import { useQuery as useZeroQuery } from "@rocicorp/zero/react"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; -import { Folder as FolderIcon } from "lucide-react"; +import { + BookOpen, + ChevronLeft, + ChevronRight, + Files, + Folder as FolderIcon, + Plug, +} from "lucide-react"; import { forwardRef, useCallback, useDeferredValue, useEffect, - useImperativeHandle, useMemo, useRef, useState, } from "react"; +import type * as React from "react"; import { FOLDER_MENTION_DOCUMENT_TYPE, type MentionedDocumentInfo, } from "@/atoms/chat/mentioned-documents.atom"; +import { useAtomValue } from "jotai"; +import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms"; +import { + COMPOSIO_CONNECTORS, + OAUTH_CONNECTORS, +} from "@/components/assistant-ui/connector-popup/constants/connector-constants"; +import { getConnectorDisplayName } from "@/components/assistant-ui/connector-popup/tabs/all-connectors-tab"; import { ComposerSuggestionGroup, ComposerSuggestionGroupHeading, @@ -26,18 +40,20 @@ import { ComposerSuggestionSeparator, ComposerSuggestionSkeleton, } from "@/components/new-chat/composer-suggestion-popup"; +import { + type ComposerSuggestionNavigatorRef, + type ComposerSuggestionNode, + useComposerSuggestionNavigator, +} from "@/components/new-chat/use-composer-suggestion-navigator"; import { Spinner } from "@/components/ui/spinner"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; +import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import type { Document, SearchDocumentTitlesResponse } from "@/contracts/types/document.types"; import { documentsApiService } from "@/lib/apis/documents-api.service"; import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; import { queries } from "@/zero/queries"; -export interface DocumentMentionPickerRef { - selectHighlighted: () => void; - moveUp: () => void; - moveDown: () => void; -} +export type DocumentMentionPickerRef = ComposerSuggestionNavigatorRef; interface DocumentMentionPickerProps { searchSpaceId: number; @@ -51,34 +67,86 @@ const PAGE_SIZE = 20; const MIN_SEARCH_LENGTH = 2; const DEBOUNCE_MS = 100; -/** - * Custom debounce hook that delays value updates until user input stabilizes. - * Preferred over throttling for search inputs as it reduces API request frequency - * and prevents race conditions from stale responses overtaking recent ones. - */ +type BrowseView = + | { kind: "root" } + | { kind: "surfsense-docs" } + | { kind: "files-folders" } + | { kind: "connectors" } + | { kind: "connector-type"; connectorType: string; title: string }; + +type ResourceNodeValue = + | { kind: "view"; view: BrowseView } + | { kind: "mention"; mention: MentionedDocumentInfo }; + function useDebounced(value: T, delay = DEBOUNCE_MS) { const [debounced, setDebounced] = useState(value); const timeoutRef = useRef | undefined>(undefined); useEffect(() => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - - timeoutRef.current = setTimeout(() => { - setDebounced(value); - }, delay); - + if (timeoutRef.current) clearTimeout(timeoutRef.current); + timeoutRef.current = setTimeout(() => setDebounced(value), delay); return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } + if (timeoutRef.current) clearTimeout(timeoutRef.current); }; }, [value, delay]); return debounced; } +function titleForConnectorType(connectorType: string) { + const configured = + OAUTH_CONNECTORS.find((c) => c.connectorType === connectorType) || + COMPOSIO_CONNECTORS.find((c) => c.connectorType === connectorType); + return ( + configured?.title || + connectorType + .replace(/_/g, " ") + .replace(/connector/gi, "") + .trim() + ); +} + +function makeDocMention(doc: Pick): MentionedDocumentInfo { + return { + id: doc.id, + title: doc.title, + document_type: doc.document_type, + kind: "doc", + }; +} + +function makeFolderMention(folder: { id: number; title: string }): MentionedDocumentInfo { + return { + id: folder.id, + title: folder.title, + document_type: FOLDER_MENTION_DOCUMENT_TYPE, + kind: "folder", + }; +} + +function makeConnectorMention(connector: SearchSourceConnector): MentionedDocumentInfo { + const accountName = getConnectorDisplayName(connector.name); + const connectorTitle = titleForConnectorType(connector.connector_type); + return { + id: connector.id, + title: `${connectorTitle}: ${accountName}`, + document_type: connector.connector_type, + kind: "connector", + connector_type: connector.connector_type, + account_name: accountName, + }; +} + +function mentionMatchesSearch(mention: MentionedDocumentInfo, searchLower: string) { + return [ + mention.title, + mention.document_type, + mention.kind, + mention.kind === "connector" ? mention.connector_type : "", + mention.kind === "connector" ? mention.account_name : "", + ].some((value) => value.toLowerCase().includes(searchLower)); +} + export const DocumentMentionPicker = forwardRef< DocumentMentionPickerRef, DocumentMentionPickerProps @@ -86,18 +154,14 @@ export const DocumentMentionPicker = forwardRef< { searchSpaceId, onSelectionChange, onDone, initialSelectedDocuments = [], externalSearch = "" }, ref ) { - // Debounced search value to minimize API calls and prevent race conditions const search = externalSearch; const debouncedSearch = useDebounced(search, DEBOUNCE_MS); - // Deferred snapshot of debouncedSearch — client-side filtering uses this so it - // is treated as a non-urgent update, keeping the input responsive. const deferredSearch = useDeferredValue(debouncedSearch); - const [highlightedIndex, setHighlightedIndex] = useState(0); - const itemRefs = useRef>(new Map()); - const scrollContainerRef = useRef(null); - const shouldScrollRef = useRef(false); // Keyboard navigation scroll flag + const hasSearch = debouncedSearch.trim().length > 0; + const isSearchValid = debouncedSearch.trim().length >= MIN_SEARCH_LENGTH; + const isSingleCharSearch = debouncedSearch.trim().length === 1; + const [view, setView] = useState({ kind: "root" }); - // Pagination state for infinite scroll const [accumulatedDocuments, setAccumulatedDocuments] = useState< Pick[] >([]); @@ -105,32 +169,26 @@ export const DocumentMentionPicker = forwardRef< const [hasMore, setHasMore] = useState(false); const [isLoadingMore, setIsLoadingMore] = useState(false); - // Folders for this search space — pulled from Zero so the picker - // stays consistent with the documents sidebar (same source of - // truth, automatic updates on rename/delete). const [zeroFolders] = useZeroQuery(queries.folders.bySpace({ searchSpaceId })); + const { data: connectors = [], isLoading: isConnectorsLoading } = useAtomValue(connectorsAtom); + const paginationScopeKey = useMemo( + () => `${searchSpaceId}:${debouncedSearch}`, + [searchSpaceId, debouncedSearch] + ); + const previousPaginationScopeKeyRef = useRef(null); - /** - * Search Strategy: - * - Single character (length === 1): Client-side filtering for instant results - * - Two or more characters (length >= 2): Server-side search with pg_trgm index - * This hybrid approach optimizes UX by providing immediate feedback for short queries - * while leveraging efficient database indexing for longer, more specific searches. - */ - const isSearchValid = debouncedSearch.trim().length >= MIN_SEARCH_LENGTH; - const shouldSearch = debouncedSearch.trim().length > 0; - const isSingleCharSearch = debouncedSearch.trim().length === 1; - - // Reset pagination state when search query or search space changes. - // Documents are not cleared to maintain visual continuity during fetches. - // biome-ignore lint/correctness/useExhaustiveDependencies: Intentional reset on search/space change + // Reset pagination state when the active search scope changes. useEffect(() => { + if (previousPaginationScopeKeyRef.current === paginationScopeKey) return; + previousPaginationScopeKeyRef.current = paginationScopeKey; setCurrentPage(0); setHasMore(false); - setHighlightedIndex(0); - }, [debouncedSearch, searchSpaceId]); + }, [paginationScopeKey]); + + useEffect(() => { + if (hasSearch) setView({ kind: "root" }); + }, [hasSearch]); - // Query parameters for lightweight title search endpoint const titleSearchParams = useMemo( () => ({ search_space_id: searchSpaceId, @@ -146,77 +204,59 @@ export const DocumentMentionPicker = forwardRef< page: 0, page_size: PAGE_SIZE, }; - if (isSearchValid) { - params.title = debouncedSearch.trim(); - } + if (isSearchValid) params.title = debouncedSearch.trim(); return params; }, [debouncedSearch, isSearchValid]); - /** - * TanStack Query for document title search. - * - Uses AbortSignal for automatic request cancellation on query key changes - * - placeholderData: keepPreviousData maintains UI stability during fetches - * - Only triggers server-side search when isSearchValid (2+ characters) - */ const { data: titleSearchResults, isLoading: isTitleSearchLoading } = useQuery({ queryKey: ["document-titles", titleSearchParams], queryFn: ({ signal }) => documentsApiService.searchDocumentTitles({ queryParams: titleSearchParams }, signal), staleTime: 60 * 1000, - enabled: !!searchSpaceId && currentPage === 0 && (!shouldSearch || isSearchValid), + enabled: !!searchSpaceId && currentPage === 0 && (!hasSearch || isSearchValid), placeholderData: keepPreviousData, }); - /** - * TanStack Query for SurfSense documentation. - * - Uses AbortSignal for automatic request cancellation - * - placeholderData: keepPreviousData prevents UI flicker during refetches - */ const { data: surfsenseDocs, isLoading: isSurfsenseDocsLoading } = useQuery({ queryKey: ["surfsense-docs-mention", debouncedSearch, isSearchValid], queryFn: ({ signal }) => documentsApiService.getSurfsenseDocs({ queryParams: surfsenseDocsQueryParams }, signal), staleTime: 3 * 60 * 1000, - enabled: !shouldSearch || isSearchValid, + enabled: !hasSearch || isSearchValid, placeholderData: keepPreviousData, }); - // Post-fetch filter to eliminate false positives from backend fuzzy matching const filterBySearchTerm = useCallback( (docs: Pick[]) => { - if (!isSearchValid) return docs; // No filtering when not searching + if (!isSearchValid) return docs; const searchLower = debouncedSearch.trim().toLowerCase(); return docs.filter((doc) => doc.title.toLowerCase().includes(searchLower)); }, [debouncedSearch, isSearchValid] ); - // Combine and update document list when first page data arrives useEffect(() => { - if (currentPage === 0) { - const combinedDocs: Pick[] = []; + if (currentPage !== 0) return; + const combinedDocs: Pick[] = []; - // SurfSense docs displayed first in the list - if (surfsenseDocs?.items) { - for (const doc of surfsenseDocs.items) { - combinedDocs.push({ - id: doc.id, - title: doc.title, - document_type: "SURFSENSE_DOCS", - }); - } + if (surfsenseDocs?.items) { + for (const doc of surfsenseDocs.items) { + combinedDocs.push({ + id: doc.id, + title: doc.title, + document_type: "SURFSENSE_DOCS", + }); } - - if (titleSearchResults?.items) { - combinedDocs.push(...titleSearchResults.items); - setHasMore(titleSearchResults.has_more); - } - - setAccumulatedDocuments(filterBySearchTerm(combinedDocs)); } + + if (titleSearchResults?.items) { + combinedDocs.push(...titleSearchResults.items); + setHasMore(titleSearchResults.has_more); + } + + setAccumulatedDocuments(filterBySearchTerm(combinedDocs)); }, [titleSearchResults, surfsenseDocs, currentPage, filterBySearchTerm]); - // Load next page for infinite scroll pagination const loadNextPage = useCallback(async () => { if (isLoadingMore || !hasMore) return; @@ -230,9 +270,9 @@ export const DocumentMentionPicker = forwardRef< page_size: PAGE_SIZE, ...(isSearchValid ? { title: debouncedSearch.trim() } : {}), }; - const response: SearchDocumentTitlesResponse = await documentsApiService.searchDocumentTitles( - { queryParams } - ); + const response: SearchDocumentTitlesResponse = await documentsApiService.searchDocumentTitles({ + queryParams, + }); setAccumulatedDocuments((prev) => [...prev, ...response.items]); setHasMore(response.has_more); @@ -244,41 +284,12 @@ export const DocumentMentionPicker = forwardRef< } }, [currentPage, hasMore, isLoadingMore, debouncedSearch, searchSpaceId, isSearchValid]); - // Trigger pagination when user scrolls near the bottom (50px threshold) - const handleScroll = useCallback( - (e: React.UIEvent) => { - const target = e.currentTarget; - const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight; - - if (scrollBottom < 50 && hasMore && !isLoadingMore) { - loadNextPage(); - } - }, - [hasMore, isLoadingMore, loadNextPage] - ); - - /** - * Client-side filtering for single character searches. - * Filters cached documents locally for instant feedback without additional API calls. - * Server-side search is reserved for 2+ character queries to leverage database indexing. - * Uses deferredSearch (a deferred snapshot of debouncedSearch) so this memo is treated - * as non-urgent — React can interrupt it to keep the input responsive. - */ - const clientFilteredDocs = useMemo(() => { - if (!isSingleCharSearch) return null; + const actualDocuments = useMemo(() => { + if (!isSingleCharSearch) return accumulatedDocuments; const searchLower = deferredSearch.trim().toLowerCase(); return accumulatedDocuments.filter((doc) => doc.title.toLowerCase().includes(searchLower)); - }, [isSingleCharSearch, deferredSearch, accumulatedDocuments]); + }, [accumulatedDocuments, deferredSearch, isSingleCharSearch]); - // Select data source based on search length: client-filtered for single char, server results for 2+ - const actualDocuments = isSingleCharSearch ? (clientFilteredDocs ?? []) : accumulatedDocuments; - // Only show loading spinner on initial load (no documents yet), not during subsequent searches - const actualLoading = - (isTitleSearchLoading || isSurfsenseDocsLoading) && - currentPage === 0 && - !isSingleCharSearch && - accumulatedDocuments.length === 0; - // Partition documents by type for grouped UI rendering const surfsenseDocsList = useMemo( () => actualDocuments.filter((doc) => doc.document_type === "SURFSENSE_DOCS"), [actualDocuments] @@ -287,47 +298,25 @@ export const DocumentMentionPicker = forwardRef< () => actualDocuments.filter((doc) => doc.document_type !== "SURFSENSE_DOCS"), [actualDocuments] ); - - // Folder mention candidates filtered by the current search term. - // Single-char and server-search both use the same client filter - // — folder counts in a workspace are tiny compared to docs, so we - // don't need a paged endpoint. Empty search shows all folders. - const folderMentions: MentionedDocumentInfo[] = useMemo(() => { - const all = (zeroFolders ?? []).map((f) => ({ - id: f.id, - title: f.name, - document_type: FOLDER_MENTION_DOCUMENT_TYPE, - kind: "folder" as const, - })); - if (!shouldSearch) return all; + const folderMentions = useMemo(() => { + const all = (zeroFolders ?? []).map((f) => makeFolderMention({ id: f.id, title: f.name })); + if (!hasSearch) return all; const needle = (isSingleCharSearch ? deferredSearch : debouncedSearch).trim().toLowerCase(); if (!needle) return all; return all.filter((f) => f.title.toLowerCase().includes(needle)); - }, [zeroFolders, debouncedSearch, deferredSearch, isSingleCharSearch, shouldSearch]); + }, [zeroFolders, debouncedSearch, deferredSearch, isSingleCharSearch, hasSearch]); + + const connectorMentions = useMemo( + () => connectors.filter((c) => c.is_active).map(makeConnectorMention), + [connectors] + ); - // Doc-shape entries reuse their ``document_type`` discriminator; - // folder entries lift the existing kind-aware key so the same - // matchers used by the chip atom apply unchanged. const selectedKeys = useMemo( () => new Set(initialSelectedDocuments.map((d) => getMentionDocKey(d))), [initialSelectedDocuments] ); - // Combined navigation order: SurfSense docs -> User docs -> Folders. - // Mirrors the on-screen ordering so keyboard arrows match what the - // user sees. - const selectableMentions = useMemo(() => { - const docs: MentionedDocumentInfo[] = actualDocuments.map((doc) => ({ - id: doc.id, - title: doc.title, - document_type: doc.document_type, - kind: "doc" as const, - })); - const ordered = [...docs, ...folderMentions]; - return ordered.filter((m) => !selectedKeys.has(getMentionDocKey(m))); - }, [actualDocuments, folderMentions, selectedKeys]); - - const handleSelectMention = useCallback( + const selectMention = useCallback( (mention: MentionedDocumentInfo) => { onSelectionChange([...initialSelectedDocuments, mention]); onDone(); @@ -335,258 +324,303 @@ export const DocumentMentionPicker = forwardRef< [initialSelectedDocuments, onSelectionChange, onDone] ); - // Auto-scroll highlighted item into view (keyboard navigation only, not mouse hover) - useEffect(() => { - if (!shouldScrollRef.current) { - return; - } - shouldScrollRef.current = false; - - const rafId = requestAnimationFrame(() => { - const item = itemRefs.current.get(highlightedIndex); - const container = scrollContainerRef.current; - - if (item && container) { - const itemRect = item.getBoundingClientRect(); - const containerRect = container.getBoundingClientRect(); - const padding = 8; - const isAboveViewport = itemRect.top < containerRect.top + padding; - const isBelowViewport = itemRect.bottom > containerRect.bottom - padding; - - if (isAboveViewport || isBelowViewport) { - const itemOffsetTop = item.offsetTop; - const containerHeight = container.clientHeight; - const itemHeight = item.offsetHeight; - const targetScrollTop = itemOffsetTop - containerHeight / 2 + itemHeight / 2; - const maxScrollTop = container.scrollHeight - containerHeight; - const clampedScrollTop = Math.max(0, Math.min(targetScrollTop, maxScrollTop)); - - container.scrollTo({ - top: clampedScrollTop, - behavior: "smooth", - }); - } - } - }); - - return () => cancelAnimationFrame(rafId); - }, [highlightedIndex]); - - // Reset highlight position when search query changes - const prevSearchRef = useRef(search); - if (prevSearchRef.current !== search) { - prevSearchRef.current = search; - if (highlightedIndex !== 0) { - setHighlightedIndex(0); - } - } - - // Expose navigation and selection methods to parent component via ref - useImperativeHandle( - ref, - () => ({ - selectHighlighted: () => { - if (selectableMentions[highlightedIndex]) { - handleSelectMention(selectableMentions[highlightedIndex]); - } + const rootNodes = useMemo[]>( + () => [ + { + id: "surfsense-docs", + label: "SurfSense Docs", + subtitle: "Browse product documentation", + icon: , + type: "branch", + value: { kind: "view", view: { kind: "surfsense-docs" } }, }, - moveUp: () => { - shouldScrollRef.current = true; - setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : selectableMentions.length - 1)); + { + id: "files-folders", + label: "Files & Folders", + subtitle: "Browse your knowledge base", + icon: , + type: "branch", + value: { kind: "view", view: { kind: "files-folders" } }, }, - moveDown: () => { - shouldScrollRef.current = true; - setHighlightedIndex((prev) => (prev < selectableMentions.length - 1 ? prev + 1 : 0)); + { + id: "connectors", + label: "Connectors", + subtitle: connectors.length + ? "Choose the exact account for tool use" + : "No connected accounts yet", + icon: , + type: "branch", + disabled: connectors.length === 0, + value: { kind: "view", view: { kind: "connectors" } }, }, - }), - [selectableMentions, highlightedIndex, handleSelectMention] + ], + [connectors.length] ); - // Keyboard navigation handler for arrow keys, Enter, and Escape - const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { - if (selectableMentions.length === 0) return; + const searchNodes = useMemo[]>(() => { + const searchLower = (isSingleCharSearch ? deferredSearch : debouncedSearch).trim().toLowerCase(); + const docNodes = actualDocuments.map((doc) => { + const mention = makeDocMention(doc); + return { + id: getMentionDocKey(mention), + label: doc.title, + icon: getConnectorIcon(doc.document_type, "size-4"), + type: "item" as const, + disabled: selectedKeys.has(getMentionDocKey(mention)), + value: { kind: "mention" as const, mention }, + }; + }); + const folderNodes = folderMentions.map((mention) => ({ + id: getMentionDocKey(mention), + label: mention.title, + subtitle: "Folder", + icon: , + type: "item" as const, + disabled: selectedKeys.has(getMentionDocKey(mention)), + value: { kind: "mention" as const, mention }, + })); + const connectorNodes = connectorMentions + .filter((mention) => !searchLower || mentionMatchesSearch(mention, searchLower)) + .map((mention) => ({ + id: getMentionDocKey(mention), + label: mention.title, + subtitle: "Connector account", + icon: getConnectorIcon(mention.document_type, "size-4") ?? , + type: "item" as const, + disabled: selectedKeys.has(getMentionDocKey(mention)), + value: { kind: "mention" as const, mention }, + })); - switch (e.key) { - case "ArrowDown": - e.preventDefault(); - shouldScrollRef.current = true; - setHighlightedIndex((prev) => (prev < selectableMentions.length - 1 ? prev + 1 : 0)); - break; - case "ArrowUp": - e.preventDefault(); - shouldScrollRef.current = true; - setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : selectableMentions.length - 1)); - break; - case "Enter": - e.preventDefault(); - if (selectableMentions[highlightedIndex]) { - handleSelectMention(selectableMentions[highlightedIndex]); - } - break; - case "Escape": - e.preventDefault(); - onDone(); - break; + return [...docNodes, ...folderNodes, ...connectorNodes]; + }, [ + actualDocuments, + connectorMentions, + debouncedSearch, + deferredSearch, + folderMentions, + isSingleCharSearch, + selectedKeys, + ]); + + const connectorTypeEntries = useMemo(() => { + const byType = new Map(); + for (const connector of connectors.filter((c) => c.is_active)) { + const list = byType.get(connector.connector_type) ?? []; + list.push(connector); + byType.set(connector.connector_type, list); + } + return Array.from(byType.entries()).sort(([a], [b]) => + titleForConnectorType(a).localeCompare(titleForConnectorType(b)) + ); + }, [connectors]); + + const browseNodes = useMemo[]>(() => { + if (view.kind === "root") return rootNodes; + if (view.kind === "surfsense-docs") { + return surfsenseDocsList.map((doc) => { + const mention = makeDocMention(doc); + return { + id: getMentionDocKey(mention), + label: doc.title, + icon: getConnectorIcon(doc.document_type, "size-4"), + type: "item" as const, + disabled: selectedKeys.has(getMentionDocKey(mention)), + value: { kind: "mention" as const, mention }, + }; + }); + } + if (view.kind === "files-folders") { + const folders = folderMentions.map((mention) => ({ + id: getMentionDocKey(mention), + label: mention.title, + subtitle: "Folder", + icon: , + type: "item" as const, + disabled: selectedKeys.has(getMentionDocKey(mention)), + value: { kind: "mention" as const, mention }, + })); + const docs = userDocsList.map((doc) => { + const mention = makeDocMention(doc); + return { + id: getMentionDocKey(mention), + label: doc.title, + icon: getConnectorIcon(doc.document_type, "size-4"), + type: "item" as const, + disabled: selectedKeys.has(getMentionDocKey(mention)), + value: { kind: "mention" as const, mention }, + }; + }); + return [...folders, ...docs]; + } + if (view.kind === "connectors") { + return connectorTypeEntries.map(([connectorType, typeConnectors]) => ({ + id: `connector-type:${connectorType}`, + label: titleForConnectorType(connectorType), + subtitle: `${typeConnectors.length} ${typeConnectors.length === 1 ? "account" : "accounts"}`, + icon: getConnectorIcon(connectorType, "size-4") ?? , + type: "branch" as const, + value: { + kind: "view" as const, + view: { + kind: "connector-type" as const, + connectorType, + title: titleForConnectorType(connectorType), + }, + }, + })); + } + return connectors + .filter((connector) => connector.is_active && connector.connector_type === view.connectorType) + .map((connector) => { + const mention = makeConnectorMention(connector); + return { + id: getMentionDocKey(mention), + label: getConnectorDisplayName(connector.name), + subtitle: `${view.title} account`, + icon: getConnectorIcon(connector.connector_type, "size-4") ?? , + type: "item" as const, + disabled: selectedKeys.has(getMentionDocKey(mention)), + value: { kind: "mention" as const, mention }, + }; + }); + }, [ + connectors, + connectorTypeEntries, + folderMentions, + rootNodes, + selectedKeys, + surfsenseDocsList, + userDocsList, + view, + ]); + + const visibleNodes = hasSearch ? searchNodes : browseNodes; + const handleNodeSelect = useCallback( + (node: ComposerSuggestionNode) => { + const value = node.value; + if (!value) return; + if (value.kind === "view") { + setView(value.view); + return; + } + selectMention(value.mention); + }, + [selectMention] + ); + const handleBack = useCallback(() => { + if (hasSearch || view.kind === "root") return false; + if (view.kind === "connector-type") { + setView({ kind: "connectors" }); + return true; + } + setView({ kind: "root" }); + return true; + }, [hasSearch, view]); + + const navigator = useComposerSuggestionNavigator({ + nodes: visibleNodes, + onSelect: handleNodeSelect, + onBack: handleBack, + ref, + }); + + const handleScroll = useCallback( + (e: React.UIEvent) => { + if (view.kind === "connectors" || view.kind === "connector-type") return; + const target = e.currentTarget; + const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight; + + if (scrollBottom < 50 && hasMore && !isLoadingMore) { + loadNextPage(); } }, - [selectableMentions, highlightedIndex, handleSelectMention, onDone] + [hasMore, isLoadingMore, loadNextPage, view.kind] ); + const actualLoading = + (isTitleSearchLoading || isSurfsenseDocsLoading || isConnectorsLoading) && + !isSingleCharSearch && + visibleNodes.length === 0 && + (view.kind === "root" || hasSearch); + + const title = + hasSearch || view.kind === "root" + ? null + : view.kind === "surfsense-docs" + ? "SurfSense Docs" + : view.kind === "files-folders" + ? "Files & Folders" + : view.kind === "connectors" + ? "Connectors" + : view.title; + return ( {actualLoading ? ( - ) : actualDocuments.length > 0 || folderMentions.length > 0 ? ( + ) : ( - {/* SurfSense Documentation */} - {surfsenseDocsList.length > 0 && ( + {title ? ( <> - SurfSense Docs - {surfsenseDocsList.map((doc) => { - const mention: MentionedDocumentInfo = { - id: doc.id, - title: doc.title, - document_type: doc.document_type, - kind: "doc", - }; - const docKey = getMentionDocKey(mention); - const isAlreadySelected = selectedKeys.has(docKey); - const selectableIndex = selectableMentions.findIndex( - (m) => getMentionDocKey(m) === docKey - ); - const isHighlighted = !isAlreadySelected && selectableIndex === highlightedIndex; - - return ( - { - if (el && selectableIndex >= 0) itemRefs.current.set(selectableIndex, el); - else if (selectableIndex >= 0) itemRefs.current.delete(selectableIndex); - }} - icon={getConnectorIcon(doc.document_type)} - selected={isHighlighted} - disabled={isAlreadySelected} - onClick={() => !isAlreadySelected && handleSelectMention(mention)} - onMouseEnter={() => { - if (!isAlreadySelected && selectableIndex >= 0) { - setHighlightedIndex(selectableIndex); - } - }} - > - - {doc.title} - - - ); - })} + } + muted + onClick={handleBack} + > + {title} + + + ) : null} + + {visibleNodes.length > 0 ? ( + <> + {hasSearch ? ( + Suggested Context + ) : null} + {visibleNodes.map((node, index) => ( + !node.disabled && handleNodeSelect(node)} + onMouseEnter={() => navigator.setHighlightedIndex(index)} + > + + + {node.label} + + {node.subtitle ? ( + + {node.subtitle} + + ) : null} + + {node.type === "branch" ? ( + + ) : null} + + ))} + + ) : ( + + {hasSearch ? "No matching context" : "No items available"} + )} - {/* User Documents */} - {userDocsList.length > 0 && ( - <> - {surfsenseDocsList.length > 0 && } - Your Documents - {userDocsList.map((doc) => { - const mention: MentionedDocumentInfo = { - id: doc.id, - title: doc.title, - document_type: doc.document_type, - kind: "doc", - }; - const docKey = getMentionDocKey(mention); - const isAlreadySelected = selectedKeys.has(docKey); - const selectableIndex = selectableMentions.findIndex( - (m) => getMentionDocKey(m) === docKey - ); - const isHighlighted = !isAlreadySelected && selectableIndex === highlightedIndex; - - return ( - { - if (el && selectableIndex >= 0) itemRefs.current.set(selectableIndex, el); - else if (selectableIndex >= 0) itemRefs.current.delete(selectableIndex); - }} - icon={getConnectorIcon(doc.document_type)} - selected={isHighlighted} - disabled={isAlreadySelected} - onClick={() => !isAlreadySelected && handleSelectMention(mention)} - onMouseEnter={() => { - if (!isAlreadySelected && selectableIndex >= 0) { - setHighlightedIndex(selectableIndex); - } - }} - > - - {doc.title} - - - ); - })} - - )} - - {/* Folders — single source of truth is Zero (same store - that powers the documents sidebar). Selecting a - folder inserts a folder chip whose path the agent - can walk with ``ls`` / ``find_documents``. */} - {folderMentions.length > 0 && ( - <> - {(surfsenseDocsList.length > 0 || userDocsList.length > 0) && ( - - )} - Folders - {folderMentions.map((folder) => { - const folderKey = getMentionDocKey(folder); - const isAlreadySelected = selectedKeys.has(folderKey); - const selectableIndex = selectableMentions.findIndex( - (m) => getMentionDocKey(m) === folderKey - ); - const isHighlighted = !isAlreadySelected && selectableIndex === highlightedIndex; - - return ( - { - if (el && selectableIndex >= 0) itemRefs.current.set(selectableIndex, el); - else if (selectableIndex >= 0) itemRefs.current.delete(selectableIndex); - }} - icon={} - selected={isHighlighted} - disabled={isAlreadySelected} - onClick={() => !isAlreadySelected && handleSelectMention(folder)} - onMouseEnter={() => { - if (!isAlreadySelected && selectableIndex >= 0) { - setHighlightedIndex(selectableIndex); - } - }} - > - - {folder.title} - - - ); - })} - - )} - - {/* Pagination loading indicator */} {isLoadingMore && (
)}
- ) : ( - No matching documents )}
); diff --git a/surfsense_web/components/new-chat/use-composer-suggestion-navigator.ts b/surfsense_web/components/new-chat/use-composer-suggestion-navigator.ts new file mode 100644 index 000000000..da4dc60c3 --- /dev/null +++ b/surfsense_web/components/new-chat/use-composer-suggestion-navigator.ts @@ -0,0 +1,120 @@ +"use client"; + +import type * as React from "react"; +import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; + +export type ComposerSuggestionNode = { + id: string; + label: string; + subtitle?: string; + icon?: React.ReactNode; + keywords?: string[]; + type: "branch" | "item" | "action"; + value?: TValue; + disabled?: boolean; +}; + +export type ComposerSuggestionNavigatorRef = { + selectHighlighted: () => void; + moveUp: () => void; + moveDown: () => void; + goBack: () => boolean; +}; + +export type ComposerSuggestionNavigatorOptions = { + nodes: ComposerSuggestionNode[]; + onSelect: (node: ComposerSuggestionNode) => void; + onBack?: () => boolean; + ref?: React.Ref; +}; + +export function useComposerSuggestionNavigator({ + nodes, + onSelect, + onBack, + ref, +}: ComposerSuggestionNavigatorOptions) { + const [highlightedIndex, setHighlightedIndex] = useState(0); + const itemRefs = useRef>(new Map()); + const scrollContainerRef = useRef(null); + const shouldScrollRef = useRef(false); + const nodesKey = useMemo(() => nodes.map((node) => node.id).join("\u0000"), [nodes]); + const previousNodesKeyRef = useRef(null); + + // Reset keyboard focus when the caller swaps the visible node set. + useEffect(() => { + if (previousNodesKeyRef.current === nodesKey) return; + previousNodesKeyRef.current = nodesKey; + setHighlightedIndex(0); + itemRefs.current.clear(); + }, [nodesKey]); + + useEffect(() => { + if (!shouldScrollRef.current) return; + shouldScrollRef.current = false; + + const rafId = requestAnimationFrame(() => { + const item = itemRefs.current.get(highlightedIndex); + const container = scrollContainerRef.current; + if (!item || !container) return; + + const itemRect = item.getBoundingClientRect(); + const containerRect = container.getBoundingClientRect(); + if (itemRect.top < containerRect.top || itemRect.bottom > containerRect.bottom) { + item.scrollIntoView({ block: "nearest" }); + } + }); + + return () => cancelAnimationFrame(rafId); + }, [highlightedIndex]); + + const moveUp = useCallback(() => { + if (nodes.length === 0) return; + shouldScrollRef.current = true; + setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : nodes.length - 1)); + }, [nodes.length]); + + const moveDown = useCallback(() => { + if (nodes.length === 0) return; + shouldScrollRef.current = true; + setHighlightedIndex((prev) => (prev < nodes.length - 1 ? prev + 1 : 0)); + }, [nodes.length]); + + const selectHighlighted = useCallback(() => { + const node = nodes[highlightedIndex]; + if (!node || node.disabled) return; + onSelect(node); + }, [highlightedIndex, nodes, onSelect]); + + const goBack = useCallback(() => onBack?.() ?? false, [onBack]); + + useImperativeHandle( + ref, + () => ({ + selectHighlighted, + moveUp, + moveDown, + goBack, + }), + [goBack, moveDown, moveUp, selectHighlighted] + ); + + const getItemRef = useCallback( + (index: number) => (el: HTMLButtonElement | null) => { + if (el) itemRefs.current.set(index, el); + else itemRefs.current.delete(index); + }, + [] + ); + + return { + highlightedIndex, + setHighlightedIndex, + scrollContainerRef, + getItemRef, + moveUp, + moveDown, + selectHighlighted, + goBack, + }; +} diff --git a/surfsense_web/lib/chat/mention-doc-key.ts b/surfsense_web/lib/chat/mention-doc-key.ts index 5c0bd6254..114faace5 100644 --- a/surfsense_web/lib/chat/mention-doc-key.ts +++ b/surfsense_web/lib/chat/mention-doc-key.ts @@ -1,7 +1,7 @@ type MentionKeyInput = { id: number; document_type?: string | null; - kind?: "doc" | "folder"; + kind?: "doc" | "folder" | "connector"; }; /** From 2d134439ecae7a2f1492e7880429617edff5f222 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 26 May 2026 21:52:04 +0530 Subject: [PATCH 076/198] feat(web): enhance mention handling to support connectors and improve document key management --- surfsense_backend/app/schemas/new_chat.py | 19 ++--- .../app/tasks/chat/persistence.py | 19 +++-- .../new-chat/[[...chat_id]]/page.tsx | 15 ++-- .../atoms/chat/mentioned-documents.atom.ts | 34 +++----- .../assistant-ui/inline-mention-editor.tsx | 68 +++++++++++----- .../components/assistant-ui/thread.tsx | 31 ++++---- .../components/assistant-ui/user-message.tsx | 2 +- .../components/documents/FolderTreeView.tsx | 1 - .../new-chat/document-mention-picker.tsx | 44 ++++++----- surfsense_web/lib/chat/mention-doc-key.ts | 12 +-- surfsense_web/lib/connector-telemetry.ts | 79 +++++++------------ 11 files changed, 160 insertions(+), 164 deletions(-) diff --git a/surfsense_backend/app/schemas/new_chat.py b/surfsense_backend/app/schemas/new_chat.py index c721f495e..8b49413c6 100644 --- a/surfsense_backend/app/schemas/new_chat.py +++ b/surfsense_backend/app/schemas/new_chat.py @@ -203,13 +203,11 @@ class NewChatUserImagePart(BaseModel): class MentionedDocumentInfo(BaseModel): """Display metadata for a single ``@``-mention chip. - Carries either a knowledge-base document or a knowledge-base folder - (discriminated by ``kind``). The full triple - ``{id, title, document_type}`` is forwarded by the frontend mention - chip so the server can embed it in the persisted user message - ``ContentPart[]`` (single ``mentioned-documents`` part). The - history loader then renders the chips on reload without an extra - fetch — mirrors the pre-refactor frontend ``persistUserTurn`` shape. + Carries a knowledge-base document, knowledge-base folder, or + connected account (discriminated by ``kind``). Each kind uses its + real identity fields: docs carry ``document_type``, folders carry + only their folder id/title, and connectors carry ``connector_type`` + plus account metadata. ``kind`` defaults to ``"doc"`` so legacy clients and persisted rows that predate folder mentions deserialise unchanged. @@ -217,17 +215,14 @@ class MentionedDocumentInfo(BaseModel): id: int title: str = Field(..., min_length=1, max_length=500) - document_type: str = Field(..., min_length=1, max_length=100) + document_type: str | None = Field(default=None, min_length=1, max_length=100) kind: Literal["doc", "folder", "connector"] = Field( default="doc", description=( "Discriminator for the chip's referent: ``doc`` is a " "knowledge-base ``Document`` row, ``folder`` is a " "knowledge-base ``Folder`` row, and ``connector`` is a " - "concrete connected account. Folders carry the sentinel " - "``document_type='FOLDER'`` to keep the frontend dedup key " - "``(kind:document_type:id)`` from colliding doc and folder " - "ids that happen to share an integer value." + "concrete connected account." ), ) connector_type: str | None = Field(default=None, max_length=100) diff --git a/surfsense_backend/app/tasks/chat/persistence.py b/surfsense_backend/app/tasks/chat/persistence.py index 07266cf69..9d100c13c 100644 --- a/surfsense_backend/app/tasks/chat/persistence.py +++ b/surfsense_backend/app/tasks/chat/persistence.py @@ -109,7 +109,7 @@ def _build_user_content( [{"type": "text", "text": "..."}, {"type": "image", "image": "data:..."}, {"type": "mentioned-documents", "documents": [{"id": int, - "title": str, "document_type": str, "kind": "doc" | "folder"}, + "title": str, "kind": "doc" | "folder" | "connector", ...}, ...]}] The companion reader is @@ -117,8 +117,8 @@ def _build_user_content( which expects exactly this shape — keep them in sync. ``mentioned_documents``: optional list of mention chip dicts. Each - dict may include a ``kind`` discriminator (``"doc"`` or ``"folder"``) - so the persisted ContentPart round-trips folder chips on reload. + dict may include a ``kind`` discriminator so the persisted + ContentPart round-trips folder and connector chips on reload. When ``kind`` is missing we default to ``"doc"`` so legacy clients that haven't migrated to the union schema still persist correctly. """ @@ -134,18 +134,23 @@ def _build_user_content( doc_id = doc.get("id") title = doc.get("title") document_type = doc.get("document_type") - if doc_id is None or title is None or document_type is None: - continue kind_raw = doc.get("kind", "doc") kind = kind_raw if kind_raw in ("doc", "folder", "connector") else "doc" + if doc_id is None or title is None: + continue + if kind == "doc" and document_type is None: + continue item = { "id": doc_id, "title": str(title), - "document_type": str(document_type), "kind": kind, } + if document_type is not None: + item["document_type"] = str(document_type) if kind == "connector": - connector_type = doc.get("connector_type") or document_type + connector_type = doc.get("connector_type") + if connector_type is None: + continue account_name = doc.get("account_name") or title item["connector_type"] = str(connector_type) item["account_name"] = str(account_name) diff --git a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx index 8d1f5da46..6cd95a79c 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx @@ -73,6 +73,7 @@ import { convertToThreadMessage, reconcileInterruptedAssistantMessages, } from "@/lib/chat/message-utils"; +import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; import { isPodcastGenerating, looksLikePodcastRequest, @@ -206,7 +207,7 @@ function pairBundleToolCallIds( const MentionedDocumentInfoSchema = z.object({ id: z.number(), title: z.string(), - document_type: z.string(), + document_type: z.string().optional(), kind: z .union([z.literal("doc"), z.literal("folder"), z.literal("connector")]) .optional() @@ -234,9 +235,8 @@ function extractMentionedDocuments(content: unknown): MentionedDocumentInfo[] { return { id: doc.id, title: doc.title, - document_type: doc.document_type, kind: "connector", - connector_type: doc.connector_type ?? doc.document_type, + connector_type: doc.connector_type ?? doc.document_type ?? "UNKNOWN", account_name: doc.account_name ?? doc.title, }; } @@ -244,14 +244,13 @@ function extractMentionedDocuments(content: unknown): MentionedDocumentInfo[] { return { id: doc.id, title: doc.title, - document_type: "FOLDER", kind: "folder", }; } return { id: doc.id, title: doc.title, - document_type: doc.document_type, + document_type: doc.document_type ?? "UNKNOWN", kind: "doc", }; }); @@ -957,15 +956,13 @@ export default function NewChatPage() { }); // Collect unique mention chips for display & persistence. - // Dedup key is ``kind:document_type:id`` so a folder and a - // doc with the same integer id never collapse into one - // entry. The ``kind`` field is forwarded to the backend + // The ``kind`` field is forwarded to the backend // so the persisted ``mentioned-documents`` content part // can render the correct chip type on reload. const allMentionedDocs: MentionedDocumentInfo[] = []; const seenDocKeys = new Set(); for (const doc of mentionedDocuments) { - const key = `${doc.kind}:${doc.document_type}:${doc.id}`; + const key = getMentionDocKey(doc); if (seenDocKeys.has(key)) continue; seenDocKeys.add(key); allMentionedDocs.push(doc); diff --git a/surfsense_web/atoms/chat/mentioned-documents.atom.ts b/surfsense_web/atoms/chat/mentioned-documents.atom.ts index 9efd2b7fe..25d1e397a 100644 --- a/surfsense_web/atoms/chat/mentioned-documents.atom.ts +++ b/surfsense_web/atoms/chat/mentioned-documents.atom.ts @@ -3,13 +3,6 @@ import { atom } from "jotai"; import type { Document } from "@/contracts/types/document.types"; -/** - * Sentinel ``document_type`` used for folder mention chips so the - * dedup key (`kind:document_type:id`) never collides a document with a - * folder that happens to share an integer id. - */ -export const FOLDER_MENTION_DOCUMENT_TYPE = "FOLDER"; - /** * Display metadata for a single ``@``-mention chip. * @@ -27,13 +20,11 @@ export type MentionedDocumentInfo = | { id: number; title: string; - document_type: typeof FOLDER_MENTION_DOCUMENT_TYPE; kind: "folder"; } | { id: number; title: string; - document_type: string; kind: "connector"; connector_type: string; account_name: string; @@ -51,8 +42,7 @@ type LegacyDocMention = Pick; * Normalize an arbitrary chip-like input into the discriminated * ``MentionedDocumentInfo`` shape. Existing call sites that only have * ``{id, title, document_type}`` flow through here so they don't have - * to thread ``kind`` everywhere — the helper defaults to ``"doc"`` and - * rewrites the document type for folders. + * to thread ``kind`` everywhere — the helper defaults to ``"doc"``. */ export function toMentionedDocumentInfo( input: LegacyDocMention | MentionedDocumentInfo @@ -78,31 +68,32 @@ export function makeFolderMention(input: { id: number; name: string }): Mentione return { id: input.id, title: input.name, - document_type: FOLDER_MENTION_DOCUMENT_TYPE, kind: "folder", }; } /** - * Atom to store the full mention objects (documents + folders) attached - * via @-mention chips in the current chat composer. Persists across - * component remounts. + * Atom to store the full context objects attached via @-mention chips in + * the current chat composer. Persists across component remounts. */ export const mentionedDocumentsAtom = atom([]); /** * Derived read-only atom that maps deduplicated mention chips into - * backend payload fields. Doc chips split by ``document_type`` exactly - * like before; folder chips are projected into a separate - * ``folder_ids`` bucket so the route can forward - * ``mentioned_folder_ids`` to the agent without the priority middleware - * conflating them with hybrid-search ids. + * backend payload fields. Each mention kind maps to its own explicit + * payload bucket so non-document context never has to masquerade as a + * document type. */ export const mentionedDocumentIdsAtom = atom((get) => { const allMentions = get(mentionedDocumentsAtom); const seen = new Set(); const deduped = allMentions.filter((m) => { - const key = `${m.kind}:${m.document_type}:${m.id}`; + const key = + m.kind === "doc" + ? `doc:${m.document_type}:${m.id}` + : m.kind === "connector" + ? `connector:${m.connector_type}:${m.id}` + : `folder:${m.id}`; if (seen.has(key)) return false; seen.add(key); return true; @@ -120,7 +111,6 @@ export const mentionedDocumentIdsAtom = atom((get) => { connectors: connectors.map((c) => ({ id: c.id, title: c.title, - document_type: c.document_type, kind: c.kind, connector_type: c.connector_type, account_name: c.account_name, diff --git a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx index b93ea253d..c0d9d9212 100644 --- a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx +++ b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx @@ -20,7 +20,6 @@ import { useMemo, useRef, } from "react"; -import { FOLDER_MENTION_DOCUMENT_TYPE } from "@/atoms/chat/mentioned-documents.atom"; import { Button } from "@/components/ui/button"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import type { Document } from "@/contracts/types/document.types"; @@ -40,8 +39,6 @@ export interface MentionedDocument { /** * Input shape for inserting a chip. ``kind`` defaults to ``"doc"``. - * Folder chips default ``document_type`` to ``FOLDER_MENTION_DOCUMENT_TYPE`` - * so the dedup key never collides with a doc chip sharing the same id. */ export type MentionChipInput = { id: number; @@ -78,7 +75,12 @@ export interface InlineMentionEditorRef { doc: Pick, options?: { removeTriggerText?: boolean } ) => void; - removeDocumentChip: (docId: number, docType?: string) => void; + removeDocumentChip: ( + docId: number, + docType?: string, + kind?: MentionKind, + connectorType?: string + ) => void; setDocumentChipStatus: ( docId: number, docType: string | undefined, @@ -95,7 +97,7 @@ interface InlineMentionEditorProps { onActionClose?: () => void; onSubmit?: () => void; onChange?: (text: string, docs: MentionedDocument[]) => void; - onDocumentRemove?: (docId: number, docType?: string) => void; + onDocumentRemove?: (docId: number, docType?: string, kind?: MentionKind, connectorType?: string) => void; onKeyDown?: (e: React.KeyboardEvent) => void; disabled?: boolean; className?: string; @@ -135,7 +137,12 @@ const EMPTY_VALUE: ComposerValue = [{ type: "p", children: [{ text: "" }] }]; * the X button and Backspace go through the same call site. */ type MentionEditorContextValue = { - removeChip: (docId: number, docType: string | undefined) => void; + removeChip: ( + docId: number, + docType: string | undefined, + kind: MentionKind | undefined, + connectorType: string | undefined + ) => void; }; const MentionEditorContext = createContext(null); @@ -181,7 +188,12 @@ const MentionElement: FC> = ({ onMouseDown={(e) => e.preventDefault()} onClick={(e) => { e.stopPropagation(); - ctx.removeChip(element.id, element.document_type); + ctx.removeChip( + element.id, + element.document_type, + element.kind, + element.connector_type + ); }} className="absolute inset-0 size-3 rounded-sm p-0 opacity-0 transition-opacity hover:bg-transparent hover:text-primary focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-0 group-hover:opacity-100 [&_svg]:size-3" > @@ -456,18 +468,11 @@ export const InlineMentionEditor = forwardRef { + (docId: number, docType?: string, kind?: MentionKind, connectorType?: string) => { const match = (n: unknown) => { if (!n || typeof n !== "object" || !("type" in n)) return false; const node = n as MentionElementNode; if (node.type !== MENTION_TYPE) return false; if (node.id !== docId) return false; + if (kind) { + return ( + getMentionDocKey({ + id: node.id, + kind: node.kind ?? "doc", + document_type: node.document_type, + connector_type: node.connector_type, + }) === + getMentionDocKey({ + id: docId, + kind, + document_type: docType, + connector_type: connectorType, + }) + ); + } return (node.document_type ?? "UNKNOWN") === (docType ?? "UNKNOWN"); }; @@ -554,9 +575,14 @@ export const InlineMentionEditor = forwardRef { - removeDocumentChip(docId, docType); - onDocumentRemove?.(docId, docType); + ( + docId: number, + docType: string | undefined, + kind: MentionKind | undefined, + connectorType: string | undefined + ) => { + removeDocumentChip(docId, docType, kind, connectorType); + onDocumentRemove?.(docId, docType, kind, connectorType); }, [onDocumentRemove, removeDocumentChip] ); @@ -679,7 +705,7 @@ export const InlineMentionEditor = forwardRef { } } return docs.map((d) => { - const documentType = d.document_type ?? "UNKNOWN"; if (d.kind === "connector") { return { id: d.id, title: d.title, - document_type: documentType, kind: "connector", - connector_type: d.connector_type ?? documentType, + connector_type: d.connector_type ?? "UNKNOWN", account_name: d.account_name ?? d.title, }; } @@ -559,17 +556,13 @@ const Composer: FC = () => { return { id: d.id, title: d.title, - document_type: FOLDER_MENTION_DOCUMENT_TYPE, kind: "folder", }; } return { id: d.id, title: d.title, - // Atom requires a string; ``"UNKNOWN"`` matches the - // sentinel ``getMentionDocKey`` and the editor's - // match predicates use. - document_type: documentType, + document_type: d.document_type ?? "UNKNOWN", kind: "doc", }; }); @@ -760,13 +753,14 @@ const Composer: FC = () => { ]); const handleDocumentRemove = useCallback( - (docId: number, docType?: string) => { + (docId: number, docType?: string, kind?: "doc" | "folder" | "connector", connectorType?: string) => { setMentionedDocuments((prev) => { - if (!docType) { - // Fallback when chip type is unavailable. - return prev.filter((doc) => doc.id !== docId); - } - const removedKey = getMentionDocKey({ id: docId, document_type: docType }); + const removedKey = getMentionDocKey({ + id: docId, + document_type: docType, + kind, + connector_type: connectorType, + }); return prev.filter((doc) => getMentionDocKey(doc) !== removedKey); }); }, @@ -810,7 +804,12 @@ const Composer: FC = () => { for (const [key, doc] of prevDocsMap) { if (!nextDocsMap.has(key)) { - editor.removeDocumentChip(doc.id, doc.document_type); + editor.removeDocumentChip( + doc.id, + doc.kind === "doc" ? doc.document_type : undefined, + doc.kind, + doc.kind === "connector" ? doc.connector_type : undefined + ); } } diff --git a/surfsense_web/components/assistant-ui/user-message.tsx b/surfsense_web/components/assistant-ui/user-message.tsx index 3e6dc829a..b30db5f69 100644 --- a/surfsense_web/components/assistant-ui/user-message.tsx +++ b/surfsense_web/components/assistant-ui/user-message.tsx @@ -104,7 +104,7 @@ const UserTextPart: FC = () => { const icon = isFolder ? ( ) : isConnector ? ( - getConnectorIcon(segment.doc.connector_type ?? segment.doc.document_type, "size-3.5") ?? ( + getConnectorIcon(segment.doc.connector_type, "size-3.5") ?? ( ) ) : ( diff --git a/surfsense_web/components/documents/FolderTreeView.tsx b/surfsense_web/components/documents/FolderTreeView.tsx index fb1030028..7c076e99a 100644 --- a/surfsense_web/components/documents/FolderTreeView.tsx +++ b/surfsense_web/components/documents/FolderTreeView.tsx @@ -190,7 +190,6 @@ export function FolderTreeView({ for (const f of folders) { const folderMentionKey = getMentionDocKey({ id: f.id, - document_type: "FOLDER", kind: "folder", }); states[f.id] = mentionedDocKeys.has(folderMentionKey) ? "all" : "none"; diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index f8a84c51b..c424ae6c3 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -20,10 +20,7 @@ import { useState, } from "react"; import type * as React from "react"; -import { - FOLDER_MENTION_DOCUMENT_TYPE, - type MentionedDocumentInfo, -} from "@/atoms/chat/mentioned-documents.atom"; +import type { MentionedDocumentInfo } from "@/atoms/chat/mentioned-documents.atom"; import { useAtomValue } from "jotai"; import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms"; import { @@ -78,6 +75,10 @@ type ResourceNodeValue = | { kind: "view"; view: BrowseView } | { kind: "mention"; mention: MentionedDocumentInfo }; +function isConnectorActive(connector: SearchSourceConnector) { + return connector.is_active !== false; +} + function useDebounced(value: T, delay = DEBOUNCE_MS) { const [debounced, setDebounced] = useState(value); const timeoutRef = useRef | undefined>(undefined); @@ -115,22 +116,24 @@ function makeDocMention(doc: Pick): }; } -function makeFolderMention(folder: { id: number; title: string }): MentionedDocumentInfo { +function makeFolderMention( + folder: { id: number; title: string } +): Extract { return { id: folder.id, title: folder.title, - document_type: FOLDER_MENTION_DOCUMENT_TYPE, kind: "folder", }; } -function makeConnectorMention(connector: SearchSourceConnector): MentionedDocumentInfo { +function makeConnectorMention( + connector: SearchSourceConnector +): Extract { const accountName = getConnectorDisplayName(connector.name); const connectorTitle = titleForConnectorType(connector.connector_type); return { id: connector.id, title: `${connectorTitle}: ${accountName}`, - document_type: connector.connector_type, kind: "connector", connector_type: connector.connector_type, account_name: accountName, @@ -140,8 +143,8 @@ function makeConnectorMention(connector: SearchSourceConnector): MentionedDocume function mentionMatchesSearch(mention: MentionedDocumentInfo, searchLower: string) { return [ mention.title, - mention.document_type, mention.kind, + mention.kind === "doc" ? mention.document_type : "", mention.kind === "connector" ? mention.connector_type : "", mention.kind === "connector" ? mention.account_name : "", ].some((value) => value.toLowerCase().includes(searchLower)); @@ -171,6 +174,7 @@ export const DocumentMentionPicker = forwardRef< const [zeroFolders] = useZeroQuery(queries.folders.bySpace({ searchSpaceId })); const { data: connectors = [], isLoading: isConnectorsLoading } = useAtomValue(connectorsAtom); + const activeConnectors = useMemo(() => connectors.filter(isConnectorActive), [connectors]); const paginationScopeKey = useMemo( () => `${searchSpaceId}:${debouncedSearch}`, [searchSpaceId, debouncedSearch] @@ -307,8 +311,8 @@ export const DocumentMentionPicker = forwardRef< }, [zeroFolders, debouncedSearch, deferredSearch, isSingleCharSearch, hasSearch]); const connectorMentions = useMemo( - () => connectors.filter((c) => c.is_active).map(makeConnectorMention), - [connectors] + () => activeConnectors.map(makeConnectorMention), + [activeConnectors] ); const selectedKeys = useMemo( @@ -345,16 +349,16 @@ export const DocumentMentionPicker = forwardRef< { id: "connectors", label: "Connectors", - subtitle: connectors.length + subtitle: activeConnectors.length ? "Choose the exact account for tool use" : "No connected accounts yet", icon: , type: "branch", - disabled: connectors.length === 0, + disabled: activeConnectors.length === 0, value: { kind: "view", view: { kind: "connectors" } }, }, ], - [connectors.length] + [activeConnectors.length] ); const searchNodes = useMemo[]>(() => { @@ -385,7 +389,7 @@ export const DocumentMentionPicker = forwardRef< id: getMentionDocKey(mention), label: mention.title, subtitle: "Connector account", - icon: getConnectorIcon(mention.document_type, "size-4") ?? , + icon: getConnectorIcon(mention.connector_type, "size-4") ?? , type: "item" as const, disabled: selectedKeys.has(getMentionDocKey(mention)), value: { kind: "mention" as const, mention }, @@ -404,7 +408,7 @@ export const DocumentMentionPicker = forwardRef< const connectorTypeEntries = useMemo(() => { const byType = new Map(); - for (const connector of connectors.filter((c) => c.is_active)) { + for (const connector of activeConnectors) { const list = byType.get(connector.connector_type) ?? []; list.push(connector); byType.set(connector.connector_type, list); @@ -412,7 +416,7 @@ export const DocumentMentionPicker = forwardRef< return Array.from(byType.entries()).sort(([a], [b]) => titleForConnectorType(a).localeCompare(titleForConnectorType(b)) ); - }, [connectors]); + }, [activeConnectors]); const browseNodes = useMemo[]>(() => { if (view.kind === "root") return rootNodes; @@ -469,8 +473,8 @@ export const DocumentMentionPicker = forwardRef< }, })); } - return connectors - .filter((connector) => connector.is_active && connector.connector_type === view.connectorType) + return activeConnectors + .filter((connector) => connector.connector_type === view.connectorType) .map((connector) => { const mention = makeConnectorMention(connector); return { @@ -484,7 +488,7 @@ export const DocumentMentionPicker = forwardRef< }; }); }, [ - connectors, + activeConnectors, connectorTypeEntries, folderMentions, rootNodes, diff --git a/surfsense_web/lib/chat/mention-doc-key.ts b/surfsense_web/lib/chat/mention-doc-key.ts index 114faace5..87676dbd6 100644 --- a/surfsense_web/lib/chat/mention-doc-key.ts +++ b/surfsense_web/lib/chat/mention-doc-key.ts @@ -1,18 +1,20 @@ type MentionKeyInput = { id: number; document_type?: string | null; + connector_type?: string | null; kind?: "doc" | "folder" | "connector"; }; /** * Build a stable dedup key for a mention chip. * - * The ``kind:document_type:id`` shape prevents a document and a folder - * with the same integer id from colliding in the chip array (folders - * use the ``FOLDER`` sentinel ``document_type``; the ``kind`` prefix - * is the belt-and-braces guard). + * Each mention kind keys off its real identity fields: + * docs by document type, folders by folder id, and connectors by + * connector type + account id. */ export function getMentionDocKey(doc: MentionKeyInput): string { const kind = doc.kind ?? "doc"; - return `${kind}:${doc.document_type ?? "UNKNOWN"}:${doc.id}`; + if (kind === "folder") return `folder:${doc.id}`; + if (kind === "connector") return `connector:${doc.connector_type ?? "UNKNOWN"}:${doc.id}`; + return `doc:${doc.document_type ?? "UNKNOWN"}:${doc.id}`; } diff --git a/surfsense_web/lib/connector-telemetry.ts b/surfsense_web/lib/connector-telemetry.ts index ef1b3de32..eeccea1e8 100644 --- a/surfsense_web/lib/connector-telemetry.ts +++ b/surfsense_web/lib/connector-telemetry.ts @@ -1,22 +1,17 @@ -import { EnumConnectorName } from "@/contracts/enums/connector"; -import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import { - OAUTH_CONNECTORS, COMPOSIO_CONNECTORS, CRAWLERS, + OAUTH_CONNECTORS, OTHER_CONNECTORS, } from "@/components/assistant-ui/connector-popup/constants/connector-constants"; +import { EnumConnectorName } from "@/contracts/enums/connector"; +import type { SearchSourceConnector } from "@/contracts/types/connector.types"; // ============================================================================= // Connector Telemetry Types & Registry // ============================================================================= -export type ConnectorTelemetryGroup = - | "oauth" - | "composio" - | "crawler" - | "other" - | "unknown"; +export type ConnectorTelemetryGroup = "oauth" | "composio" | "crawler" | "other" | "unknown"; export interface ConnectorTelemetryMeta { connector_type: string; @@ -31,10 +26,11 @@ export interface ConnectorTelemetryMeta { * picked up here, so adding a new integration does NOT require touching * `lib/posthog/events.ts` or per-connector tracking code. */ -const CONNECTOR_TELEMETRY_REGISTRY: ReadonlyMap< - string, - ConnectorTelemetryMeta -> = (() => { +let connectorTelemetryRegistry: ReadonlyMap | undefined; + +function getConnectorTelemetryRegistry(): ReadonlyMap { + if (connectorTelemetryRegistry) return connectorTelemetryRegistry; + const map = new Map(); for (const c of OAUTH_CONNECTORS) { @@ -70,18 +66,17 @@ const CONNECTOR_TELEMETRY_REGISTRY: ReadonlyMap< }); } - return map; -})(); + connectorTelemetryRegistry = map; + return connectorTelemetryRegistry; +} /** * Returns telemetry metadata for a connector_type, or a minimal "unknown" * record so tracking never no-ops for connectors that exist in the backend * but were forgotten in the UI registry. */ -export function getConnectorTelemetryMeta( - connectorType: string, -): ConnectorTelemetryMeta { - const hit = CONNECTOR_TELEMETRY_REGISTRY.get(connectorType); +export function getConnectorTelemetryMeta(connectorType: string): ConnectorTelemetryMeta { + const hit = getConnectorTelemetryRegistry().get(connectorType); if (hit) return hit; return { @@ -101,34 +96,20 @@ export function getConnectorTelemetryMeta( * These are used for connectors that were NOT created via MCP OAuth. */ const LEGACY_REAUTH_ENDPOINTS: Partial> = { - [EnumConnectorName.LINEAR_CONNECTOR]: - "/api/v1/auth/linear/connector/reauth", - [EnumConnectorName.JIRA_CONNECTOR]: - "/api/v1/auth/jira/connector/reauth", - [EnumConnectorName.NOTION_CONNECTOR]: - "/api/v1/auth/notion/connector/reauth", - [EnumConnectorName.GOOGLE_DRIVE_CONNECTOR]: - "/api/v1/auth/google/drive/connector/reauth", - [EnumConnectorName.GOOGLE_GMAIL_CONNECTOR]: - "/api/v1/auth/google/gmail/connector/reauth", - [EnumConnectorName.GOOGLE_CALENDAR_CONNECTOR]: - "/api/v1/auth/google/calendar/connector/reauth", - [EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR]: - "/api/v1/auth/composio/connector/reauth", - [EnumConnectorName.COMPOSIO_GMAIL_CONNECTOR]: - "/api/v1/auth/composio/connector/reauth", - [EnumConnectorName.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR]: - "/api/v1/auth/composio/connector/reauth", - [EnumConnectorName.ONEDRIVE_CONNECTOR]: - "/api/v1/auth/onedrive/connector/reauth", - [EnumConnectorName.DROPBOX_CONNECTOR]: - "/api/v1/auth/dropbox/connector/reauth", - [EnumConnectorName.CONFLUENCE_CONNECTOR]: - "/api/v1/auth/confluence/connector/reauth", - [EnumConnectorName.TEAMS_CONNECTOR]: - "/api/v1/auth/teams/connector/reauth", - [EnumConnectorName.DISCORD_CONNECTOR]: - "/api/v1/auth/discord/connector/reauth", + [EnumConnectorName.LINEAR_CONNECTOR]: "/api/v1/auth/linear/connector/reauth", + [EnumConnectorName.JIRA_CONNECTOR]: "/api/v1/auth/jira/connector/reauth", + [EnumConnectorName.NOTION_CONNECTOR]: "/api/v1/auth/notion/connector/reauth", + [EnumConnectorName.GOOGLE_DRIVE_CONNECTOR]: "/api/v1/auth/google/drive/connector/reauth", + [EnumConnectorName.GOOGLE_GMAIL_CONNECTOR]: "/api/v1/auth/google/gmail/connector/reauth", + [EnumConnectorName.GOOGLE_CALENDAR_CONNECTOR]: "/api/v1/auth/google/calendar/connector/reauth", + [EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR]: "/api/v1/auth/composio/connector/reauth", + [EnumConnectorName.COMPOSIO_GMAIL_CONNECTOR]: "/api/v1/auth/composio/connector/reauth", + [EnumConnectorName.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR]: "/api/v1/auth/composio/connector/reauth", + [EnumConnectorName.ONEDRIVE_CONNECTOR]: "/api/v1/auth/onedrive/connector/reauth", + [EnumConnectorName.DROPBOX_CONNECTOR]: "/api/v1/auth/dropbox/connector/reauth", + [EnumConnectorName.CONFLUENCE_CONNECTOR]: "/api/v1/auth/confluence/connector/reauth", + [EnumConnectorName.TEAMS_CONNECTOR]: "/api/v1/auth/teams/connector/reauth", + [EnumConnectorName.DISCORD_CONNECTOR]: "/api/v1/auth/discord/connector/reauth", }; /** @@ -138,9 +119,7 @@ const LEGACY_REAUTH_ENDPOINTS: Partial> = { * the URL from the service key. Legacy OAuth connectors fall back to the * static ``LEGACY_REAUTH_ENDPOINTS`` map. */ -export function getReauthEndpoint( - connector: SearchSourceConnector, -): string | undefined { +export function getReauthEndpoint(connector: SearchSourceConnector): string | undefined { const mcpService = connector.config?.mcp_service as string | undefined; if (mcpService) { return `/api/v1/auth/mcp/${mcpService}/connector/reauth`; From 79f5e8f88c22afba5075dac7b5302bb06bb285c5 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Tue, 26 May 2026 22:40:22 +0530 Subject: [PATCH 077/198] feat(web): add connector display definitions and enhance composer suggestion components --- .../constants/connector-constants.ts | 15 ++- .../new-chat/composer-suggestion-popup.tsx | 43 +++++-- .../new-chat/document-mention-picker.tsx | 119 +++++++++--------- 3 files changed, 105 insertions(+), 72 deletions(-) diff --git a/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts b/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts index 01a911d70..dedb35465 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts +++ b/surfsense_web/components/assistant-ui/connector-popup/constants/connector-constants.ts @@ -1,5 +1,4 @@ import { EnumConnectorName } from "@/contracts/enums/connector"; -import type { SearchSourceConnector } from "@/contracts/types/connector.types"; /** * Connectors that operate in real time (no background indexing). @@ -230,6 +229,20 @@ export const COMPOSIO_CONNECTORS = [ }, ] as const; +export const CONNECTOR_DISPLAY_DEFINITIONS = [ + ...OAUTH_CONNECTORS, + ...CRAWLERS, + ...OTHER_CONNECTORS, + ...COMPOSIO_CONNECTORS, +] as const; + +export function getConnectorTitle(connectorType: string): string { + return ( + CONNECTOR_DISPLAY_DEFINITIONS.find((connector) => connector.connectorType === connectorType) + ?.title ?? connectorType + ); +} + // Composio Toolkits (available integrations via Composio) export const COMPOSIO_TOOLKITS = [ { diff --git a/surfsense_web/components/new-chat/composer-suggestion-popup.tsx b/surfsense_web/components/new-chat/composer-suggestion-popup.tsx index fd46d8ee7..d72cf1366 100644 --- a/surfsense_web/components/new-chat/composer-suggestion-popup.tsx +++ b/surfsense_web/components/new-chat/composer-suggestion-popup.tsx @@ -31,7 +31,7 @@ function ComposerSuggestionPopoverContent({ onCloseAutoFocus?.(event); }} className={cn( - "w-[280px] overflow-hidden rounded-md border border-popover-border bg-popover p-0 text-popover-foreground shadow-md sm:w-[320px]", + "w-[256px] overflow-hidden rounded-md border border-popover-border bg-popover p-0 text-popover-foreground shadow-md sm:w-[288px]", "data-[state=open]:!animate-none data-[state=closed]:!animate-none data-[state=open]:!duration-0 data-[state=closed]:!duration-0", className )} @@ -47,14 +47,14 @@ const ComposerSuggestionList = React.forwardRef< >(({ className, ...props }, ref) => (
)); ComposerSuggestionList.displayName = "ComposerSuggestionList"; function ComposerSuggestionGroup({ className, ...props }: React.HTMLAttributes) { - return
; + return
; } function ComposerSuggestionGroupHeading({ @@ -63,12 +63,32 @@ function ComposerSuggestionGroupHeading({ }: React.HTMLAttributes) { return (
); } +function ComposerSuggestionHeader({ + className, + icon, + children, + ...props +}: React.HTMLAttributes & { icon?: React.ReactNode }) { + return ( +
+ {icon ? {icon} : null} + {children} +
+ ); +} + const ComposerSuggestionItem = React.forwardRef< HTMLButtonElement, Omit, "variant"> & { @@ -83,7 +103,7 @@ const ComposerSuggestionItem = React.forwardRef< variant="ghost" disabled={disabled} className={cn( - "h-auto w-full justify-start gap-2 rounded-md px-3 py-2 text-left text-sm font-normal transition-colors", + "h-auto w-full justify-start gap-2 rounded-md px-2.5 py-1.5 text-left text-sm font-normal transition-colors", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", muted && !selected && "text-muted-foreground hover:bg-accent hover:text-accent-foreground", selected && "bg-accent text-accent-foreground", @@ -99,7 +119,7 @@ ComposerSuggestionItem.displayName = "ComposerSuggestionItem"; function ComposerSuggestionSeparator({ className, ...props }: React.ComponentProps) { return ( -
+
); @@ -111,10 +131,10 @@ function ComposerSuggestionMessage({ variant = "muted", }: React.HTMLAttributes & { variant?: "muted" | "destructive" }) { return ( -
+

-

+
+
{["a", "b", "c", "d", "e"].map((id, index) => (
= 3 && "hidden sm:flex" )} > @@ -156,6 +176,7 @@ export { ComposerSuggestionList, ComposerSuggestionGroup, ComposerSuggestionGroupHeading, + ComposerSuggestionHeader, ComposerSuggestionItem, ComposerSuggestionSeparator, ComposerSuggestionMessage, diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index c424ae6c3..c6829507f 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -8,7 +8,7 @@ import { ChevronRight, Files, Folder as FolderIcon, - Plug, + Unplug, } from "lucide-react"; import { forwardRef, @@ -23,14 +23,12 @@ import type * as React from "react"; import type { MentionedDocumentInfo } from "@/atoms/chat/mentioned-documents.atom"; import { useAtomValue } from "jotai"; import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms"; -import { - COMPOSIO_CONNECTORS, - OAUTH_CONNECTORS, -} from "@/components/assistant-ui/connector-popup/constants/connector-constants"; +import { getConnectorTitle } from "@/components/assistant-ui/connector-popup/constants/connector-constants"; import { getConnectorDisplayName } from "@/components/assistant-ui/connector-popup/tabs/all-connectors-tab"; import { ComposerSuggestionGroup, ComposerSuggestionGroupHeading, + ComposerSuggestionHeader, ComposerSuggestionItem, ComposerSuggestionList, ComposerSuggestionMessage, @@ -94,19 +92,6 @@ function useDebounced(value: T, delay = DEBOUNCE_MS) { return debounced; } -function titleForConnectorType(connectorType: string) { - const configured = - OAUTH_CONNECTORS.find((c) => c.connectorType === connectorType) || - COMPOSIO_CONNECTORS.find((c) => c.connectorType === connectorType); - return ( - configured?.title || - connectorType - .replace(/_/g, " ") - .replace(/connector/gi, "") - .trim() - ); -} - function makeDocMention(doc: Pick): MentionedDocumentInfo { return { id: doc.id, @@ -130,7 +115,7 @@ function makeConnectorMention( connector: SearchSourceConnector ): Extract { const accountName = getConnectorDisplayName(connector.name); - const connectorTitle = titleForConnectorType(connector.connector_type); + const connectorTitle = getConnectorTitle(connector.connector_type); return { id: connector.id, title: `${connectorTitle}: ${accountName}`, @@ -319,6 +304,7 @@ export const DocumentMentionPicker = forwardRef< () => new Set(initialSelectedDocuments.map((d) => getMentionDocKey(d))), [initialSelectedDocuments] ); + const showSurfsenseDocsRootRef = useRef((surfsenseDocs?.items?.length ?? 0) > 0); const selectMention = useCallback( (mention: MentionedDocumentInfo) => { @@ -329,35 +315,41 @@ export const DocumentMentionPicker = forwardRef< ); const rootNodes = useMemo[]>( - () => [ - { - id: "surfsense-docs", - label: "SurfSense Docs", - subtitle: "Browse product documentation", - icon: , - type: "branch", - value: { kind: "view", view: { kind: "surfsense-docs" } }, - }, - { - id: "files-folders", - label: "Files & Folders", - subtitle: "Browse your knowledge base", - icon: , - type: "branch", - value: { kind: "view", view: { kind: "files-folders" } }, - }, - { - id: "connectors", - label: "Connectors", - subtitle: activeConnectors.length - ? "Choose the exact account for tool use" - : "No connected accounts yet", - icon: , - type: "branch", - disabled: activeConnectors.length === 0, - value: { kind: "view", view: { kind: "connectors" } }, - }, - ], + () => { + const nodes: ComposerSuggestionNode[] = []; + if (showSurfsenseDocsRootRef.current) { + nodes.push({ + id: "surfsense-docs", + label: "SurfSense Docs", + subtitle: "Browse product documentation", + icon: , + type: "branch", + value: { kind: "view", view: { kind: "surfsense-docs" } }, + }); + } + nodes.push( + { + id: "files-folders", + label: "Files & Folders", + subtitle: "Browse your knowledge base", + icon: , + type: "branch", + value: { kind: "view", view: { kind: "files-folders" } }, + }, + { + id: "connectors", + label: "Connectors", + subtitle: activeConnectors.length + ? "Choose the exact account for tool use" + : "No connected accounts yet", + icon: , + type: "branch", + disabled: activeConnectors.length === 0, + value: { kind: "view", view: { kind: "connectors" } }, + } + ); + return nodes; + }, [activeConnectors.length] ); @@ -389,7 +381,7 @@ export const DocumentMentionPicker = forwardRef< id: getMentionDocKey(mention), label: mention.title, subtitle: "Connector account", - icon: getConnectorIcon(mention.connector_type, "size-4") ?? , + icon: getConnectorIcon(mention.connector_type, "size-4") ?? , type: "item" as const, disabled: selectedKeys.has(getMentionDocKey(mention)), value: { kind: "mention" as const, mention }, @@ -414,7 +406,7 @@ export const DocumentMentionPicker = forwardRef< byType.set(connector.connector_type, list); } return Array.from(byType.entries()).sort(([a], [b]) => - titleForConnectorType(a).localeCompare(titleForConnectorType(b)) + getConnectorTitle(a).localeCompare(getConnectorTitle(b)) ); }, [activeConnectors]); @@ -459,16 +451,16 @@ export const DocumentMentionPicker = forwardRef< if (view.kind === "connectors") { return connectorTypeEntries.map(([connectorType, typeConnectors]) => ({ id: `connector-type:${connectorType}`, - label: titleForConnectorType(connectorType), + label: getConnectorTitle(connectorType), subtitle: `${typeConnectors.length} ${typeConnectors.length === 1 ? "account" : "accounts"}`, - icon: getConnectorIcon(connectorType, "size-4") ?? , + icon: getConnectorIcon(connectorType, "size-4") ?? , type: "branch" as const, value: { kind: "view" as const, view: { kind: "connector-type" as const, connectorType, - title: titleForConnectorType(connectorType), + title: getConnectorTitle(connectorType), }, }, })); @@ -481,7 +473,7 @@ export const DocumentMentionPicker = forwardRef< id: getMentionDocKey(mention), label: getConnectorDisplayName(connector.name), subtitle: `${view.title} account`, - icon: getConnectorIcon(connector.connector_type, "size-4") ?? , + icon: getConnectorIcon(connector.connector_type, "size-4") ?? , type: "item" as const, disabled: selectedKeys.has(getMentionDocKey(mention)), value: { kind: "mention" as const, mention }, @@ -571,13 +563,20 @@ export const DocumentMentionPicker = forwardRef< {title ? ( <> - } - muted - onClick={handleBack} + + + + } > - {title} - + {title} + ) : null} From 17293125efc5f9bee28aade13ee320add5ba3029 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Wed, 27 May 2026 00:26:44 +0530 Subject: [PATCH 078/198] feat(web): implement recent mention management in document mention picker and enhance composer component --- .../components/assistant-ui/thread.tsx | 7 +- .../new-chat/composer-suggestion-popup.tsx | 32 +-- .../new-chat/document-mention-picker.tsx | 271 ++++++++++++++---- .../components/new-chat/prompt-picker.tsx | 6 +- 4 files changed, 243 insertions(+), 73 deletions(-) diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 7143a2cdf..9ea8bc982 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -70,6 +70,7 @@ import { UserMessage } from "@/components/assistant-ui/user-message"; import { ComposerSuggestionPopoverContent } from "@/components/new-chat/composer-suggestion-popup"; import { DocumentMentionPicker, + promoteRecentMention, type DocumentMentionPickerRef, } from "../new-chat/document-mention-picker"; import { PromptPicker, type PromptPickerRef } from "@/components/new-chat/prompt-picker"; @@ -768,6 +769,7 @@ const Composer: FC = () => { ); const handleDocumentsMention = useCallback((mentions: MentionedDocumentInfo[]) => { + const parsedSearchSpaceId = Number(search_space_id); const editorMentionedDocs = editorRef.current?.getMentionedDocuments() ?? []; const editorDocKeys = new Set(editorMentionedDocs.map((doc) => getMentionDocKey(doc))); @@ -775,6 +777,9 @@ const Composer: FC = () => { const key = getMentionDocKey(mention); if (editorDocKeys.has(key)) continue; editorRef.current?.insertMentionChip(mention); + if (Number.isFinite(parsedSearchSpaceId)) { + promoteRecentMention(parsedSearchSpaceId, mention); + } // Track within the loop so a duplicate-in-batch can't double-insert. editorDocKeys.add(key); } @@ -783,7 +788,7 @@ const Composer: FC = () => { // onChange — no second write path here. setMentionQuery(""); setSuggestionAnchorPoint(null); - }, []); + }, [search_space_id]); useEffect(() => { const editor = editorRef.current; diff --git a/surfsense_web/components/new-chat/composer-suggestion-popup.tsx b/surfsense_web/components/new-chat/composer-suggestion-popup.tsx index d72cf1366..2909fbf86 100644 --- a/surfsense_web/components/new-chat/composer-suggestion-popup.tsx +++ b/surfsense_web/components/new-chat/composer-suggestion-popup.tsx @@ -31,7 +31,7 @@ function ComposerSuggestionPopoverContent({ onCloseAutoFocus?.(event); }} className={cn( - "w-[256px] overflow-hidden rounded-md border border-popover-border bg-popover p-0 text-popover-foreground shadow-md sm:w-[288px]", + "w-[232px] overflow-hidden rounded-md border border-popover-border bg-popover p-0 text-popover-foreground shadow-md sm:w-[264px]", "data-[state=open]:!animate-none data-[state=closed]:!animate-none data-[state=open]:!duration-0 data-[state=closed]:!duration-0", className )} @@ -47,14 +47,14 @@ const ComposerSuggestionList = React.forwardRef< >(({ className, ...props }, ref) => (
)); ComposerSuggestionList.displayName = "ComposerSuggestionList"; function ComposerSuggestionGroup({ className, ...props }: React.HTMLAttributes) { - return
; + return
; } function ComposerSuggestionGroupHeading({ @@ -63,7 +63,7 @@ function ComposerSuggestionGroupHeading({ }: React.HTMLAttributes) { return (
); @@ -78,12 +78,12 @@ function ComposerSuggestionHeader({ return (
- {icon ? {icon} : null} + {icon ? {icon} : null} {children}
); @@ -103,7 +103,7 @@ const ComposerSuggestionItem = React.forwardRef< variant="ghost" disabled={disabled} className={cn( - "h-auto w-full justify-start gap-2 rounded-md px-2.5 py-1.5 text-left text-sm font-normal transition-colors", + "h-auto w-full justify-start gap-1.5 rounded-md px-2 py-1 text-left text-xs font-normal transition-colors", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", muted && !selected && "text-muted-foreground hover:bg-accent hover:text-accent-foreground", selected && "bg-accent text-accent-foreground", @@ -111,7 +111,7 @@ const ComposerSuggestionItem = React.forwardRef< )} {...props} > - {icon ? {icon} : null} + {icon ? {icon} : null} {children} )); @@ -119,7 +119,7 @@ ComposerSuggestionItem.displayName = "ComposerSuggestionItem"; function ComposerSuggestionSeparator({ className, ...props }: React.ComponentProps) { return ( -
+
); @@ -134,7 +134,7 @@ function ComposerSuggestionMessage({

-

- +
+
{["a", "b", "c", "d", "e"].map((id, index) => (
= 3 && "hidden sm:flex" )} > - + - - + +
))} diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index c6829507f..3b96a7cd2 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -11,7 +11,9 @@ import { Unplug, } from "lucide-react"; import { + Fragment, forwardRef, + type UIEvent, useCallback, useDeferredValue, useEffect, @@ -19,7 +21,6 @@ import { useRef, useState, } from "react"; -import type * as React from "react"; import type { MentionedDocumentInfo } from "@/atoms/chat/mentioned-documents.atom"; import { useAtomValue } from "jotai"; import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms"; @@ -61,6 +62,8 @@ interface DocumentMentionPickerProps { const PAGE_SIZE = 20; const MIN_SEARCH_LENGTH = 2; const DEBOUNCE_MS = 100; +const RECENTS_LIMIT = 3; +const RECENTS_STORAGE_PREFIX = "surfsense:composer-mention-recents:v1:"; type BrowseView = | { kind: "root" } @@ -77,6 +80,89 @@ function isConnectorActive(connector: SearchSourceConnector) { return connector.is_active !== false; } +function isMentionedContextItem(value: unknown): value is MentionedDocumentInfo { + if (!value || typeof value !== "object") return false; + const item = value as Partial; + if (typeof item.id !== "number" || typeof item.title !== "string") return false; + if (item.kind === "doc") return typeof item.document_type === "string"; + if (item.kind === "folder") return true; + if (item.kind === "connector") { + return typeof item.connector_type === "string" && typeof item.account_name === "string"; + } + return false; +} + +function getRecentsStorageKey(searchSpaceId: number) { + return `${RECENTS_STORAGE_PREFIX}${searchSpaceId}`; +} + +function readRecentMentions(searchSpaceId: number): MentionedDocumentInfo[] { + if (typeof window === "undefined") return []; + try { + const raw = window.localStorage.getItem(getRecentsStorageKey(searchSpaceId)); + if (!raw) return []; + const parsed: unknown = JSON.parse(raw); + if (!Array.isArray(parsed)) return []; + return parsed.filter(isMentionedContextItem).slice(0, RECENTS_LIMIT); + } catch { + return []; + } +} + +function writeRecentMentions(searchSpaceId: number, mentions: MentionedDocumentInfo[]) { + if (typeof window === "undefined") return; + try { + window.localStorage.setItem( + getRecentsStorageKey(searchSpaceId), + JSON.stringify(mentions.slice(0, RECENTS_LIMIT)) + ); + } catch { + // Recents are optional UI state; storage failures should not block mention insertion. + } +} + +export function promoteRecentMention(searchSpaceId: number, mention: MentionedDocumentInfo) { + const mentionKey = getMentionDocKey(mention); + const next = [ + mention, + ...readRecentMentions(searchSpaceId).filter((item) => getMentionDocKey(item) !== mentionKey), + ].slice(0, RECENTS_LIMIT); + writeRecentMentions(searchSpaceId, next); + return next; +} + +function getMentionIcon(mention: MentionedDocumentInfo) { + if (mention.kind === "folder") return ; + if (mention.kind === "connector") { + return getConnectorIcon(mention.connector_type, "size-4") ?? ; + } + return getConnectorIcon(mention.document_type, "size-4"); +} + +function refreshRecentMention( + mention: MentionedDocumentInfo, + documents: Pick[], + folders: { id: number; name: string }[], + connectors: SearchSourceConnector[], + hasHydratedRecentDocs: boolean +): MentionedDocumentInfo | null { + if (mention.kind === "doc") { + const doc = documents.find( + (item) => item.id === mention.id && item.document_type === mention.document_type + ); + if (doc) return makeDocMention(doc); + return hasHydratedRecentDocs ? null : mention; + } + if (mention.kind === "folder") { + const folder = folders.find((item) => item.id === mention.id); + return folder ? makeFolderMention({ id: folder.id, title: folder.name }) : null; + } + const connector = connectors.find( + (item) => item.id === mention.id && item.connector_type === mention.connector_type + ); + return connector ? makeConnectorMention(connector) : null; +} + function useDebounced(value: T, delay = DEBOUNCE_MS) { const [debounced, setDebounced] = useState(value); const timeoutRef = useRef | undefined>(undefined); @@ -156,6 +242,9 @@ export const DocumentMentionPicker = forwardRef< const [currentPage, setCurrentPage] = useState(0); const [hasMore, setHasMore] = useState(false); const [isLoadingMore, setIsLoadingMore] = useState(false); + const [recentMentions, setRecentMentions] = useState(() => + readRecentMentions(searchSpaceId) + ); const [zeroFolders] = useZeroQuery(queries.folders.bySpace({ searchSpaceId })); const { data: connectors = [], isLoading: isConnectorsLoading } = useAtomValue(connectorsAtom); @@ -178,6 +267,10 @@ export const DocumentMentionPicker = forwardRef< if (hasSearch) setView({ kind: "root" }); }, [hasSearch]); + useEffect(() => { + setRecentMentions(readRecentMentions(searchSpaceId)); + }, [searchSpaceId]); + const titleSearchParams = useMemo( () => ({ search_space_id: searchSpaceId, @@ -226,24 +319,24 @@ export const DocumentMentionPicker = forwardRef< useEffect(() => { if (currentPage !== 0) return; - const combinedDocs: Pick[] = []; + const combinedDocs: Pick[] = []; - if (surfsenseDocs?.items) { - for (const doc of surfsenseDocs.items) { - combinedDocs.push({ - id: doc.id, - title: doc.title, - document_type: "SURFSENSE_DOCS", - }); + if (surfsenseDocs?.items) { + for (const doc of surfsenseDocs.items) { + combinedDocs.push({ + id: doc.id, + title: doc.title, + document_type: "SURFSENSE_DOCS", + }); + } } - } - if (titleSearchResults?.items) { - combinedDocs.push(...titleSearchResults.items); - setHasMore(titleSearchResults.has_more); - } + if (titleSearchResults?.items) { + combinedDocs.push(...titleSearchResults.items); + setHasMore(titleSearchResults.has_more); + } - setAccumulatedDocuments(filterBySearchTerm(combinedDocs)); + setAccumulatedDocuments(filterBySearchTerm(combinedDocs)); }, [titleSearchResults, surfsenseDocs, currentPage, filterBySearchTerm]); const loadNextPage = useCallback(async () => { @@ -299,6 +392,47 @@ export const DocumentMentionPicker = forwardRef< () => activeConnectors.map(makeConnectorMention), [activeConnectors] ); + const recentDocMentions = useMemo( + () => recentMentions.filter((mention) => mention.kind === "doc"), + [recentMentions] + ); + const recentDocIdsKey = useMemo( + () => recentDocMentions.map((mention) => mention.id).join(","), + [recentDocMentions] + ); + const { data: hydratedRecentDocs = [], isFetched: hasHydratedRecentDocs } = useQuery({ + queryKey: ["composer-mention-recent-docs", searchSpaceId, recentDocIdsKey], + queryFn: async () => { + const results = await Promise.allSettled( + recentDocMentions.map((mention) => documentsApiService.getDocument({ id: mention.id })) + ); + return results + .map((result) => (result.status === "fulfilled" ? result.value : null)) + .filter((doc): doc is Document => doc !== null); + }, + enabled: recentDocMentions.length > 0, + staleTime: 60 * 1000, + }); + const recentValidationDocuments = useMemo( + () => [...actualDocuments, ...hydratedRecentDocs], + [actualDocuments, hydratedRecentDocs] + ); + const visibleRecentMentions = useMemo( + () => + recentMentions + .map((mention) => + refreshRecentMention( + mention, + recentValidationDocuments, + zeroFolders ?? [], + activeConnectors, + hasHydratedRecentDocs + ) + ) + .filter((mention): mention is MentionedDocumentInfo => mention !== null) + .slice(0, RECENTS_LIMIT), + [activeConnectors, hasHydratedRecentDocs, recentMentions, recentValidationDocuments, zeroFolders] + ); const selectedKeys = useMemo( () => new Set(initialSelectedDocuments.map((d) => getMentionDocKey(d))), @@ -313,10 +447,22 @@ export const DocumentMentionPicker = forwardRef< }, [initialSelectedDocuments, onSelectionChange, onDone] ); + const recentRootNodes = useMemo[]>( + () => + visibleRecentMentions.map((mention) => ({ + id: `recent:${getMentionDocKey(mention)}`, + label: mention.title, + icon: getMentionIcon(mention), + type: "item" as const, + disabled: selectedKeys.has(getMentionDocKey(mention)), + value: { kind: "mention" as const, mention }, + })), + [visibleRecentMentions, selectedKeys] + ); const rootNodes = useMemo[]>( () => { - const nodes: ComposerSuggestionNode[] = []; + const nodes: ComposerSuggestionNode[] = [...recentRootNodes]; if (showSurfsenseDocsRootRef.current) { nodes.push({ id: "surfsense-docs", @@ -350,7 +496,7 @@ export const DocumentMentionPicker = forwardRef< ); return nodes; }, - [activeConnectors.length] + [activeConnectors.length, recentRootNodes] ); const searchNodes = useMemo[]>(() => { @@ -519,10 +665,11 @@ export const DocumentMentionPicker = forwardRef< onBack: handleBack, ref, }); + const canLoadMoreDocuments = hasSearch || view.kind === "files-folders"; const handleScroll = useCallback( - (e: React.UIEvent) => { - if (view.kind === "connectors" || view.kind === "connector-type") return; + (e: UIEvent) => { + if (!canLoadMoreDocuments) return; const target = e.currentTarget; const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight; @@ -530,7 +677,7 @@ export const DocumentMentionPicker = forwardRef< loadNextPage(); } }, - [hasMore, isLoadingMore, loadNextPage, view.kind] + [canLoadMoreDocuments, hasMore, isLoadingMore, loadNextPage] ); const actualLoading = @@ -564,15 +711,21 @@ export const DocumentMentionPicker = forwardRef< {title ? ( <> { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + handleBack(); + } + }} + className="cursor-pointer rounded-sm transition-colors hover:text-foreground focus-visible:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" icon={ - + + + } > {title} @@ -586,31 +739,43 @@ export const DocumentMentionPicker = forwardRef< {hasSearch ? ( Suggested Context ) : null} - {visibleNodes.map((node, index) => ( - !node.disabled && handleNodeSelect(node)} - onMouseEnter={() => navigator.setHighlightedIndex(index)} - > - - - {node.label} + {!hasSearch && view.kind === "root" && recentRootNodes.length > 0 ? ( + Recents + ) : null} + {visibleNodes.map((node, index) => { + const showRecentsSeparator = + !hasSearch && + view.kind === "root" && + recentRootNodes.length > 0 && + index === recentRootNodes.length; + return ( + + {showRecentsSeparator ? : null} + !node.disabled && handleNodeSelect(node)} + onMouseEnter={() => navigator.setHighlightedIndex(index)} + > + + + {node.label} + + {node.subtitle ? ( + + {node.subtitle} + + ) : null} - {node.subtitle ? ( - - {node.subtitle} - - ) : null} - - {node.type === "branch" ? ( - - ) : null} - - ))} + {node.type === "branch" ? ( + + ) : null} + + + ); + })} ) : ( @@ -618,7 +783,7 @@ export const DocumentMentionPicker = forwardRef< )} - {isLoadingMore && ( + {canLoadMoreDocuments && isLoadingMore && (
diff --git a/surfsense_web/components/new-chat/prompt-picker.tsx b/surfsense_web/components/new-chat/prompt-picker.tsx index b8fba5b61..54d44662c 100644 --- a/surfsense_web/components/new-chat/prompt-picker.tsx +++ b/surfsense_web/components/new-chat/prompt-picker.tsx @@ -142,12 +142,12 @@ export const PromptPicker = forwardRef(funct if (el) itemRefs.current.set(index, el); else itemRefs.current.delete(index); }} - icon={} + icon={} selected={index === highlightedIndex} onClick={() => handleSelect(index)} onMouseEnter={() => setHighlightedIndex(index)} > - {action.name} + {action.name} ))} @@ -157,7 +157,7 @@ export const PromptPicker = forwardRef(funct if (el) itemRefs.current.set(createPromptIndex, el); else itemRefs.current.delete(createPromptIndex); }} - icon={} + icon={} muted selected={highlightedIndex === createPromptIndex} onClick={() => handleSelect(createPromptIndex)} From dbf235cbdaf3f418b9531f782b10a8732012634c Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Wed, 27 May 2026 00:43:50 +0530 Subject: [PATCH 079/198] feat(web): enhance composer suggestion skeleton, improve document mention picker loading states & improved LLM prompt block for connector mentions --- .../app/tasks/chat/stream_new_chat.py | 7 +++--- .../new-chat/composer-suggestion-popup.tsx | 14 +++++++---- .../new-chat/document-mention-picker.tsx | 24 ++++++++++++------- .../components/new-chat/prompt-picker.tsx | 2 +- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/surfsense_backend/app/tasks/chat/stream_new_chat.py b/surfsense_backend/app/tasks/chat/stream_new_chat.py index 81c801959..1b2a4cfbb 100644 --- a/surfsense_backend/app/tasks/chat/stream_new_chat.py +++ b/surfsense_backend/app/tasks/chat/stream_new_chat.py @@ -1401,14 +1401,15 @@ async def stream_new_chat( continue connector_lines.append( f' - connector_id={connector_id}, connector_type="{connector_type}", ' - f'account="{account_name or ""}"' + f'account_name="{account_name or ""}"' ) if connector_lines: context_parts.append( "\n" "The user selected these exact connector accounts with @. " - "For read, write, or HITL tool calls involving these services, " - "prefer the matching connector_id instead of guessing from available accounts:\n" + "These entries are selection metadata, not retrieved connector content. " + "When a connector-backed tool needs an account, use the matching " + "connector_id from this list if the tool supports connector_id:\n" + "\n".join(connector_lines) + "\n" ) diff --git a/surfsense_web/components/new-chat/composer-suggestion-popup.tsx b/surfsense_web/components/new-chat/composer-suggestion-popup.tsx index 2909fbf86..3fdf48875 100644 --- a/surfsense_web/components/new-chat/composer-suggestion-popup.tsx +++ b/surfsense_web/components/new-chat/composer-suggestion-popup.tsx @@ -31,7 +31,7 @@ function ComposerSuggestionPopoverContent({ onCloseAutoFocus?.(event); }} className={cn( - "w-[232px] overflow-hidden rounded-md border border-popover-border bg-popover p-0 text-popover-foreground shadow-md sm:w-[264px]", + "w-[232px] select-none overflow-hidden rounded-md border border-popover-border bg-popover p-0 text-popover-foreground shadow-md sm:w-[264px]", "data-[state=open]:!animate-none data-[state=closed]:!animate-none data-[state=open]:!duration-0 data-[state=closed]:!duration-0", className )} @@ -145,18 +145,24 @@ function ComposerSuggestionMessage({ ); } -function ComposerSuggestionSkeleton() { +function ComposerSuggestionSkeleton({ + rows = 5, + mobileRows = 3, +}: { + rows?: number; + mobileRows?: number; +}) { return (
- {["a", "b", "c", "d", "e"].map((id, index) => ( + {Array.from({ length: rows }, (_, index) => `skeleton-row-${index}`).map((id, index) => (
= 3 && "hidden sm:flex" + index >= mobileRows && "hidden sm:flex" )} > diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index 3b96a7cd2..c26e51922 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -438,7 +438,7 @@ export const DocumentMentionPicker = forwardRef< () => new Set(initialSelectedDocuments.map((d) => getMentionDocKey(d))), [initialSelectedDocuments] ); - const showSurfsenseDocsRootRef = useRef((surfsenseDocs?.items?.length ?? 0) > 0); + const showSurfsenseDocsRoot = surfsenseDocsList.length > 0; const selectMention = useCallback( (mention: MentionedDocumentInfo) => { @@ -463,7 +463,7 @@ export const DocumentMentionPicker = forwardRef< const rootNodes = useMemo[]>( () => { const nodes: ComposerSuggestionNode[] = [...recentRootNodes]; - if (showSurfsenseDocsRootRef.current) { + if (showSurfsenseDocsRoot) { nodes.push({ id: "surfsense-docs", label: "SurfSense Docs", @@ -496,7 +496,7 @@ export const DocumentMentionPicker = forwardRef< ); return nodes; }, - [activeConnectors.length, recentRootNodes] + [activeConnectors.length, recentRootNodes, showSurfsenseDocsRoot] ); const searchNodes = useMemo[]>(() => { @@ -680,11 +680,18 @@ export const DocumentMentionPicker = forwardRef< [canLoadMoreDocuments, hasMore, isLoadingMore, loadNextPage] ); + const isRootBrowseView = !hasSearch && view.kind === "root"; + const isVisibleViewLoading = hasSearch + ? isTitleSearchLoading || isSurfsenseDocsLoading || isConnectorsLoading + : view.kind === "surfsense-docs" + ? isSurfsenseDocsLoading + : view.kind === "files-folders" + ? isTitleSearchLoading + : view.kind === "connectors" || view.kind === "connector-type" + ? isConnectorsLoading + : false; const actualLoading = - (isTitleSearchLoading || isSurfsenseDocsLoading || isConnectorsLoading) && - !isSingleCharSearch && - visibleNodes.length === 0 && - (view.kind === "root" || hasSearch); + isVisibleViewLoading && !isSingleCharSearch && visibleNodes.length === 0 && !isRootBrowseView; const title = hasSearch || view.kind === "root" @@ -703,9 +710,10 @@ export const DocumentMentionPicker = forwardRef< onScroll={handleScroll} role="listbox" tabIndex={-1} + className={isRootBrowseView ? "max-h-none overflow-visible sm:max-h-none" : undefined} > {actualLoading ? ( - + ) : ( {title ? ( diff --git a/surfsense_web/components/new-chat/prompt-picker.tsx b/surfsense_web/components/new-chat/prompt-picker.tsx index 54d44662c..986a5d608 100644 --- a/surfsense_web/components/new-chat/prompt-picker.tsx +++ b/surfsense_web/components/new-chat/prompt-picker.tsx @@ -127,7 +127,7 @@ export const PromptPicker = forwardRef(funct return ( {isLoading ? ( - + ) : isError ? ( Failed to load prompts ) : filtered.length === 0 ? ( From b8450e60d5f2b7fa61d725afa3b2a24c982c7f39 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Wed, 27 May 2026 01:41:37 +0530 Subject: [PATCH 080/198] refactor(web): update Switch component styles in ComposerAction for improved layout and consistency --- surfsense_web/components/assistant-ui/thread.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 9ea8bc982..5c5f99940 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -1077,7 +1077,7 @@ const ComposerAction: FC = ({ isBlockedByOtherUser = false )} @@ -1317,7 +1317,7 @@ const ComposerAction: FC = ({ isBlockedByOtherUser = false )} @@ -1367,7 +1367,7 @@ const ComposerAction: FC = ({ isBlockedByOtherUser = false ); @@ -1419,7 +1419,7 @@ const ComposerAction: FC = ({ isBlockedByOtherUser = false onPointerDown={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()} onCheckedChange={() => toggleToolGroup(toolNames)} - className="shrink-0 scale-[0.6]" + className="mr-2 shrink-0 origin-right scale-[0.6]" /> @@ -1448,7 +1448,7 @@ const ComposerAction: FC = ({ isBlockedByOtherUser = false ); @@ -1488,7 +1488,7 @@ const ComposerAction: FC = ({ isBlockedByOtherUser = false ); From 123f0d3b5d7e5b56c1fccd9583ae4699e7e6bc62 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:30:21 +0200 Subject: [PATCH 081/198] docs(automation): add v2 design plan baseline Track the initial v2 design document for the SurfSense automation feature. This is the baseline snapshot of the design before applying the v1-minimum scope narrowing (capability trimming, MCP deferral, queue-routing deferral). Subsequent commits trim this down to the v1 scope. --- automation-design-plan.md | 1395 +++++++++++++++++++++++++++++++++++++ 1 file changed, 1395 insertions(+) create mode 100644 automation-design-plan.md diff --git a/automation-design-plan.md b/automation-design-plan.md new file mode 100644 index 000000000..072f7ad99 --- /dev/null +++ b/automation-design-plan.md @@ -0,0 +1,1395 @@ +# SurfSense Automation Feature — Design Plan (v2) + +A generic, extensible automation system for SurfSense that lets users (and +future SurfSense features) trigger agent work on a schedule, on an external +event, or on demand — with the ability to author automations either by hand +or from a natural-language description that yields an editable, structured +definition. + +This document supersedes the v1 draft. It folds in the design audit pass and +the corrections from working through worked examples (notably: removing the +connector bias, clarifying the executor's role, integrating MCP cleanly, and +committing to JSON Schema as the single declarative language). + +--- + +## 1. The load-bearing principle + +> **The JSON definition is the program. Everything else is interpreter.** + +Every decision in this document serves that principle. If we ever face a +design choice and one option lets some behavior leak out of the definition +into the engine, we pick the other option. + +Three properties follow from this principle, and they're the reason the +system will survive feature growth: + +- **Reproducibility** — same definition + same inputs → same observable + behavior, regardless of which version of the engine runs it. +- **Portability** — definitions can be exported, imported, version- + controlled, code-reviewed, and shared across SurfSense instances. +- **LLM tractability** — the NL authoring flow works because the LLM only + needs to produce a self-contained JSON document that validates against a + schema. It doesn't need to understand the engine. + +--- + +## 2. The four-layer contract + +The system is structured as four layers. Layers 1, 2, and 4 are defined by +SurfSense developers (at registration time). Layer 3 is what users write +(or the NL generator produces). The runtime reads all four to do its job. + +| Layer | What it is | Defined by | +| ----- | ---------- | ---------- | +| **1. Capability registry** | What this SurfSense instance can do | Developers, at startup | +| **2. Action contract** | Per-action input/output schema | Developers, at startup | +| **3. Automation definition** | One concrete saved automation | Users (or NL generator) | +| **4. Trigger contract** | Per-trigger config and payload schemas | Developers, at startup | + +Each layer constrains the one above. The runtime reads all four but doesn't +know what's in them ahead of time. That's how a new capability or trigger +type becomes available across the engine without code changes outside its +registration. + +### Schema language + +Every shape in every layer is described in **JSON Schema (draft 2020-12).** +No exceptions, no parallel languages, no inline shorthand. Two documented +extensions on top: + +- `default: "$some_token"` — runtime-resolved defaults. The vocabulary is + fixed: `$last_fired_at`, `$creator`, `$space_default`. The engine resolves + these to values before validation. +- `x-surfsense-*` annotations — editor hints (widget type, autocomplete + source). The validator ignores them; the form editor reads them. + +--- + +## 3. Capability registry (Layer 1) + +A `Capability` is one discrete thing the SurfSense backend exposes — +"post a Slack message," "query the Search Space," "generate a podcast." It +is the atomic unit of "things automations can do." + +```python +@dataclass +class Capability: + id: str # "slack.post_message" + name: str # "Post Slack message" + description: str # for the NL generator + input_schema: dict # JSON Schema + output_schema: dict # JSON Schema + required_credentials: list[CredSpec] # what creds the handler needs + side_effects: set[SideEffect] # READ, WRITE, EXTERNAL_WRITE, + # COST_INCURRING, USER_VISIBLE + expected_duration_seconds: int # estimate or upper bound + cost_estimate: Callable[[dict], Decimal] # f(input) → estimated USD + handler: AsyncHandler +``` + +### Where capabilities live: a two-tier registry + +The capability registry has different storage requirements for different +kinds of capabilities. **Native capabilities and MCP capabilities have +different lifecycles**, so they're persisted differently: + +| Tier | What's there | Where it lives | Lifetime | +| --- | --- | --- | --- | +| **Native** | Capabilities defined in SurfSense's codebase (`search_space.query`, `agent.run`, etc.) | In-memory dict, populated at startup from `automations/capabilities/native.py` | Process lifetime, identical across all workers | +| **MCP (durable)** | The fact that this SearchSpace has connected to this MCP server, the tool list it exposes, credentials | PostgreSQL: `mcp_connections` and `mcp_tools` tables | Persistent across restarts and across time | +| **MCP (cached)** | Handler closures wrapping `(connection_id, tool_name)` | Per-worker in-memory cache, lazily built from the database on first reference | Process lifetime, rebuilt on demand | + +The reason this matters: **a user connects an MCP server on Monday, writes +an automation on Tuesday, the automation runs on Friday.** Between Monday +and Friday, workers will restart many times. Any state that only lives in +worker memory is gone. The closures generated at connection time would +not survive. + +So we split persistence by lifecycle: + +- Native capability handlers live in the codebase. Always available, no + need for the database. +- MCP capability metadata lives in the database, so the knowledge "this + SearchSpace has these capabilities" survives any restart. +- The actual closures are built on demand from the database state. They + live in worker memory only until the worker dies, at which point they + get rebuilt by the next worker that needs them. + +### MCP database schema + +```sql +CREATE TABLE mcp_connections ( + id UUID PRIMARY KEY, + search_space_id INT REFERENCES search_spaces(id), + server_url TEXT, + transport TEXT, -- "http", "stdio", etc. + name TEXT, -- "Slack (Acme workspace)" + access_token BYTEA, -- encrypted at rest + refresh_token BYTEA, -- encrypted at rest + expires_at TIMESTAMPTZ, + last_harvested_at TIMESTAMPTZ, + created_at TIMESTAMPTZ, + created_by INT REFERENCES users(id) +); + +CREATE TABLE mcp_tools ( + id UUID PRIMARY KEY, + connection_id UUID REFERENCES mcp_connections(id) ON DELETE CASCADE, + name TEXT, -- "post_message" + description TEXT, + input_schema JSONB, + output_schema JSONB, + side_effects TEXT[], -- inferred or admin-curated + UNIQUE (connection_id, name) +); +``` + +### MCP lifecycle: connect, harvest, invoke + +Three phases, each with distinct concerns. + +**Phase 1 — Connect (one-time, on user action).** User clicks "Connect +Slack MCP." OAuth flow completes. A row is added to `mcp_connections` +with the encrypted tokens. + +**Phase 2 — Harvest (right after connect, also re-runnable).** SurfSense +opens a temporary client to the MCP server, calls `tools/list`, and writes +one row to `mcp_tools` per discovered tool. The temporary client is then +discarded; only the database state persists. + +```python +async def harvest_mcp_server(connection_id: UUID, ctx): + connection = await ctx.db.get(MCPConnection, connection_id) + client = build_temporary_client(connection) + tools = await client.list_tools() + + # Replace existing tool rows for this connection + await ctx.db.execute( + delete(MCPTool).where(MCPTool.connection_id == connection_id) + ) + for tool in tools: + ctx.db.add(MCPTool( + connection_id=connection_id, + name=tool.name, + description=tool.description, + input_schema=tool.inputSchema, + output_schema=tool.outputSchema, + side_effects=infer_side_effects(tool), + )) + connection.last_harvested_at = now() + await ctx.db.commit() +``` + +Harvesting can be re-run on a schedule (say, daily) or on user request, +to pick up new tools the server has added. + +**Phase 3 — Invoke (every time a step references an MCP capability).** +This is where the closure gets built. The executor calls +`ctx.get_capability("slack.post_message")`. The worker's in-memory cache is +checked; on miss, the database is queried: + +```python +async def get_capability(capability_id: str, ctx: ActionContext) -> Capability: + cached = _WORKER_CAPABILITY_CACHE.get((ctx.search_space.id, capability_id)) + if cached: + return cached + + if is_native(capability_id): + capability = _NATIVE_REGISTRY[capability_id] + else: + # MCP path: look up tool metadata + tool_row = await ctx.db.execute( + select(MCPTool) + .join(MCPConnection) + .where(MCPConnection.search_space_id == ctx.search_space.id) + .where(tool_qualified_name(MCPTool, MCPConnection) == capability_id) + ) + capability = Capability( + id=capability_id, + input_schema=tool_row.input_schema, + output_schema=tool_row.output_schema, + side_effects=set(tool_row.side_effects), + handler=make_mcp_handler( + connection_id=tool_row.connection_id, + tool_name=tool_row.name, + ), + ) + + _WORKER_CAPABILITY_CACHE[(ctx.search_space.id, capability_id)] = capability + return capability +``` + +The closure created by `make_mcp_handler` captures only the connection ID +and tool name. When invoked, it asks `ctx.resolve_mcp_client(connection_id)` +to build an authenticated client from the connection record (including +token refresh if needed). That client is also transient — built per call, +discarded after. + +### Credentials: resolved at the moment of use + +The handler doesn't carry credentials and the closure doesn't capture them. +When invoked, the handler asks `ActionContext` for what it needs: + +```python +def make_mcp_handler(connection_id: UUID, tool_name: str): + async def handler(ctx: ActionContext, args: dict) -> Any: + # Credential resolution happens here, per call + client = await ctx.resolve_mcp_client(connection_id) + response = await client.call_tool(name=tool_name, arguments=args) + return response.content + return handler +``` + +`ctx.resolve_mcp_client(connection_id)`: +1. Loads the `mcp_connections` row +2. Decrypts the access token +3. Refreshes the token if it's expired (using the refresh token) +4. Constructs an `MCPClient` with the token set as a default authorization + header + +The HTTP library carries the auth header on every subsequent call the +client makes — the handler doesn't think about it after construction. + +For native capabilities calling external APIs directly, +`ctx.resolve_http_client(provider)` returns an authenticated `httpx` +client. For LLM operations, `ctx.resolve_llm(provider)` returns a +configured LLM client. **Three resolution methods, one pattern: the +context returns a client already authenticated.** + +Three properties this gives us: + +- **Credentials never appear in the automation definition.** The JSON + contains capability references and connection IDs, never tokens. +- **Credentials never appear in the LLM's context.** Even during + `agent_task`, the LLM sees tool descriptions only; the host holds + credentials and uses them when executing the tools the LLM requests. +- **Credentials are loaded per-call, not pre-loaded.** The credential + exists in memory only during the moment a handler is making a call. No + long-lived secrets in worker memory. + +--- + +## 4. Action contract (Layer 2) + +An `Action` is what a user references in a plan step. Most actions are +thin wrappers around one capability (e.g., `slack_post` wraps +`slack.post_message`). Some compose: `agent_task` is one action whose +handler invokes the LangGraph runtime, which in turn can call many +capabilities. + +```python +@dataclass +class ActionDefinition: + type: str # "agent_task", "slack_post" + name: str # for the UI + description: str # for the NL generator + config_schema: dict # JSON Schema for action.config + output_contract: dict | DynamicOutput # what it produces + uses_capabilities: list[str] # IDs from the registry + produces_artifacts: list[ArtifactSpec] # see §8 + handler: AsyncHandler +``` + +### Tight vs loose actions + +Two patterns coexist by design: + +- **Tight actions** (`slack_post`, `linear_create_issue`, `send_email`): + config_schema is fully specified, output_contract is fixed, handler is a + thin wrapper. ~20 LOC each. Used when the user knows exactly what they + want done — no LLM tokens spent on trivial work. + +- **Loose actions** (`agent_task`): config_schema accepts a `prompt` and a + `tools` allowlist; output_contract is *dynamic* — the user declares the + output shape they want via `output_schema` in the step config; the + handler asks the LLM to return that shape and validates. Used when + judgment is needed. + +The agent's tool list is **the same capabilities** that tight actions call +directly. One registry, two invocation modes. Adding a new MCP server gives +both modes access to its tools automatically. + +### How names in the definition become function calls + +The definition contains strings like `"action": "slack_post"`. The string is +just a name — it does not point to a function. At runtime, the executor +performs a **name-based lookup** against the action registry: + +```python +# step.action is a string from the JSON definition, e.g. "slack_post" +action_def = _ACTION_REGISTRY[step.action] # dict lookup +handler = action_def.handler # Python callable +result = await handler(ctx, resolved_config) # invocation +``` + +The registry is a Python dict (or a thin wrapper around one) populated at +process startup. Each entry in `automations/actions/*.py` calls a +`register_action(...)` function at module import time, putting its +`ActionDefinition` (including the handler function reference) into the +registry. + +The same pattern applies to capabilities. The definition references +capabilities by ID (`"slack.post_message"`); the capability registry maps +the ID to a `Capability` object holding the handler. Definitions never +reference Python code directly — they reference names that the registry +resolves to code. + +This separation is what makes the contract portable. The definition is +pure data. The registry is the engine's runtime vocabulary. They meet at +name-based lookup; nothing else crosses the boundary. + +### The full expressive spectrum + +The contract supports a continuous spectrum from purely deterministic to +fully agentic. Six practical shapes worth recognizing: + +| Shape | Example | Cost / latency profile | +| --- | --- | --- | +| **1. Direct call** | `slack_post` with literal channel and template | No LLM. ~200ms. Fractions of a cent. | +| **2. Direct call with computed inputs** | `linear_create_issue` using `{{summary.title}}` from a prior step | No LLM for this step. Cheap. | +| **3. Single-domain agent task** | `agent_task` with `tools: ["slack.*"]` only | One LLM, bounded toolset. | +| **4. Multi-domain agent task, narrow** | `agent_task` with `tools: ["github.list_pull_requests", "linear.create_issue"]` | One LLM, named capabilities. | +| **5. Multi-domain agent task, broad** | `agent_task` with `tools: ["slack.*", "github.*", "linear.*"]` | One LLM, large toolset, most agentic. | +| **6. Composed plan** | `agent_task` (narrow) for thinking → `slack_post` + `linear_create_issue` for acting | Best cost-to-power ratio. | + +Shape 6 is the underrated one and the cost-and-speed answer. The agent +reasons once (Shape 3 or 4) and its structured output drives several +deterministic actions. This is roughly 5–10x cheaper and 3–4x faster than +forcing the agent to do everything (Shape 5) and produces the same outcome. + +**The NL generator's job is to propose Shape 6-style plans by default.** +The Review LLM flags proposals that use `agent_task` for steps a +deterministic action could handle. This is the discipline that keeps +automations cheap at scale. + +The user navigates the spectrum by intent (describing what they want), not +by mechanism — the shape selection is the engine's responsibility, not the +user's. + +--- + +## 5. Automation definition (Layer 3) + +This is the JSON the user writes (or the NL generator produces). Stored in +`automations.definition` as JSONB. + +### Top-level shape + +```jsonc +{ + "schema_version": "1.0", + "name": "Daily competitor digest", + "goal": "Summarize new competitor content and post to Slack", + + "inputs": { + "schema": { + "type": "object", + "required": ["since"], + "properties": { + "since": { "type": "string", "format": "date-time", + "default": "$last_fired_at" }, + "tags": { "type": "array", "items": { "type": "string" }, + "default": ["competitor"] } + } + } + }, + + "triggers": [ + { + "type": "schedule", + "config": { "cron": "0 9 * * 1-5", "timezone": "Africa/Kigali" } + } + ], + + "plan": [ + { + "step_id": "research", + "action": "agent_task", + "config": { + "prompt": "Find documents tagged {{inputs.tags}} indexed since {{inputs.since}}. Return JSON with bullets and source_doc_ids.", + "tools": ["search_space.query", "search_space.fetch_document"], + "model": "anthropic/claude-sonnet-4-7", + "output_schema": { + "type": "object", + "required": ["bullets", "source_doc_ids"], + "properties": { + "bullets": { "type": "array", "items": { "type": "string" } }, + "source_doc_ids": { "type": "array", "items": { "type": "string" } } + } + } + }, + "output_as": "summary" + }, + { + "step_id": "deliver", + "action": "slack_post", + "config": { + "channel_id": "C0123", + "message_template": "*Competitor digest*\n\n{% for b in summary.bullets %}• {{b}}\n{% endfor %}" + } + } + ], + + "execution": { + "timeout_seconds": 600, + "max_retries": 2, + "retry_backoff": "exponential", + "concurrency": "drop_if_running", + "budget_cap_usd": 1.50, + "on_failure": [ /* steps to run if main plan fails after retries */ ] + }, + + "metadata": { "tags": ["digest"], "created_from_nl": true } +} +``` + +### Plan steps + +```jsonc +{ + "step_id": "...", // unique within plan + "action": "...", // references an ActionDefinition.type + "when": "{{ ... }}", // optional Jinja expr → bool; false = skip + "config": { ... }, // validated against action's config_schema + "output_as": "...", // binds output to this name for later steps + "max_retries": 0, // optional, overrides automation default + "timeout_seconds": 1200 // optional, overrides automation default +} +``` + +Steps run **sequentially**. No parallelism, no DAGs, no loops. If a user +needs branching, they use `when:` on multiple steps. If they need +parallelism or iteration, they use `agent_task` and let the agent reason +about it, or they compose automations through events (§7.5). + +--- + +## 6. Trigger contract (Layer 4) + +Three trigger types. That's the entire taxonomy. + +### `schedule` + +```python +TriggerDefinition( + type="schedule", + config_schema={ + "type": "object", + "required": ["cron", "timezone"], + "properties": { + "cron": { "type": "string" }, + "timezone": { "type": "string", "format": "iana-timezone" } + } + }, + payload_schema={ + "type": "object", + "properties": { + "fired_at": { "type": "string", "format": "date-time" }, + "scheduled_for": { "type": "string", "format": "date-time" }, + "last_fired_at": { "type": "string", "format": "date-time" } + } + } +) +``` + +Implementation: extends `app/utils/periodic_scheduler.py`, which already +reads connector sync schedules. Adds a second source — `automation_triggers +WHERE type='schedule'`. Same Celery Beat checker, two source tables. + +Minimum interval: 1 minute (the existing checker's resolution). The form +editor warns when users set intervals under 15 minutes that they probably +want an event trigger instead. + +### `webhook` + +```python +TriggerDefinition( + type="webhook", + config_schema={ + "type": "object", + "properties": { + "input_mapping": { + "type": "object", + "additionalProperties": { "type": "string" } + # values are JSONPath expressions + } + } + }, + # payload is whatever the POST body is; user-defined shape via mapping +) +``` + +Endpoint: `POST /api/v1/automations/{id}/fire`. Bearer token shown once, +hashed at rest, rotatable, revocable. Returns `202 Accepted` with the +created run's URL. Caller polls for status; we do not push callbacks in +v1 (a `callback_webhook` action can be added later). + +Idempotency: honors `Idempotency-Key` header or `idempotency_key` in body. +Dedups against runs in the last 24 hours. + +### `event` + +```python +TriggerDefinition( + type="event", + config_schema={ + "type": "object", + "required": ["event_type"], + "properties": { + "event_type": { "type": "string" }, # e.g. "drive.file_added" + # or "surfsense.podcast.generated" + "filters": { "$ref": "#/definitions/filter_expression" } + } + } + # payload shape is documented per event_type in a separate registry +) +``` + +**Events absorb both connector events and internal SurfSense events.** A +file added to Drive and a podcast finishing in SurfSense are both events +in the same `domain_events` table, both subscribable by automations, both +matched by the same dispatcher code. The engine doesn't distinguish. + +### Filter grammar + +Filters are JSON-structured operators, not expressions. This is the one +place we deliberately don't use Jinja, because filters run on a hot path +(every event matched against every subscribing trigger) and structured +filters can be indexed and short-circuited. + +Vocabulary: +- Equality: `equals`, `not_equals` +- String: `starts_with`, `ends_with`, `contains`, `regex` +- Numeric: `gt`, `gte`, `lt`, `lte` +- Set: `in`, `not_in` +- Existence: `exists` +- Composition: `$and`, `$or`, `$not` + +Inspired by AWS EventBridge and MongoDB query syntax. The filter grammar +itself is published as a JSON Schema, so users get inline error messages. + +--- + +## 7. Runtime components + +Each component is distinct, replaceable, and has one job. + +### 7.1 Dispatcher + +What it does: matches firing triggers to automations, creates `AutomationRun` +rows, enqueues executor tasks. + +For schedule triggers: Celery Beat polls the trigger table, computes due +ones, fires. + +For webhook triggers: the FastAPI handler is the dispatcher entry point. +Validates token, runs input_mapping, creates run. + +For event triggers: subscribes to the `domain_events` table. For each new +event, evaluates all matching triggers' filters, fires the matches. + +Common path (after a trigger has fired): +1. Resolve `inputs` from trigger payload and defaults +2. Validate resolved inputs against the automation's input schema +3. **Cost estimate** — sum capabilities' `cost_estimate(args)` for the plan; + refuse if exceeds `budget_cap_usd` +4. **Idempotency check** — dedup against existing pending/running runs +5. **Snapshot the resolved definition** into the run row (immutable history) +6. Enqueue executor task on the appropriate Celery queue (per + `expected_duration_seconds`) + +### 7.2 Executor + +What it is: **a Celery task wrapping a single function that walks a plan +step by step.** Not an agent, not a workflow engine, not a scheduler. A +loop with bookkeeping. Maybe 200 lines. + +```python +async def execute_run(run_id: int) -> None: + run = load_run(run_id); run.status = "running"; save(run) + context = build_run_context(run) + step_outputs = {} + + for step in run.plan: + if step.when and not evaluate_predicate(step.when, context | step_outputs): + record_step_skipped(run, step); continue + + resolved_config = render_config(step.config, context | step_outputs) + action = action_registry.get(step.action) + validate(resolved_config, action.config_schema) + + try: + result = await with_retries( + action.handler, + ctx=build_action_context(run, action), + args=resolved_config, + policy=step.retry_policy or run.execution.retry_policy, + ) + validate(result, step.output_schema) + if step.output_as: + step_outputs[step.output_as] = result + record_step_succeeded(run, step, result) + except Exception as e: + record_step_failed(run, step, e) + await run_on_failure(run, e) + return + + run.status = "succeeded"; save(run) + publish_event("automation.run.succeeded", run) # see §7.5 +``` + +Intelligence lives **inside handlers**, not in the executor. The most +intelligent handler is `agent_task`, which spins up a LangGraph Deep Agent +for one step and returns when the agent finishes. The executor sees a +validated dict come back; it doesn't know that step was "smart." + +### 7.3 Action handlers + +One handler per `ActionDefinition.type`. Receives `(ctx, args)`, returns +a dict matching `output_contract` (or matching the user-declared +`output_schema` for dynamic-output actions like `agent_task`). + +Handlers handle their own credential resolution via `ctx.resolve_credentials`. +They do not know about retries, timeouts, or budget caps — those are the +executor's concern. + +### 7.4 Template engine + +#### Why it exists + +Most fields in an automation definition contain literal strings the user +authored once — but the actual rendered value has to change per run, because +it includes data from the trigger payload or from prior step outputs. The +template engine is what turns `"Daily digest for {{run.started_at}}"` into +`"Daily digest for 2026-05-26"` at run time. + +Three fields use it: +- `*_template` strings in tight action configs (Slack messages, email bodies, + Linear titles, etc.) +- `prompt` in `agent_task` configs (so the agent sees resolved values, not + `{{...}}` placeholders) +- `when:` step predicates (which need to evaluate to a boolean) + +#### Public interface + +Single module, ~80 lines. Three public functions — everything else in the +engine routes through these: + +```python +def render_template(template: str, context: dict) -> str: ... +def evaluate_predicate(expression: str, context: dict) -> bool: ... +def build_run_context(run, step_outputs) -> dict: ... +``` + +Backed by Jinja2's `SandboxedEnvironment`. The whole module is the seam: if +the template language is ever swapped, only this file changes. + +#### Security architecture: allowlist by default + +`SandboxedEnvironment` starts empty. A freshly-created instance gives a +template access to: +- Variables in the context dict we pass in (`run`, `inputs`, prior step + outputs) +- Public (non-underscore) attributes of those variables +- Jinja's built-in control flow (`{% if %}`, `{% for %}`, `{% set %}`) + +Nothing else. No Python builtins, no modules, no I/O, no network, no +filesystem. Everything beyond the above must be **explicitly registered.** +This is the structurally important property: anything we didn't add is +inaccessible. The risk surface equals the size of what we registered. + +The three sandbox rules that enforce this: +1. **Attribute access is filtered** — names starting with underscore are + rejected. This blocks the entire family of `{{x.__class__.__mro__...}}` + Python escape paths in one rule. +2. **Globals are allowlist-only** — `open`, `eval`, `exec`, `__import__`, + `getattr`, every module name, are all absent unless we register them. + We register zero globals. +3. **Unsafe callables are blocked** — `str.format` and `str.format_map` + specifically (due to CVE-2016-10745), plus anything marked + `unsafe_callable`. + +#### What we register, exactly + +- **Filters: a curated 15**, no more. `join`, `length`, `default`, `upper`, + `lower`, `truncate`, `tojson`, `date`, `replace`, `trim`, `slugify`, + `first`, `last`, `sort`, `reverse`. Each one is audited for what it does + with its input; none of them takes a callable, runs `eval`, or reaches + into Python objects beyond simple data transformation. +- **Globals: none.** +- **Tests: only the safe built-ins** (`defined`, `none`, `number`, `string`, + `mapping`, `sequence`, `boolean`). + +Adding a new filter requires a deliberate code change and review: does this +filter do anything dangerous with its input? If yes, don't add it. The list +only grows by audited additions. + +#### Runtime limits (defense in depth) + +The sandbox handles the attack surface inside the template language. Three +additional limits handle resource exhaustion that the language permits but +the runtime shouldn't tolerate: + +- **Template source length capped at 8 KB.** Checked before parsing. +- **Render time capped at 100 ms per render.** Implemented via a watchdog + thread; renders that exceed are killed and the step fails. Catches + `{% for i in range(10**9) %}` and nested loop bombs. +- **Output size capped at 1 MB.** A small template can produce a multi-GB + string via `{{ 'A' * 10**8 }}`-style multiplication; this catches it. + +Plus `StrictUndefined`: any reference to a missing variable raises +immediately rather than silently rendering empty, so misconfigurations +fail fast. + +#### Threat model and residual risk + +The trust model from day one is: + +- Templates are generated by an LLM from a user's natural-language input + (see §10), or written/edited by humans in the editable form +- A second LLM reviews the proposal and produces a plain-language summary + plus flagged anomalies for the user +- The user reviews and approves before the automation runs +- The Generator LLM's input is scoped (user prompt + schema + registry + only — no arbitrary document content), minimizing prompt-injection paths + +The sandbox + runtime limits + curated filter list protect against the +malformed-template attack. Human review protects against the +semantically-malicious-but-syntactically-valid attack. These are +complementary layers, not redundant. + +Known residual risks, each genuinely small: + +- **Future Jinja CVEs.** Historical sandbox bypasses have existed and + been patched. This is a generic third-party-dependency risk, comparable + to bugs in any other library we rely on. Mitigation: subscribe to + security advisories, ship updates within a week of disclosure. +- **Side channels via prompts to LLMs.** A template that renders into a + prompt can attempt prompt injection of the agent at run time. This is + not a sandbox concern but a separate concern in `agent_task`'s design. +- **Operator deployments with long-lived secrets in worker env vars.** + Mitigation: credentials fetched per-handler-per-call via + `ActionContext.resolve_credentials`, never pre-loaded into worker + env vars accessible to templates. + +The sandbox-with-allowlist architecture means **the attack surface +equals the set of things we registered.** With zero globals registered +and 15 audited filters, the surface is small, bounded, and reviewable. +This is the structural property that makes the architecture sound, and +it doesn't depend on hypothetical assumptions about who authors templates. + +#### Pre-Phase-5 gate + +One trust-model change is documented in the roadmap: **Phase 5 introduces +template sharing across SearchSpaces** (automation templates as +exportable, importable artifacts). At that point, the *approver* of a +template (the original author) is no longer the *runner* (the importer). +The "human reviews before save" mitigation breaks down because the +reviewer doesn't bear the risk. + +Before Phase 5 ships, this needs an explicit re-approval flow: importing +a template triggers a fresh review pass by the importing user, with the +flagged-anomalies output prominently displayed, and the import cannot +complete without explicit per-template approval. + +This is a UX/flow decision, not a template-language migration. Jinja +itself stays; what changes is the approval workflow at the import boundary. + +#### The `run.*` namespace exposed in every template + +``` +run.id, run.started_at, run.automation_id, run.automation_name, +run.automation_version, run.trigger_type, run.trigger_id, +run.search_space_id, run.creator_id, run.attempt, +run.failed_step_id, run.error.* (only in on_failure context) +``` + +#### Default value rendering + +Non-string template values render as JSON by default (via the `finalize` +hook): lists become `["a", "b"]`, dicts become `{"k": "v"}`, datetimes +become ISO 8601. The `| join`, `| length`, `| tojson` filters give explicit +control. Strings render as themselves with no quoting. `None` renders as +empty string in templates, as `null` in JSON contexts. + +### 7.5 Event bus + +`domain_events` table, polled by Celery Beat alongside the existing +scheduler. Both connector events and internal SurfSense events publish to +it. Both are consumed by the dispatcher's event-trigger subscriber. + +**Automations themselves publish events.** Successful and failed runs emit +`automation.run.succeeded` / `automation.run.failed` events with the run +metadata. This makes automations composable through events — chain them by +subscribing one automation's event trigger to another's run event. No new +mechanism; the trigger filter and event publishing already exist. + +Upgrade path documented: when throughput or latency demands it, replace +PostgreSQL polling with Redis Streams. The `events.publish()` and +`events.subscribe()` interfaces stay the same. Nothing else changes. + +--- + +## 8. Cross-cutting concerns + +### Concurrency policy + +Per-automation `concurrency` field controls what happens when a new fire +occurs while a previous run is still running: + +- `drop_if_running` — silently skip the new fire +- `queue` — execute serially, in arrival order +- `allow_parallel` — start a new run independently + +The dispatcher enforces this before enqueueing. + +### Retry policy + +Three fields, per-automation defaults with optional per-step overrides: +- `max_retries`: integer, 0–10 +- `retry_backoff`: `none` | `linear` | `exponential` +- `timeout_seconds`: integer + +Retries on: +- Capability handler exceptions +- Output schema validation failures (for dynamic-output actions, the + validation error is fed back to the LLM in the retry) + +Not retries: +- `when:` evaluation failures (these are user errors, surface immediately) +- Input validation failures (caught at dispatch, never reach the executor) + +### Budget enforcement + +`budget_cap_usd` is per-run. The dispatcher refuses to enqueue if estimated +cost exceeds it. The executor kills the run if accumulated cost crosses it +mid-flight (the LLM ops handler reports tokens consumed back to the +executor between calls). + +### On-failure handlers + +`execution.on_failure` is a list of steps that run after the main plan has +failed and all retries are exhausted. Same step shape as the main plan. +Cannot have their own `on_failure`. See `run.error.*` in the run context. + +### Artifacts + +Actions that produce artifacts declare `produces_artifacts: list[ArtifactSpec]`: + +```python +@dataclass +class ArtifactSpec: + kind: str # "audio", "document", "image", "data" + retention: str # "transient" | "default" | "permanent" + visibility: str # "private" | "search_space" | "shared" +``` + +The engine handles storage (writes to SurfSense's existing object storage), +URL generation (signed, scoped to the run's permissions), and cleanup (a +nightly Celery Beat task deletes expired artifacts). + +### Duration classes and queue routing + +Capabilities declare `expected_duration_seconds`. The dispatcher routes +runs to Celery queues based on the longest-duration step: +- < 10s → `automations_fast` +- 10s – 5min → `automations_medium` +- 5min – 1hr → `automations_long` + +Operators scale each queue's worker pool independently. A future "very +long" queue is a config change, not a contract change. + +--- + +## 9. Data model + +Six tables. All scoped by `search_space_id` for RBAC. + +The first four (`automations`, `automation_triggers`, `automation_runs`, +`domain_events`) are the engine's own state. The last two +(`mcp_connections`, `mcp_tools`) hold the durable knowledge that backs +MCP-derived capabilities — see §3 for the lifecycle rationale. + +### `automations` + +| field | type | notes | +| ----------------- | ----------------------------------- | -------------------------------------------------------------------------- | +| `id` | int PK | | +| `search_space_id` | FK → `search_spaces.id` | | +| `created_by` | FK → `users.id` | runs execute as this identity | +| `name` | str | | +| `description` | str | | +| `status` | enum | `active`, `paused`, `archived` | +| `definition` | jsonb | the editable structured spec | +| `version` | int | bumped on every edit | +| `created_at` / `updated_at` | timestamps | | + +### `automation_triggers` + +| field | type | notes | +| --------------- | ----------------------------------------------------------------------------- | ------------------------------------------- | +| `id` | int PK | | +| `automation_id` | FK | | +| `type` | enum: `schedule`, `webhook`, `event` | | +| `config` | jsonb | validated against trigger's `config_schema` | +| `enabled` | bool | | +| `secret_hash` | str / null | for webhook bearer tokens | +| `last_fired_at` | timestamp | | + +### `automation_runs` + +| field | type | notes | +| ----------------- | ---------------------------------------------------------------------------- | -------------------------------------------------- | +| `id` | int PK | | +| `automation_id` | FK | | +| `trigger_id` | FK / null | null = manual via UI | +| `status` | enum | `pending`, `running`, `succeeded`, `failed`, `cancelled`, `timed_out` | +| `definition_snapshot` | jsonb | the definition as it was when this run fired | +| `trigger_payload` | jsonb | | +| `resolved_inputs` | jsonb | | +| `step_results` | jsonb | array of per-step results with timing | +| `output` | jsonb / null | | +| `artifacts` | jsonb | references to created artifacts | +| `error` | jsonb / null | | +| `cost_usd` | decimal | accumulated cost | +| `started_at` / `finished_at` | timestamps | | +| `agent_session_id`| str / null | link to LangGraph trace if agent_task was used | + +### `domain_events` + +| field | type | notes | +| ----------------- | ----------- | -------------------------------------------------- | +| `id` | UUID PK | | +| `search_space_id` | FK | scoping | +| `event_type` | varchar | e.g. `drive.file_added`, `automation.run.succeeded` | +| `source_id` | varchar | which connector/automation/etc. produced it | +| `payload` | jsonb | matches the event type's documented schema | +| `created_at` | timestamp | | +| `consumed_by` | jsonb | array of consumer_ids, for tracking + replay | +| `expires_at` | timestamp | auto-cleanup after 7 days | + +### `mcp_connections` + +Persistent record of MCP server connections per SearchSpace. + +| field | type | notes | +| ------------------- | ----------- | -------------------------------------------------- | +| `id` | UUID PK | | +| `search_space_id` | FK | scoping | +| `server_url` | text | the MCP server's endpoint | +| `transport` | text | `"http"`, `"stdio"`, etc. | +| `name` | text | human-readable label (e.g., "Slack — Acme") | +| `access_token` | bytea | encrypted at rest | +| `refresh_token` | bytea | encrypted at rest | +| `expires_at` | timestamp | for OAuth tokens | +| `last_harvested_at` | timestamp | when tool list was last refreshed | +| `created_at` | timestamp | | +| `created_by` | FK → users | | + +### `mcp_tools` + +The tool list each connected MCP server exposes. Acts as the durable +source for MCP capabilities — definitions reference `mcp_tools` rows by +qualified name, and worker processes lazily build handler closures from +this state. + +| field | type | notes | +| --------------- | ----------- | ------------------------------------------------ | +| `id` | UUID PK | | +| `connection_id` | FK → `mcp_connections.id` ON DELETE CASCADE | | +| `name` | text | the tool name reported by the MCP server | +| `description` | text | description for the NL generator and form editor | +| `input_schema` | jsonb | JSON Schema for tool arguments | +| `output_schema` | jsonb | JSON Schema for tool results | +| `side_effects` | text[] | inferred from MCP hints + naming + admin override | +| UNIQUE | | (connection_id, name) | + +NL drafts are **not** a core table. They live in a generic short-TTL store +(Redis or a transient table) when the NL flow is built in Phase 3. + +--- + +## 10. NL authoring flow + +**This is how the system is intended to be used from day one, not just a +Phase 3 addition.** The product surface is: user describes intent in natural +language, LLM produces a structured proposal, user reviews and edits in an +auto-generated form, then saves. Hand-authoring JSON directly is supported +but is not the primary path. + +This shapes the trust model. Templates are LLM-generated from day one, not +hand-written by power users. The mitigation is human-in-the-loop review, +not "trusted authors only." + +### Pass 1: Proposal generation + +User provides natural-language input. The Generator LLM is given: +- The full schema set (input schema for definition, registry of action + types with their config_schemas, registry of trigger types, available + capabilities for this SearchSpace, list of allowed Jinja filters) +- A tool to list available connectors, channels, and other SearchSpace + resources, so it doesn't invent names that don't exist +- A few-shot set of examples + +**Scoped input.** The Generator does *not* receive arbitrary SearchSpace +document content. Its context is the user's prompt plus the schema and +registry information. This minimizes the prompt-injection surface — there's +no document text in the context for an attacker to seed instructions into. + +If a user wants document-aware generation later ("create an automation +that processes documents like this one"), that's a deliberate feature +extension with its own prompt-injection mitigations, not the default flow. + +Output: a structured proposal matching the automation definition schema. + +### Pass 2: Deterministic validation + +Server-side, before the proposal reaches the user: +- Validate against JSON Schema (shape correctness) +- Verify every capability referenced exists in the registry (resource existence) +- Verify every connector/channel/resource referenced exists in this SearchSpace +- Validate every template against the sandbox's allowlist (no underscore + attributes, no unregistered filter names, length under cap) + +Failures here are deterministic errors, not warnings. A proposal that +references a non-existent capability or includes a template using +`{{x.__class__}}` is rejected before the user sees it; the Generator is +re-prompted with the validation error and asked to fix the proposal. + +### Pass 2.5: Review pass + +A second LLM call — the **Review LLM** — examines the validated proposal and +produces two outputs for the user: + +1. **A plain-language summary** of what the automation will do, in business + terms. "This automation will run every weekday at 9am. It reads documents + in this SearchSpace tagged 'competitor' that were indexed since the last + run, asks an agent to summarize them as 5 bullets, and posts the summary + to your #engineering-standup Slack channel. Estimated cost: $0.40 per + run." + +2. **A "things worth checking" list** flagging anything unusual: + - Templates with unusual attribute paths or filter usage + - Prompts containing instructions that look more like commands than + descriptions ("ignore previous instructions" style) + - Action sequences that touch external systems without obvious benefit + to the user + - Cost estimates that seem high relative to the goal + - References to capabilities the user hasn't used before + - Schedules tighter than 15 minutes (likely should be event triggers) + +The Review LLM is a **UX layer** that makes review actually useful. It is +**not a security boundary.** The deterministic controls (sandbox, runtime +limits, schema validator) are the security boundaries. The Review LLM +helps users catch their own intent mismatches and surfaces anomalies for +attention, but the sandbox would block dangerous templates even if the +Review LLM missed them. + +This separation is important: two probabilistic controls compounding can +create a false sense of security. The Review LLM is explicitly framed in +the architecture as helper, not gatekeeper. + +### Pass 3: Editable review + +The user lands on a form pre-filled with the proposal. The page shows: +- The plain-language summary from the Review pass +- The flagged items, prominently displayed near the relevant fields +- The full editable form, auto-generated from the JSON Schemas +- Cost estimate and impact summary (which external systems get touched) + +**Every field is editable.** Clarifications appear as required fields. +Templates are shown in code-styled fields with syntax highlighting and the +filter palette visible. The user can edit any field; saving re-runs Pass 2 +(deterministic validation) before persisting. + +Hitting **Save** promotes the proposal to an `automation` row. + +### Editing existing automations + +NL editing of an existing automation is a patch operation: the Generator +LLM receives the current definition plus the NL instruction and produces a +modified proposal. The same Pass 2 (validation) and Pass 2.5 (review) run +against the modified version, and the user reviews the diff before saving. +Existing run history is unaffected — only future runs use the new version. + +### Why human-in-the-loop is non-negotiable + +The Generator LLM, the Review LLM, and the sandbox are three layers of +defense against malformed or malicious proposals. The human approval step +is the fourth and most important layer. It exists because: + +- LLMs can be prompt-injected; humans can spot text that asks them to + ignore instructions +- LLMs can produce confident-but-wrong proposals; humans can catch + semantic mismatches between intent and output +- The cost of a bad automation running unattended is high; the cost of a + user clicking "approve" after reading is low + +The architecture must never offer "auto-approve" or "skip review" options +for LLM-generated proposals. Save requires human action on the proposal, +always. + +--- + +## 11. Repository layout + +``` +surfsense_backend/app/ +├── automations/ # NEW: the engine +│ ├── __init__.py +│ ├── models.py # SQLAlchemy models for 6 tables +│ ├── schemas.py # Pydantic schemas (definition envelope, etc.) +│ ├── routes.py # FastAPI router (/api/v1/automations) +│ ├── service.py # CRUD + business logic +│ ├── dispatcher.py # trigger matching, cost check, run creation +│ ├── executor.py # the Celery task that runs a plan +│ ├── templating.py # Jinja sandbox + filters +│ ├── events.py # publish/subscribe for domain_events +│ ├── filters.py # JSON filter grammar evaluator +│ ├── actions/ +│ │ ├── registry.py +│ │ ├── agent_task.py +│ │ ├── transform_data.py +│ │ ├── slack_post.py +│ │ ├── send_email.py +│ │ ├── notification.py +│ │ └── (more in Phase 5: podcast_generation, report_generation, ...) +│ ├── triggers/ +│ │ ├── registry.py +│ │ ├── schedule.py # Celery Beat hookup +│ │ ├── webhook.py # /fire endpoint +│ │ └── event.py # subscribes to domain_events +│ ├── capabilities/ +│ │ ├── registry.py +│ │ ├── native.py # native capability registrations +│ │ ├── mcp_harvester.py # registers MCP tools as capabilities (Phase 4) +│ │ └── (LLM ops registered alongside) +│ └── nl/ # Phase 1 — primary user path +│ ├── generator.py # Generator LLM +│ ├── reviewer.py # Review LLM (summary + flagged items) +│ ├── validator.py # deterministic schema + resource checks +│ └── prompts.py # system prompts for both LLMs +│ +├── utils/ +│ └── periodic_scheduler.py # EXTENDED to scan automation_triggers +│ +└── alembic/versions/ + └── NN_add_automation_tables.py + +surfsense_web/app/(routes)/ +└── automations/ # NEW: UI + ├── page.tsx # list + ├── new/page.tsx # NL input + draft preview (Phase 1) + ├── [id]/page.tsx # editor (auto-generated forms) + └── [id]/runs/page.tsx # run history, streamed via Electric SQL +``` + +--- + +## 12. Phased delivery + +Each phase delivers something usable. Each de-risks the next. **NL authoring +is the primary user path from Phase 1** — what evolves across phases is +which actions and triggers are available, not whether users can describe +automations in natural language. + +### Phase 1 — Engine MVP with NL authoring +- 4 tables + Alembic migration +- Capability registry with native capabilities (`search_space.query`, + `search_space.fetch_document`, `agent.run`) +- `agent_task` action only +- `schedule` trigger + manual "Run now" endpoint +- Executor with retries, timeouts, budget caps +- Template engine (Jinja sandbox + 15 filters + 4 runtime limits) +- **NL authoring flow**: Generator LLM, deterministic validator, + Review LLM, editable form +- Run history UI with Electric SQL streaming + +**After Phase 1**: a user can describe an automation in natural language, +review the proposal (with summary + flagged anomalies), edit any field, +save, and watch it run on a schedule. The Claude Routines value +proposition, on SurfSense's data, with NL-first authoring. + +### Phase 2 — Webhooks and delivery +- `webhook` trigger with per-automation bearer tokens +- Tight actions: `slack_post`, `send_email`, `notification` +- `transform_data` action +- `on_failure` hooks +- Step-level retry/timeout overrides +- Concurrency policy enforcement + +**After Phase 2**: external systems can drive automations, results go +somewhere humans see, complex pipelines have proper error handling. + +### Phase 3 — NL authoring polish +- NL patch flow for editing existing automations (diff-based) +- Conversational refinement during proposal review ("change the schedule + to weekdays only," "add a Slack notification on failure") +- Improved Review LLM coverage (more anomaly patterns, cost-relative-to- + goal heuristics) +- Saved prompt templates and starter examples + +**After Phase 3**: NL authoring is the polished primary surface; edit +flows are conversational rather than form-only. + +### Phase 4 — Event triggers +- `domain_events` table and `events.py` module +- Indexing pipeline publishes `connector.*` events (smallest change — just + add publish calls to the existing flow) +- Automations publish `automation.run.*` events on completion +- `event` trigger with filter grammar +- MCP capability harvester (so MCP-backed events and tools both work) + +**After Phase 4**: "do X when Y happens" automations work, including +automation-chaining through events. + +### Phase 5 — Wrapping existing features and sharing +- Wrap existing SurfSense capabilities as actions: `podcast_generation`, + `report_generation`, `indexing_sweep` +- Artifact lifecycle implementation +- `expected_duration_seconds` based queue routing (split `automations_long` + from `automations_default`) +- **Automation templates** (shareable, exportable, importable) — with + the import re-approval flow that handles the approver-≠-runner trust + shift documented in §7.4's pre-Phase-5 gate +- Cross-automation composition examples in the docs + +**After Phase 5**: every existing SurfSense capability is automatable +without any per-feature code, and automations can be shared between +SearchSpaces and users. + +--- + +## 13. Decisions locked + +For reference — every decision made through the design process, in one +place. + +### Foundations +1. ✅ JSON Schema 2020-12 is the single schema language for everything +2. ✅ Definition is the program; infrastructure is the interpreter +3. ✅ List of steps (not single action) in the plan, with `output_as` chaining +4. ✅ One capability registry serving native + MCP + LLM operations through the same interface +5. ✅ Capability IDs do not leak handler kind (`slack.post_message`, not `mcp.slack.post_message`) +6. ✅ Name-based resolution: definitions reference actions and capabilities by string ID. The registry is the runtime's vocabulary; lookup is a dict access. No code references in definitions. +7. ✅ The expressive spectrum runs from pure direct calls to broad agent_task; the NL generator proposes the cheapest shape that meets intent (Shape 6 from §4 by default) + +### Trigger taxonomy +8. ✅ Three trigger types: `schedule`, `webhook`, `event` +9. ✅ Events absorb both connector events and internal SurfSense events +10. ✅ Filter grammar is JSON-structured operators (not Jinja) + +### Templating cluster +11. ✅ Jinja2 `SandboxedEnvironment` for templates and `when:` predicates — but with the explicit understanding that the sandbox is an allowlist-by-default architecture, not a denylist +12. ✅ Zero globals registered. Curated 15 filters only, each audited for safe behavior with hostile input. List grows only by reviewed addition +13. ✅ Four runtime mitigations: `StrictUndefined`, 8 KB template source cap, 100 ms render time cap (watchdog-enforced), 1 MB output size cap +14. ✅ Non-string template values render as JSON by default +15. ✅ Fixed `run.*` namespace, documented +16. ⏸ **Pre-Phase-5 gate**: template sharing across SearchSpaces breaks the approver-equals-runner trust model. Mitigation is a re-approval flow at the import boundary (UX-level), not a template-language migration. Jinja itself stays. + +### Execution +17. ✅ Executor is a Celery task wrapping a sequential loop — not an agent +18. ✅ `when:` is optional per step; false = skipped (not failed) +19. ✅ No DAGs, no parallelism, no loops — composition via agent_task or events +20. ✅ `on_failure` part of execution policy from v1 +21. ✅ Step-level retry and timeout overrides +22. ✅ Budget cap enforced pre-enqueue and mid-flight + +### Components +23. ✅ Dispatcher / executor / handlers / registry — distinct, each replaceable +24. ✅ Side effects are a set, including `USER_VISIBLE` +25. ✅ `expected_duration_seconds` integer drives queue routing +26. ✅ `produces_artifacts` is a list of `ArtifactSpec`, not a bool +27. ✅ Output schemas recommended on `agent_task`; editor warns when missing + +### Event bus +28. ✅ `domain_events` table for v1, with upgrade path to Redis Streams +29. ✅ Automations publish run events for composability +30. ✅ Publish/subscribe behind interface — no direct table access elsewhere + +### Capability storage (two-tier persistence) +31. ✅ Native capabilities registered in-memory at startup from the codebase. Identical across all workers. +32. ✅ MCP capability metadata persisted in `mcp_connections` and `mcp_tools` tables. Survives restarts. +33. ✅ MCP handler closures built lazily per worker from database state. Worker-local cache, rebuilt on demand. +34. ✅ MCP server tool list re-harvested on a schedule (default: daily) and on user request. +35. ✅ MCP tools harvested into the capability registry at connection time +36. ✅ Side effects inferred from MCP hints + naming + admin overrides +37. ✅ MCP tools callable directly (no agent required) when caller knows args + +### Credentials +38. ✅ Credentials never appear in the automation definition — only connection IDs do +39. ✅ Credentials never appear in the LLM's context — the host holds them and uses them on the LLM's behalf +40. ✅ Credentials resolved per-call by `ActionContext`, not pre-loaded into worker environment +41. ✅ Tokens encrypted at rest in the database; refresh handled automatically by `ActionContext.resolve_*_client` + +### NL authoring +42. ✅ LLM-authored templates is the primary path from day one — not a Phase 3 addition. Hand-authoring JSON is supported but secondary +43. ✅ Generator LLM produces JSON; deterministic schema + resource validation runs before user sees the proposal +44. ✅ Review LLM produces plain-language summary + flagged anomalies for the user — UX layer, not a security boundary +45. ✅ Generator LLM's input is scoped (user prompt + schema + registry only); arbitrary document content is not fed in +46. ✅ Human approval is required before save — no auto-approval option, ever +47. ✅ Every field editable in the proposal; unresolved questions surface as clarifications +48. ✅ NL drafts are transient storage, not a core table + +### Data model +49. ✅ Six tables total — four for engine state, two for MCP persistence +50. ✅ Run rows snapshot the definition (immutable history) +51. ✅ All entities scoped by `search_space_id` for RBAC +52. ✅ Editing an automation bumps `version`; existing runs unaffected + +--- + +## 14. Open questions deferred to implementation + +None of these block design; they're decisions a developer will make in +context, with the principle from §1 as their guide. + +- Exact retry backoff formulas (multipliers, jitter, ceilings) +- Webhook signature verification standards (HMAC scheme, header naming) +- Whether to support inline JSON Schema `$ref` to external schemas, or + inline everything +- Specific CDN/storage backend choices for artifacts (probably + whatever SurfSense already uses for podcasts) +- Rate limits per SearchSpace and per user +- Audit log retention policy + +--- + +## 15. Why this is ready to build + +This document satisfies five tests: + +1. **The four worked examples** (digest, CI webhook, file-added-trigger, + weekly podcast) all express cleanly in the contract without special + cases. Each one was used to find gaps before the gaps reached code. + +2. **The audit pass identified six refinements**, all incorporated. No + pending audit items. + +3. **Every decision points back to the principle from §1.** When a future + feature request lands, "does it belong in the definition or in the + engine?" gives a clear answer. + +4. **The build is staged** so Phase 1 ships in weeks, not months, and + each subsequent phase delivers user value while de-risking the next. + +5. **Existing SurfSense infrastructure is reused**, not paralleled. Celery + Beat, PostgreSQL/JSONB, Electric SQL, SQLAlchemy/Alembic, the existing + `tools/registry.py` pattern, the existing Search Space scoping — all + continue to do what they already do. The automation engine is a new + directory, not a new system. + +The next document a developer needs is the Pydantic models and JSON +Schemas spelled out concretely. Those follow mechanically from this plan. + +--- + +*Sources consulted: Claude Code Routines documentation; NousResearch/hermes- +agent (cron and skills subsystems); n8n documentation on node types and +workflow data model; the SurfSense repository and DeepWiki architecture +notes (FastAPI + Celery Beat + Electric SQL + LangGraph Deep Agents + +Search Space RBAC); Model Context Protocol specification for capability +harvesting; AWS EventBridge for filter grammar; workflow-pattern +literature (van der Aalst et al.) for the trigger / action / concurrency +vocabulary.* From 16b661862930266418fb1bfda6b75e7fbf8f62c7 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:33:10 +0200 Subject: [PATCH 082/198] docs(automation): trim Capability dataclass to v1-minimum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reduce the §3 Capability dataclass from ten fields to five: id, description, input_schema, output_schema, handler. Removed fields (name, required_credentials, side_effects, expected_duration_seconds, cost_estimate) are reintroduced only when a concrete consumer feature demands them. The v1 invariant is that a Capability is a typed, named, callable unit and every consumer (executor, agent tool layer, future HTTP API) sees the same five-field shape. --- automation-design-plan.md | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/automation-design-plan.md b/automation-design-plan.md index 072f7ad99..ca7506446 100644 --- a/automation-design-plan.md +++ b/automation-design-plan.md @@ -76,18 +76,39 @@ is the atomic unit of "things automations can do." @dataclass class Capability: id: str # "slack.post_message" - name: str # "Post Slack message" - description: str # for the NL generator + description: str # for the NL generator + UI label input_schema: dict # JSON Schema output_schema: dict # JSON Schema - required_credentials: list[CredSpec] # what creds the handler needs - side_effects: set[SideEffect] # READ, WRITE, EXTERNAL_WRITE, - # COST_INCURRING, USER_VISIBLE - expected_duration_seconds: int # estimate or upper bound - cost_estimate: Callable[[dict], Decimal] # f(input) → estimated USD handler: AsyncHandler ``` +### v1-minimum: five fields, nothing else + +The Capability is **deliberately five fields in v1**. Every additional field +that earlier drafts considered (`name`, `required_credentials`, +`side_effects`, `expected_duration_seconds`, `cost_estimate`) has been +removed until a concrete consumer feature demands it. Authoring stays cheap +and the registry stays trivial to introspect: + +- `name` → folded into `description`. The UI can render a short label from + the first line of `description` or fall back to `id`. No separate field + needed in v1. +- `required_credentials` → returns when external-credential capabilities + ship (Phase 2). v1 capabilities run server-side with app config; nothing + to declare. +- `side_effects` → returns when RBAC inside automations or + `READ_ONLY`-only agent tool gating arrives. v1 capabilities are + hand-picked and all trusted code. +- `expected_duration_seconds` → returns when multi-queue routing ships. + Single Celery queue in v1. +- `cost_estimate` → never returns as a declared field; cost is measured + per run from a ledger, aggregated per Capability, and surfaced as a + historical average. Pre-flight checks are deferred. + +The runtime invariant: a Capability is **a typed, named, callable thing +the system can do.** Every consumer (executor, agent tool layer, future +HTTP API) sees the same five-field shape and uses it the same way. + ### Where capabilities live: a two-tier registry The capability registry has different storage requirements for different From b029c090bdfa7cbcadfab346e106a5599237ebbd Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:34:03 +0200 Subject: [PATCH 083/198] docs(automation): defer MCP integration to Phase 4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the two-tier registry, MCP database schema, harvester pseudocode, and the lazy per-worker closure cache from §3. v1 ships with a single in-memory native registry; the MCP design is reintroduced in Phase 4 along with the rest of the integration-tooling surface. The deferral is additive: the v1 registry interface is the same callable surface a Phase-4 MCP harvester will register into. No design rewrite between phases. --- automation-design-plan.md | 155 ++++++-------------------------------- 1 file changed, 23 insertions(+), 132 deletions(-) diff --git a/automation-design-plan.md b/automation-design-plan.md index ca7506446..07eca2049 100644 --- a/automation-design-plan.md +++ b/automation-design-plan.md @@ -109,143 +109,34 @@ The runtime invariant: a Capability is **a typed, named, callable thing the system can do.** Every consumer (executor, agent tool layer, future HTTP API) sees the same five-field shape and uses it the same way. -### Where capabilities live: a two-tier registry +### Where capabilities live (v1) -The capability registry has different storage requirements for different -kinds of capabilities. **Native capabilities and MCP capabilities have -different lifecycles**, so they're persisted differently: +In v1, the capability registry is a single in-memory dict, populated at +process startup from native registrations in +`automations/registries/capabilities/`. Identical across all workers. +No database persistence, no closures rebuilt per worker. -| Tier | What's there | Where it lives | Lifetime | -| --- | --- | --- | --- | -| **Native** | Capabilities defined in SurfSense's codebase (`search_space.query`, `agent.run`, etc.) | In-memory dict, populated at startup from `automations/capabilities/native.py` | Process lifetime, identical across all workers | -| **MCP (durable)** | The fact that this SearchSpace has connected to this MCP server, the tool list it exposes, credentials | PostgreSQL: `mcp_connections` and `mcp_tools` tables | Persistent across restarts and across time | -| **MCP (cached)** | Handler closures wrapping `(connection_id, tool_name)` | Per-worker in-memory cache, lazily built from the database on first reference | Process lifetime, rebuilt on demand | +### MCP integration — deferred to Phase 4 -The reason this matters: **a user connects an MCP server on Monday, writes -an automation on Tuesday, the automation runs on Friday.** Between Monday -and Friday, workers will restart many times. Any state that only lives in -worker memory is gone. The closures generated at connection time would -not survive. +The earlier two-tier registry (native + MCP-derived), the +`mcp_connections` / `mcp_tools` tables, the harvester, and the lazy +per-worker closure cache are **deferred to Phase 4** along with the +rest of the integration-tooling surface. They are removed from v1 +because: -So we split persistence by lifecycle: +- v1 has no external connector capabilities (no Slack, Notion, Drive, + etc.). The only capabilities that will ship are server-side helpers + (search-space query / fetch) plus the loose `agent_task` action. +- Without external connectors, the lifecycle mismatch that motivates + the two-tier design (connect Monday, run Friday, workers restarted + in between) doesn't arise. A startup-time dict is sufficient. +- Phase 4 reintroduces this design as-is — the registry interface in + v1 is the same callable surface a Phase-4 MCP harvester will register + into. The deferral is additive, not a different design. -- Native capability handlers live in the codebase. Always available, no - need for the database. -- MCP capability metadata lives in the database, so the knowledge "this - SearchSpace has these capabilities" survives any restart. -- The actual closures are built on demand from the database state. They - live in worker memory only until the worker dies, at which point they - get rebuilt by the next worker that needs them. - -### MCP database schema - -```sql -CREATE TABLE mcp_connections ( - id UUID PRIMARY KEY, - search_space_id INT REFERENCES search_spaces(id), - server_url TEXT, - transport TEXT, -- "http", "stdio", etc. - name TEXT, -- "Slack (Acme workspace)" - access_token BYTEA, -- encrypted at rest - refresh_token BYTEA, -- encrypted at rest - expires_at TIMESTAMPTZ, - last_harvested_at TIMESTAMPTZ, - created_at TIMESTAMPTZ, - created_by INT REFERENCES users(id) -); - -CREATE TABLE mcp_tools ( - id UUID PRIMARY KEY, - connection_id UUID REFERENCES mcp_connections(id) ON DELETE CASCADE, - name TEXT, -- "post_message" - description TEXT, - input_schema JSONB, - output_schema JSONB, - side_effects TEXT[], -- inferred or admin-curated - UNIQUE (connection_id, name) -); -``` - -### MCP lifecycle: connect, harvest, invoke - -Three phases, each with distinct concerns. - -**Phase 1 — Connect (one-time, on user action).** User clicks "Connect -Slack MCP." OAuth flow completes. A row is added to `mcp_connections` -with the encrypted tokens. - -**Phase 2 — Harvest (right after connect, also re-runnable).** SurfSense -opens a temporary client to the MCP server, calls `tools/list`, and writes -one row to `mcp_tools` per discovered tool. The temporary client is then -discarded; only the database state persists. - -```python -async def harvest_mcp_server(connection_id: UUID, ctx): - connection = await ctx.db.get(MCPConnection, connection_id) - client = build_temporary_client(connection) - tools = await client.list_tools() - - # Replace existing tool rows for this connection - await ctx.db.execute( - delete(MCPTool).where(MCPTool.connection_id == connection_id) - ) - for tool in tools: - ctx.db.add(MCPTool( - connection_id=connection_id, - name=tool.name, - description=tool.description, - input_schema=tool.inputSchema, - output_schema=tool.outputSchema, - side_effects=infer_side_effects(tool), - )) - connection.last_harvested_at = now() - await ctx.db.commit() -``` - -Harvesting can be re-run on a schedule (say, daily) or on user request, -to pick up new tools the server has added. - -**Phase 3 — Invoke (every time a step references an MCP capability).** -This is where the closure gets built. The executor calls -`ctx.get_capability("slack.post_message")`. The worker's in-memory cache is -checked; on miss, the database is queried: - -```python -async def get_capability(capability_id: str, ctx: ActionContext) -> Capability: - cached = _WORKER_CAPABILITY_CACHE.get((ctx.search_space.id, capability_id)) - if cached: - return cached - - if is_native(capability_id): - capability = _NATIVE_REGISTRY[capability_id] - else: - # MCP path: look up tool metadata - tool_row = await ctx.db.execute( - select(MCPTool) - .join(MCPConnection) - .where(MCPConnection.search_space_id == ctx.search_space.id) - .where(tool_qualified_name(MCPTool, MCPConnection) == capability_id) - ) - capability = Capability( - id=capability_id, - input_schema=tool_row.input_schema, - output_schema=tool_row.output_schema, - side_effects=set(tool_row.side_effects), - handler=make_mcp_handler( - connection_id=tool_row.connection_id, - tool_name=tool_row.name, - ), - ) - - _WORKER_CAPABILITY_CACHE[(ctx.search_space.id, capability_id)] = capability - return capability -``` - -The closure created by `make_mcp_handler` captures only the connection ID -and tool name. When invoked, it asks `ctx.resolve_mcp_client(connection_id)` -to build an authenticated client from the connection record (including -token refresh if needed). That client is also transient — built per call, -discarded after. +See archived design at `docs/automation/archived/mcp-registry.md` once +v1 ships; for now the only consumer of the registry is the in-memory +native path. ### Credentials: resolved at the moment of use From 144d702c354f4985aea68a9af06d35d58ecc8f3e Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:35:37 +0200 Subject: [PATCH 084/198] docs(automation): defer credentials, cost, queue-routing, side-effects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update §3 (Credentials), §7.1 (Dispatcher common path), §8 (Duration classes and queue routing), and §13 (Decisions locked) to reflect the v1-minimum scope: - Credentials block in §3 collapses to a deferred-to-Phase-2 note. The three guarantees (no creds in definition, no creds in LLM context, per-call resolution) return unchanged when Phase 2 ships external capabilities. - Cost-estimate pre-check in the dispatcher's common path is removed. Mid-flight budget kill in the executor still enforces budget_cap_usd. - Queue routing by expected_duration_seconds is deferred. Single automations_default queue in v1. - Decisions 24, 25, 26, 32-37, 38-41 marked deferred with explicit return phase. Three new v1-minimum decisions added (5-field Capability, measured-not-declared cost, single queue). All deferrals are additive: the original designs return as-is when warranted; nothing is rewritten between phases. --- automation-design-plan.md | 122 +++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 66 deletions(-) diff --git a/automation-design-plan.md b/automation-design-plan.md index 07eca2049..9d738c6f7 100644 --- a/automation-design-plan.md +++ b/automation-design-plan.md @@ -138,47 +138,24 @@ See archived design at `docs/automation/archived/mcp-registry.md` once v1 ships; for now the only consumer of the registry is the in-memory native path. -### Credentials: resolved at the moment of use +### Credentials — deferred to Phase 2 -The handler doesn't carry credentials and the closure doesn't capture them. -When invoked, the handler asks `ActionContext` for what it needs: +The earlier per-call credential resolution pattern (`ctx.resolve_mcp_client`, +`ctx.resolve_http_client`, `ctx.resolve_llm`) is **deferred to Phase 2**. +v1 capabilities run server-side using app-level configuration; none of +the seven v1 capabilities needs per-user or per-connection auth. -```python -def make_mcp_handler(connection_id: UUID, tool_name: str): - async def handler(ctx: ActionContext, args: dict) -> Any: - # Credential resolution happens here, per call - client = await ctx.resolve_mcp_client(connection_id) - response = await client.call_tool(name=tool_name, arguments=args) - return response.content - return handler -``` +When Phase 2 ships external-credential capabilities (Slack, email, etc.), +the three guarantees the original design promised are reintroduced +unchanged: -`ctx.resolve_mcp_client(connection_id)`: -1. Loads the `mcp_connections` row -2. Decrypts the access token -3. Refreshes the token if it's expired (using the refresh token) -4. Constructs an `MCPClient` with the token set as a default authorization - header +- Credentials never appear in the automation definition (connection IDs + only). +- Credentials never appear in the LLM's context (the host holds them + and uses them on the LLM's behalf when executing tool calls). +- Credentials are loaded per-call, not pre-loaded into worker memory. -The HTTP library carries the auth header on every subsequent call the -client makes — the handler doesn't think about it after construction. - -For native capabilities calling external APIs directly, -`ctx.resolve_http_client(provider)` returns an authenticated `httpx` -client. For LLM operations, `ctx.resolve_llm(provider)` returns a -configured LLM client. **Three resolution methods, one pattern: the -context returns a client already authenticated.** - -Three properties this gives us: - -- **Credentials never appear in the automation definition.** The JSON - contains capability references and connection IDs, never tokens. -- **Credentials never appear in the LLM's context.** Even during - `agent_task`, the LLM sees tool descriptions only; the host holds - credentials and uses them when executing the tools the LLM requests. -- **Credentials are loaded per-call, not pre-loaded.** The credential - exists in memory only during the moment a handler is making a call. No - long-lived secrets in worker memory. +The Phase-2 design returns as-is; only the v1 surface is simplified. --- @@ -504,12 +481,18 @@ event, evaluates all matching triggers' filters, fires the matches. Common path (after a trigger has fired): 1. Resolve `inputs` from trigger payload and defaults 2. Validate resolved inputs against the automation's input schema -3. **Cost estimate** — sum capabilities' `cost_estimate(args)` for the plan; - refuse if exceeds `budget_cap_usd` -4. **Idempotency check** — dedup against existing pending/running runs -5. **Snapshot the resolved definition** into the run row (immutable history) -6. Enqueue executor task on the appropriate Celery queue (per - `expected_duration_seconds`) +3. **Idempotency check** — dedup against existing pending/running runs +4. **Snapshot the resolved definition** into the run row (immutable history) +5. Enqueue executor task on the single `automations_default` Celery queue + +The cost-estimate pre-check (originally step 3) is **deferred**. +v1 capabilities do not declare `cost_estimate`; pre-flight budgeting +returns when a historical-cost ledger exists. The mid-flight budget +cap (§7.2) still kills the run if accumulated cost crosses +`budget_cap_usd`. + +Queue routing by `expected_duration_seconds` is **deferred** until load +patterns justify a second queue. v1 uses a single queue. ### 7.2 Executor @@ -801,16 +784,18 @@ The engine handles storage (writes to SurfSense's existing object storage), URL generation (signed, scoped to the run's permissions), and cleanup (a nightly Celery Beat task deletes expired artifacts). -### Duration classes and queue routing +### Duration classes and queue routing — deferred -Capabilities declare `expected_duration_seconds`. The dispatcher routes -runs to Celery queues based on the longest-duration step: -- < 10s → `automations_fast` -- 10s – 5min → `automations_medium` -- 5min – 1hr → `automations_long` +The original design routed runs to multiple Celery queues based on each +capability's declared `expected_duration_seconds`. v1 ships with **one +queue** (`automations_default`) and capabilities do not declare a +duration. Multi-queue routing returns when burst load on a single queue +actually justifies the operational complexity of independent worker +pools. -Operators scale each queue's worker pool independently. A future "very -long" queue is a config change, not a contract change. +Adding the second queue is a config change plus reintroducing +`expected_duration_seconds` on the `Capability` dataclass — both +mechanical, additive, and free of design rewrite. --- @@ -1210,9 +1195,9 @@ place. ### Components 23. ✅ Dispatcher / executor / handlers / registry — distinct, each replaceable -24. ✅ Side effects are a set, including `USER_VISIBLE` -25. ✅ `expected_duration_seconds` integer drives queue routing -26. ✅ `produces_artifacts` is a list of `ArtifactSpec`, not a bool +24. ⏸ Side effects are a set, including `USER_VISIBLE` — **deferred** until multi-user automation RBAC ships +25. ⏸ `expected_duration_seconds` integer drives queue routing — **deferred** until a second Celery queue is needed +26. ⏸ `produces_artifacts` is a list of `ArtifactSpec`, not a bool — **deferred** until artifacts beyond the deliverable handlers' own persistence are needed 27. ✅ Output schemas recommended on `agent_task`; editor warns when missing ### Event bus @@ -1220,20 +1205,25 @@ place. 29. ✅ Automations publish run events for composability 30. ✅ Publish/subscribe behind interface — no direct table access elsewhere -### Capability storage (two-tier persistence) +### Capability storage 31. ✅ Native capabilities registered in-memory at startup from the codebase. Identical across all workers. -32. ✅ MCP capability metadata persisted in `mcp_connections` and `mcp_tools` tables. Survives restarts. -33. ✅ MCP handler closures built lazily per worker from database state. Worker-local cache, rebuilt on demand. -34. ✅ MCP server tool list re-harvested on a schedule (default: daily) and on user request. -35. ✅ MCP tools harvested into the capability registry at connection time -36. ✅ Side effects inferred from MCP hints + naming + admin overrides -37. ✅ MCP tools callable directly (no agent required) when caller knows args +32. ⏸ MCP capability metadata persisted in `mcp_connections` and `mcp_tools` tables — **deferred to Phase 4** +33. ⏸ MCP handler closures built lazily per worker from database state — **deferred to Phase 4** +34. ⏸ MCP server tool list re-harvested on a schedule — **deferred to Phase 4** +35. ⏸ MCP tools harvested into the capability registry at connection time — **deferred to Phase 4** +36. ⏸ Side effects inferred from MCP hints + naming + admin overrides — **deferred to Phase 4** +37. ⏸ MCP tools callable directly (no agent required) when caller knows args — **deferred to Phase 4** -### Credentials -38. ✅ Credentials never appear in the automation definition — only connection IDs do -39. ✅ Credentials never appear in the LLM's context — the host holds them and uses them on the LLM's behalf -40. ✅ Credentials resolved per-call by `ActionContext`, not pre-loaded into worker environment -41. ✅ Tokens encrypted at rest in the database; refresh handled automatically by `ActionContext.resolve_*_client` +### Credentials — all deferred to Phase 2 +38. ⏸ Credentials never appear in the automation definition — only connection IDs do — **Phase 2** +39. ⏸ Credentials never appear in the LLM's context — the host holds them — **Phase 2** +40. ⏸ Credentials resolved per-call by `ActionContext`, not pre-loaded into worker environment — **Phase 2** +41. ⏸ Tokens encrypted at rest; refresh handled automatically by `ActionContext.resolve_*_client` — **Phase 2** + +### v1-minimum (new lock) +v1. ✅ `Capability` is exactly five fields: `id`, `description`, `input_schema`, `output_schema`, `handler`. Additional fields are added only when a concrete consumer feature requires them. +v2. ✅ Cost is **measured** from a per-run ledger, not declared. Pre-flight cost checks return when the ledger has enough history. +v3. ✅ Single `automations_default` Celery queue in v1. Multi-queue routing returns when load justifies it. ### NL authoring 42. ✅ LLM-authored templates is the primary path from day one — not a Phase 3 addition. Hand-authoring JSON is supported but secondary From db8c472664b301d38c75d30d96bf65136a11a981 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:37:05 +0200 Subject: [PATCH 085/198] docs(automation): narrow v1 data model + Phase 1 scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §9 (Data model): drop from six tables to three. v1 ships automations, automation_triggers, automation_runs only. domain_events deferred to Phase 3 (event trigger); mcp_connections/mcp_tools deferred to Phase 4 (MCP integration). Remove the table definitions for the deferred ones and replace with a deferred-tables note pointing to the consuming phase. automation_triggers.type enum narrowed to schedule|manual for v1. Webhook and event types ship with their respective phases. secret_hash column deferred to Phase 2 alongside the webhook trigger. automation_runs.cost_usd column deferred until at least one v1 capability records token-level cost — additive when reintroduced. §14 (Phase 1) reorganized into four explicit steps matching the work we're about to do: scaffolding + schemas + empty registries (step 1), then registry population (step 2), then executor (step 3), then NL authoring + UI (step 4). The current commit batch lands step 1 only. --- automation-design-plan.md | 126 ++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 67 deletions(-) diff --git a/automation-design-plan.md b/automation-design-plan.md index 9d738c6f7..f57385e31 100644 --- a/automation-design-plan.md +++ b/automation-design-plan.md @@ -801,12 +801,20 @@ mechanical, additive, and free of design rewrite. ## 9. Data model -Six tables. All scoped by `search_space_id` for RBAC. +**v1 ships three tables:** `automations`, `automation_triggers`, +`automation_runs`. All scoped by `search_space_id` for RBAC. -The first four (`automations`, `automation_triggers`, `automation_runs`, -`domain_events`) are the engine's own state. The last two -(`mcp_connections`, `mcp_tools`) hold the durable knowledge that backs -MCP-derived capabilities — see §3 for the lifecycle rationale. +The other three tables described in earlier drafts are deferred: + +- `domain_events` → **deferred to Phase 3** (introduced with the event + trigger). +- `mcp_connections`, `mcp_tools` → **deferred to Phase 4** (MCP + integration). + +The deferred tables ship as-is when their consuming feature lands; +nothing in the v1 schema needs to change to accommodate them. The three +v1 tables form the engine's persistent state — definitions, triggers, +and an immutable run history. ### `automations` @@ -828,12 +836,14 @@ MCP-derived capabilities — see §3 for the lifecycle rationale. | --------------- | ----------------------------------------------------------------------------- | ------------------------------------------- | | `id` | int PK | | | `automation_id` | FK | | -| `type` | enum: `schedule`, `webhook`, `event` | | +| `type` | enum: `schedule`, `manual` (Phase 2/3 add `webhook`, `event`) | | | `config` | jsonb | validated against trigger's `config_schema` | | `enabled` | bool | | -| `secret_hash` | str / null | for webhook bearer tokens | | `last_fired_at` | timestamp | | +`secret_hash` (for webhook bearer tokens) is **deferred to Phase 2** with +the webhook trigger. + ### `automation_runs` | field | type | notes | @@ -849,61 +859,25 @@ MCP-derived capabilities — see §3 for the lifecycle rationale. | `output` | jsonb / null | | | `artifacts` | jsonb | references to created artifacts | | `error` | jsonb / null | | -| `cost_usd` | decimal | accumulated cost | | `started_at` / `finished_at` | timestamps | | | `agent_session_id`| str / null | link to LangGraph trace if agent_task was used | -### `domain_events` +`cost_usd` (per-run accumulated cost) is **deferred** until at least one +v1 capability records token-level cost. When reintroduced it lands as a +column-only migration. -| field | type | notes | -| ----------------- | ----------- | -------------------------------------------------- | -| `id` | UUID PK | | -| `search_space_id` | FK | scoping | -| `event_type` | varchar | e.g. `drive.file_added`, `automation.run.succeeded` | -| `source_id` | varchar | which connector/automation/etc. produced it | -| `payload` | jsonb | matches the event type's documented schema | -| `created_at` | timestamp | | -| `consumed_by` | jsonb | array of consumer_ids, for tracking + replay | -| `expires_at` | timestamp | auto-cleanup after 7 days | +### Deferred tables -### `mcp_connections` +- **`domain_events`** — the event bus backing event triggers. Ships in + Phase 3 with the event trigger. v1 only emits `automation.run.*` + events into application logs; the table is added when at least one + consumer needs to subscribe to them. +- **`mcp_connections`** / **`mcp_tools`** — see §3. Both ship in Phase 4 + alongside the MCP harvester and the two-tier registry. -Persistent record of MCP server connections per SearchSpace. - -| field | type | notes | -| ------------------- | ----------- | -------------------------------------------------- | -| `id` | UUID PK | | -| `search_space_id` | FK | scoping | -| `server_url` | text | the MCP server's endpoint | -| `transport` | text | `"http"`, `"stdio"`, etc. | -| `name` | text | human-readable label (e.g., "Slack — Acme") | -| `access_token` | bytea | encrypted at rest | -| `refresh_token` | bytea | encrypted at rest | -| `expires_at` | timestamp | for OAuth tokens | -| `last_harvested_at` | timestamp | when tool list was last refreshed | -| `created_at` | timestamp | | -| `created_by` | FK → users | | - -### `mcp_tools` - -The tool list each connected MCP server exposes. Acts as the durable -source for MCP capabilities — definitions reference `mcp_tools` rows by -qualified name, and worker processes lazily build handler closures from -this state. - -| field | type | notes | -| --------------- | ----------- | ------------------------------------------------ | -| `id` | UUID PK | | -| `connection_id` | FK → `mcp_connections.id` ON DELETE CASCADE | | -| `name` | text | the tool name reported by the MCP server | -| `description` | text | description for the NL generator and form editor | -| `input_schema` | jsonb | JSON Schema for tool arguments | -| `output_schema` | jsonb | JSON Schema for tool results | -| `side_effects` | text[] | inferred from MCP hints + naming + admin override | -| UNIQUE | | (connection_id, name) | - -NL drafts are **not** a core table. They live in a generic short-TTL store -(Redis or a transient table) when the NL flow is built in Phase 3. +NL drafts are **not** a core table. They live in a generic short-TTL +store (Redis or a transient table) when the NL flow is built in +Phase 3. --- @@ -1092,21 +1066,39 @@ which actions and triggers are available, not whether users can describe automations in natural language. ### Phase 1 — Engine MVP with NL authoring -- 4 tables + Alembic migration -- Capability registry with native capabilities (`search_space.query`, - `search_space.fetch_document`, `agent.run`) -- `agent_task` action only -- `schedule` trigger + manual "Run now" endpoint -- Executor with retries, timeouts, budget caps -- Template engine (Jinja sandbox + 15 filters + 4 runtime limits) -- **NL authoring flow**: Generator LLM, deterministic validator, - Review LLM, editable form + +**Step 1 (current scope, this batch of commits):** +- 3 tables (`automations`, `automation_triggers`, `automation_runs`) + + Alembic migration +- Empty Capability, Action, Trigger registries (concrete entries land in + later steps when the consuming feature lands) +- Pydantic schemas for the automation definition envelope, the two v1 + trigger configs (`schedule`, `manual`), and the one v1 action config + (`agent_task`) +- Module structure under `app/automations/` (data/, schemas/, + registries/), fully isolated from the existing codebase + +**Step 2:** +- Register the `agent_task` action and the `schedule` / `manual` + triggers in the registries +- Capability registry populated with native deliverable-producing + capabilities (chosen when this step starts) + +**Step 3:** +- Executor (single-queue Celery task) with retries, timeouts, budget + caps measured against `cost_usd` ledger on the run +- Template engine (Jinja sandbox + the v1 filter allowlist + runtime + limits) +- Manual "Run now" endpoint + +**Step 4:** +- NL authoring flow: Generator LLM, deterministic validator, Review LLM, + editable form - Run history UI with Electric SQL streaming **After Phase 1**: a user can describe an automation in natural language, review the proposal (with summary + flagged anomalies), edit any field, -save, and watch it run on a schedule. The Claude Routines value -proposition, on SurfSense's data, with NL-first authoring. +save, and watch it run on a schedule. ### Phase 2 — Webhooks and delivery - `webhook` trigger with per-automation bearer tokens From 113748dfd5054aba1d3e16f7d7350297de0990ca Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:38:41 +0200 Subject: [PATCH 086/198] feat(automation): scaffold isolated module structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create app/automations/ with the SRP-per-file / grouped-folders layout that mirrors app/agents/multi_agent_chat/. Twelve __init__.py files, each a thin re-export with a single-line docstring describing the subpackage's role, no exports yet (filled in subsequent commits). Tree: app/automations/ ├── persistence/ │ ├── enums/ (status / type enums; one per file) │ └── models/ (SQLAlchemy tables; one per file) ├── schemas/ │ ├── definition/ (the JSON envelope, broken by concern) │ ├── triggers/ (per-trigger config schemas) │ └── actions/ (per-action config schemas) └── registries/ ├── capabilities/ (types.py + store.py) ├── actions/ (types.py + store.py) └── triggers/ (types.py + store.py) The persistence/ folder is named to avoid surfsense_backend/.gitignore's data/ ignore rule, which silently masked the original data/ name and its contents from version control. Isolation invariant: the module imports only from app.db (foundational Base + FK targets, unavoidable) and stdlib / SQLAlchemy / Pydantic. No imports from app.agents.*, app.services.*, app.tasks.*, app.routes.* or any other business-logic module. Confirmed importable with no side effects. --- surfsense_backend/app/automations/__init__.py | 5 +++++ surfsense_backend/app/automations/persistence/__init__.py | 5 +++++ .../app/automations/persistence/enums/__init__.py | 5 +++++ .../app/automations/persistence/models/__init__.py | 5 +++++ surfsense_backend/app/automations/registries/__init__.py | 5 +++++ .../app/automations/registries/actions/__init__.py | 5 +++++ .../app/automations/registries/capabilities/__init__.py | 5 +++++ .../app/automations/registries/triggers/__init__.py | 5 +++++ surfsense_backend/app/automations/schemas/__init__.py | 5 +++++ .../app/automations/schemas/actions/__init__.py | 5 +++++ .../app/automations/schemas/definition/__init__.py | 5 +++++ .../app/automations/schemas/triggers/__init__.py | 5 +++++ 12 files changed, 60 insertions(+) create mode 100644 surfsense_backend/app/automations/__init__.py create mode 100644 surfsense_backend/app/automations/persistence/__init__.py create mode 100644 surfsense_backend/app/automations/persistence/enums/__init__.py create mode 100644 surfsense_backend/app/automations/persistence/models/__init__.py create mode 100644 surfsense_backend/app/automations/registries/__init__.py create mode 100644 surfsense_backend/app/automations/registries/actions/__init__.py create mode 100644 surfsense_backend/app/automations/registries/capabilities/__init__.py create mode 100644 surfsense_backend/app/automations/registries/triggers/__init__.py create mode 100644 surfsense_backend/app/automations/schemas/__init__.py create mode 100644 surfsense_backend/app/automations/schemas/actions/__init__.py create mode 100644 surfsense_backend/app/automations/schemas/definition/__init__.py create mode 100644 surfsense_backend/app/automations/schemas/triggers/__init__.py diff --git a/surfsense_backend/app/automations/__init__.py b/surfsense_backend/app/automations/__init__.py new file mode 100644 index 000000000..edb7891ea --- /dev/null +++ b/surfsense_backend/app/automations/__init__.py @@ -0,0 +1,5 @@ +"""Automations: scheduled / triggered runs of capabilities — see automation-design-plan.md.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/persistence/__init__.py b/surfsense_backend/app/automations/persistence/__init__.py new file mode 100644 index 000000000..05c39014e --- /dev/null +++ b/surfsense_backend/app/automations/persistence/__init__.py @@ -0,0 +1,5 @@ +"""Persistence layer: SQLAlchemy enums under ``enums/`` and models under ``models/``.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/persistence/enums/__init__.py b/surfsense_backend/app/automations/persistence/enums/__init__.py new file mode 100644 index 000000000..f221687dc --- /dev/null +++ b/surfsense_backend/app/automations/persistence/enums/__init__.py @@ -0,0 +1,5 @@ +"""SQLAlchemy / Python enums backing the three automation tables.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/persistence/models/__init__.py b/surfsense_backend/app/automations/persistence/models/__init__.py new file mode 100644 index 000000000..da73c9e41 --- /dev/null +++ b/surfsense_backend/app/automations/persistence/models/__init__.py @@ -0,0 +1,5 @@ +"""SQLAlchemy models: one file per table (``automation.py``, ``trigger.py``, ``run.py``).""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/registries/__init__.py b/surfsense_backend/app/automations/registries/__init__.py new file mode 100644 index 000000000..e7334cca8 --- /dev/null +++ b/surfsense_backend/app/automations/registries/__init__.py @@ -0,0 +1,5 @@ +"""Three registries — ``capabilities/``, ``actions/``, ``triggers/`` — populated at import time.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/registries/actions/__init__.py b/surfsense_backend/app/automations/registries/actions/__init__.py new file mode 100644 index 000000000..6b19b7091 --- /dev/null +++ b/surfsense_backend/app/automations/registries/actions/__init__.py @@ -0,0 +1,5 @@ +"""Action registry: ``types.py`` (dataclass), ``store.py`` (dict + register fn).""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/registries/capabilities/__init__.py b/surfsense_backend/app/automations/registries/capabilities/__init__.py new file mode 100644 index 000000000..77f9f88b7 --- /dev/null +++ b/surfsense_backend/app/automations/registries/capabilities/__init__.py @@ -0,0 +1,5 @@ +"""Capability registry: ``types.py`` (dataclass), ``store.py`` (dict + register fn).""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/registries/triggers/__init__.py b/surfsense_backend/app/automations/registries/triggers/__init__.py new file mode 100644 index 000000000..bc795b61a --- /dev/null +++ b/surfsense_backend/app/automations/registries/triggers/__init__.py @@ -0,0 +1,5 @@ +"""Trigger registry: ``types.py`` (dataclass), ``store.py`` (dict + register fn).""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/schemas/__init__.py b/surfsense_backend/app/automations/schemas/__init__.py new file mode 100644 index 000000000..67211b898 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/__init__.py @@ -0,0 +1,5 @@ +"""Pydantic schemas: definition envelope, trigger configs, action configs.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/schemas/actions/__init__.py b/surfsense_backend/app/automations/schemas/actions/__init__.py new file mode 100644 index 000000000..1aa68b629 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/actions/__init__.py @@ -0,0 +1,5 @@ +"""Per-action config schemas: one file per action type registered in v1.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/schemas/definition/__init__.py b/surfsense_backend/app/automations/schemas/definition/__init__.py new file mode 100644 index 000000000..3fbda8cc8 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/definition/__init__.py @@ -0,0 +1,5 @@ +"""Automation definition envelope: the editable structured spec users author and run.""" + +from __future__ import annotations + +__all__: list[str] = [] diff --git a/surfsense_backend/app/automations/schemas/triggers/__init__.py b/surfsense_backend/app/automations/schemas/triggers/__init__.py new file mode 100644 index 000000000..2da765bc3 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/triggers/__init__.py @@ -0,0 +1,5 @@ +"""Per-trigger config schemas: one file per trigger type registered in v1.""" + +from __future__ import annotations + +__all__: list[str] = [] From 05931375f4c5d92d5737c3c06bef718a1712d374 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:42:50 +0200 Subject: [PATCH 087/198] feat(automation): add SQLAlchemy models for the three v1 tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three enums (one file each) plus three models (one file each), all under app/automations/persistence/. The module imports from app.db only (Base/BaseModel/TimestampMixin and FK targets searchspaces.id / user.id); no business-logic imports. Enums: - AutomationStatus: active | paused | archived - RunStatus: pending | running | succeeded | failed | cancelled | timed_out - TriggerType: schedule | manual (Phase-2/3 add webhook | event) Models: - Automation: search_space-scoped, created_by_user_id (SET NULL), name + description, status enum, definition JSONB, version int, updated_at with onupdate. - AutomationTrigger: FK → automations (CASCADE), type enum, config JSONB, enabled bool, last_fired_at. Webhook secret_hash is omitted until Phase 2. - AutomationRun: FK → automations (CASCADE), nullable trigger_id (SET NULL — null = manual via UI), status enum, definition_snapshot for immutable history, trigger_payload / resolved_inputs / step_results / output / artifacts / error JSONB columns, started_at / finished_at timestamps, agent_session_id for linking to the LangGraph trace. cost_usd column omitted until at least one v1 capability records token-level cost. Verified: Base.metadata exposes all three table names; columns and enums introspect as documented; no linter errors. --- .../app/automations/persistence/__init__.py | 12 ++- .../automations/persistence/enums/__init__.py | 10 ++- .../persistence/enums/automation_status.py | 18 +++++ .../persistence/enums/run_status.py | 28 +++++++ .../persistence/enums/trigger_type.py | 21 ++++++ .../persistence/models/__init__.py | 10 ++- .../persistence/models/automation.py | 75 +++++++++++++++++++ .../app/automations/persistence/models/run.py | 72 ++++++++++++++++++ .../automations/persistence/models/trigger.py | 57 ++++++++++++++ 9 files changed, 300 insertions(+), 3 deletions(-) create mode 100644 surfsense_backend/app/automations/persistence/enums/automation_status.py create mode 100644 surfsense_backend/app/automations/persistence/enums/run_status.py create mode 100644 surfsense_backend/app/automations/persistence/enums/trigger_type.py create mode 100644 surfsense_backend/app/automations/persistence/models/automation.py create mode 100644 surfsense_backend/app/automations/persistence/models/run.py create mode 100644 surfsense_backend/app/automations/persistence/models/trigger.py diff --git a/surfsense_backend/app/automations/persistence/__init__.py b/surfsense_backend/app/automations/persistence/__init__.py index 05c39014e..265742a85 100644 --- a/surfsense_backend/app/automations/persistence/__init__.py +++ b/surfsense_backend/app/automations/persistence/__init__.py @@ -2,4 +2,14 @@ from __future__ import annotations -__all__: list[str] = [] +from .enums import AutomationStatus, RunStatus, TriggerType +from .models import Automation, AutomationRun, AutomationTrigger + +__all__ = [ + "Automation", + "AutomationRun", + "AutomationStatus", + "AutomationTrigger", + "RunStatus", + "TriggerType", +] diff --git a/surfsense_backend/app/automations/persistence/enums/__init__.py b/surfsense_backend/app/automations/persistence/enums/__init__.py index f221687dc..cf9e7dd1b 100644 --- a/surfsense_backend/app/automations/persistence/enums/__init__.py +++ b/surfsense_backend/app/automations/persistence/enums/__init__.py @@ -2,4 +2,12 @@ from __future__ import annotations -__all__: list[str] = [] +from .automation_status import AutomationStatus +from .run_status import RunStatus +from .trigger_type import TriggerType + +__all__ = [ + "AutomationStatus", + "RunStatus", + "TriggerType", +] diff --git a/surfsense_backend/app/automations/persistence/enums/automation_status.py b/surfsense_backend/app/automations/persistence/enums/automation_status.py new file mode 100644 index 000000000..3f2ca9621 --- /dev/null +++ b/surfsense_backend/app/automations/persistence/enums/automation_status.py @@ -0,0 +1,18 @@ +"""``AutomationStatus`` — lifecycle of a stored automation definition.""" + +from __future__ import annotations + +from enum import StrEnum + + +class AutomationStatus(StrEnum): + """Status of an automation in the registry. + + ``active`` — eligible to fire from its triggers. + ``paused`` — definition retained, triggers do not fire. + ``archived`` — kept for run history only; no edits, no fires. + """ + + ACTIVE = "active" + PAUSED = "paused" + ARCHIVED = "archived" diff --git a/surfsense_backend/app/automations/persistence/enums/run_status.py b/surfsense_backend/app/automations/persistence/enums/run_status.py new file mode 100644 index 000000000..0f619bd82 --- /dev/null +++ b/surfsense_backend/app/automations/persistence/enums/run_status.py @@ -0,0 +1,28 @@ +"""``RunStatus`` — the state machine of a single ``AutomationRun``.""" + +from __future__ import annotations + +from enum import StrEnum + + +class RunStatus(StrEnum): + """Lifecycle states of an ``AutomationRun`` row. + + Transitions are linear with three terminal branches: + + pending → running → (succeeded | failed | cancelled | timed_out) + + ``pending`` — row created, executor task enqueued, work not started. + ``running`` — executor has picked up the run. + ``succeeded`` — terminal: plan completed without error. + ``failed`` — terminal: at least one step raised an unrecoverable error. + ``cancelled`` — terminal: caller asked for cancellation. + ``timed_out`` — terminal: run exceeded its configured timeout. + """ + + PENDING = "pending" + RUNNING = "running" + SUCCEEDED = "succeeded" + FAILED = "failed" + CANCELLED = "cancelled" + TIMED_OUT = "timed_out" diff --git a/surfsense_backend/app/automations/persistence/enums/trigger_type.py b/surfsense_backend/app/automations/persistence/enums/trigger_type.py new file mode 100644 index 000000000..eb06fe773 --- /dev/null +++ b/surfsense_backend/app/automations/persistence/enums/trigger_type.py @@ -0,0 +1,21 @@ +"""``TriggerType`` — the trigger-kind discriminator (v1 = schedule, manual).""" + +from __future__ import annotations + +from enum import StrEnum + + +class TriggerType(StrEnum): + """Kind of trigger an ``AutomationTrigger`` row represents. + + v1 ships two kinds: + + ``schedule`` — fires on a cron expression managed by Celery Beat. + ``manual`` — fires on demand from the UI's "Run now" affordance. + + ``webhook`` and ``event`` are deferred to Phase 2 and Phase 3 + respectively; adding them is an enum-value extension only. + """ + + SCHEDULE = "schedule" + MANUAL = "manual" diff --git a/surfsense_backend/app/automations/persistence/models/__init__.py b/surfsense_backend/app/automations/persistence/models/__init__.py index da73c9e41..4aca02a03 100644 --- a/surfsense_backend/app/automations/persistence/models/__init__.py +++ b/surfsense_backend/app/automations/persistence/models/__init__.py @@ -2,4 +2,12 @@ from __future__ import annotations -__all__: list[str] = [] +from .automation import Automation +from .run import AutomationRun +from .trigger import AutomationTrigger + +__all__ = [ + "Automation", + "AutomationRun", + "AutomationTrigger", +] diff --git a/surfsense_backend/app/automations/persistence/models/automation.py b/surfsense_backend/app/automations/persistence/models/automation.py new file mode 100644 index 000000000..fc4a1ed93 --- /dev/null +++ b/surfsense_backend/app/automations/persistence/models/automation.py @@ -0,0 +1,75 @@ +"""``Automation`` table — the editable, versioned automation definition.""" + +from __future__ import annotations + +from datetime import UTC, datetime + +from sqlalchemy import ( + TIMESTAMP, + Column, + Enum as SQLAlchemyEnum, + ForeignKey, + Integer, + String, + Text, +) +from sqlalchemy.dialects.postgresql import JSONB, UUID + +from app.db import BaseModel, TimestampMixin + +from ..enums.automation_status import AutomationStatus + + +class Automation(BaseModel, TimestampMixin): + """The editable, versioned spec a user authors. + + The ``definition`` JSON is what the user (or the NL generator) writes + and edits. Each save bumps ``version`` by one; the previous JSON is + not kept in this row — version history is reconstructed from the + ``definition_snapshot`` column on every ``AutomationRun`` that fired + against a given version. + """ + + __tablename__ = "automations" + + search_space_id = Column( + Integer, + ForeignKey("searchspaces.id", ondelete="CASCADE"), + nullable=False, + index=True, + ) + + created_by_user_id = Column( + UUID(as_uuid=True), + ForeignKey("user.id", ondelete="SET NULL"), + nullable=True, + index=True, + ) + + name = Column(String(200), nullable=False) + description = Column(Text, nullable=True) + + status = Column( + SQLAlchemyEnum(AutomationStatus, name="automation_status"), + nullable=False, + default=AutomationStatus.ACTIVE, + server_default=AutomationStatus.ACTIVE.value, + index=True, + ) + + definition = Column(JSONB, nullable=False) + + version = Column( + Integer, + nullable=False, + default=1, + server_default="1", + ) + + updated_at = Column( + TIMESTAMP(timezone=True), + nullable=False, + default=lambda: datetime.now(UTC), + onupdate=lambda: datetime.now(UTC), + index=True, + ) diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py new file mode 100644 index 000000000..5c6ec93ec --- /dev/null +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -0,0 +1,72 @@ +"""``AutomationRun`` table — the immutable per-fire execution record.""" + +from __future__ import annotations + +from sqlalchemy import ( + TIMESTAMP, + Column, + Enum as SQLAlchemyEnum, + ForeignKey, + Integer, + String, +) +from sqlalchemy.dialects.postgresql import JSONB + +from app.db import BaseModel, TimestampMixin + +from ..enums.run_status import RunStatus + + +class AutomationRun(BaseModel, TimestampMixin): + """One execution of an automation. + + Every fire of any trigger inserts exactly one row here. The row is + immutable from the user's perspective — the executor only updates + ``status``, ``step_results``, ``output``, ``artifacts``, ``error``, + ``started_at``, ``finished_at`` as the run progresses; the + ``definition_snapshot`` is locked at fire time so the user can always + see exactly what code path executed for any historical run. + """ + + __tablename__ = "automation_runs" + + automation_id = Column( + Integer, + ForeignKey("automations.id", ondelete="CASCADE"), + nullable=False, + index=True, + ) + + trigger_id = Column( + Integer, + ForeignKey("automation_triggers.id", ondelete="SET NULL"), + nullable=True, + index=True, + ) + + status = Column( + SQLAlchemyEnum(RunStatus, name="automation_run_status"), + nullable=False, + default=RunStatus.PENDING, + server_default=RunStatus.PENDING.value, + index=True, + ) + + definition_snapshot = Column(JSONB, nullable=False) + + trigger_payload = Column(JSONB, nullable=True) + + resolved_inputs = Column(JSONB, nullable=False, server_default="{}") + + step_results = Column(JSONB, nullable=False, server_default="[]") + + output = Column(JSONB, nullable=True) + + artifacts = Column(JSONB, nullable=False, server_default="[]") + + error = Column(JSONB, nullable=True) + + started_at = Column(TIMESTAMP(timezone=True), nullable=True) + finished_at = Column(TIMESTAMP(timezone=True), nullable=True) + + agent_session_id = Column(String(200), nullable=True) diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py new file mode 100644 index 000000000..3173770d6 --- /dev/null +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -0,0 +1,57 @@ +"""``AutomationTrigger`` table — one row per (automation, trigger-instance) pair.""" + +from __future__ import annotations + +from sqlalchemy import ( + TIMESTAMP, + Boolean, + Column, + Enum as SQLAlchemyEnum, + ForeignKey, + Integer, +) +from sqlalchemy.dialects.postgresql import JSONB + +from app.db import BaseModel, TimestampMixin + +from ..enums.trigger_type import TriggerType + + +class AutomationTrigger(BaseModel, TimestampMixin): + """One trigger attached to an automation. + + An automation may have multiple triggers — e.g. a ``schedule`` trigger + for the autonomous path and a ``manual`` trigger backing the UI's + "Run now" affordance. Each trigger's ``config`` is validated against + the registered ``TriggerDefinition.config_schema`` for its ``type``. + """ + + __tablename__ = "automation_triggers" + + automation_id = Column( + Integer, + ForeignKey("automations.id", ondelete="CASCADE"), + nullable=False, + index=True, + ) + + type = Column( + SQLAlchemyEnum(TriggerType, name="automation_trigger_type"), + nullable=False, + index=True, + ) + + config = Column(JSONB, nullable=False) + + enabled = Column( + Boolean, + nullable=False, + default=True, + server_default="true", + index=True, + ) + + last_fired_at = Column( + TIMESTAMP(timezone=True), + nullable=True, + ) From d9183464d9ec5e69f4630083993b0e3277eda7d4 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:44:33 +0200 Subject: [PATCH 088/198] feat(automation): add Alembic migration for the three automation tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migration 144 -> 143. Matches the SQLAlchemy models added in commit 7 and the v1 data model in automation-design-plan.md §9. Up: - CREATE TYPE automation_status / automation_trigger_type / automation_run_status (PostgreSQL ENUMs created first because the tables reference them). - CREATE TABLE automations with FK to searchspaces (CASCADE) and user (SET NULL); five indexes matching the SQLAlchemy model. - CREATE TABLE automation_triggers with FK to automations (CASCADE); four indexes. - CREATE TABLE automation_runs with FK to automations (CASCADE) and automation_triggers (SET NULL — null trigger_id == manual via UI); four indexes. Down: drops every index, table, and ENUM in reverse-dependency order so the migration is reversible without ON DELETE side effects. Verified: `alembic history` resolves 143 -> 144 (head) cleanly. domain_events (Phase 3) and mcp_connections / mcp_tools (Phase 4) ship in their own migrations when the consuming feature lands; this migration only covers the three v1 tables. --- .../versions/144_add_automation_tables.py | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 surfsense_backend/alembic/versions/144_add_automation_tables.py diff --git a/surfsense_backend/alembic/versions/144_add_automation_tables.py b/surfsense_backend/alembic/versions/144_add_automation_tables.py new file mode 100644 index 000000000..6aa208dc1 --- /dev/null +++ b/surfsense_backend/alembic/versions/144_add_automation_tables.py @@ -0,0 +1,167 @@ +"""Add automation tables (automations, automation_triggers, automation_runs) + +Revision ID: 144 +Revises: 143 +Create Date: 2026-05-26 + +Adds the three tables that back the v1 automation engine, plus the +three PostgreSQL ENUM types they reference. Matches the SQLAlchemy +models under ``app.automations.persistence.models`` and the v1 data +model in ``automation-design-plan.md`` §9. + +v1 ships these three tables only. ``domain_events`` is deferred to +Phase 3 with the event trigger; ``mcp_connections`` / ``mcp_tools`` +are deferred to Phase 4 with the MCP integration. +""" + +from collections.abc import Sequence + +from alembic import op + +revision: str = "144" +down_revision: str | None = "143" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + # ENUM types (PostgreSQL requires types created before tables that use them) + op.execute( + """ + CREATE TYPE automation_status AS ENUM ( + 'active', 'paused', 'archived' + ); + """ + ) + op.execute( + """ + CREATE TYPE automation_trigger_type AS ENUM ( + 'schedule', 'manual' + ); + """ + ) + op.execute( + """ + CREATE TYPE automation_run_status AS ENUM ( + 'pending', 'running', 'succeeded', 'failed', + 'cancelled', 'timed_out' + ); + """ + ) + + # automations — the editable, versioned automation definition + op.execute( + """ + CREATE TABLE automations ( + id SERIAL PRIMARY KEY, + search_space_id INTEGER NOT NULL + REFERENCES searchspaces(id) ON DELETE CASCADE, + created_by_user_id UUID + REFERENCES "user"(id) ON DELETE SET NULL, + name VARCHAR(200) NOT NULL, + description TEXT, + status automation_status NOT NULL DEFAULT 'active', + definition JSONB NOT NULL, + version INTEGER NOT NULL DEFAULT 1, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + ); + """ + ) + op.execute( + "CREATE INDEX ix_automations_search_space_id ON automations(search_space_id);" + ) + op.execute( + "CREATE INDEX ix_automations_created_by_user_id ON automations(created_by_user_id);" + ) + op.execute("CREATE INDEX ix_automations_status ON automations(status);") + op.execute("CREATE INDEX ix_automations_created_at ON automations(created_at);") + op.execute("CREATE INDEX ix_automations_updated_at ON automations(updated_at);") + + # automation_triggers — one row per (automation, trigger-instance) pair + op.execute( + """ + CREATE TABLE automation_triggers ( + id SERIAL PRIMARY KEY, + automation_id INTEGER NOT NULL + REFERENCES automations(id) ON DELETE CASCADE, + type automation_trigger_type NOT NULL, + config JSONB NOT NULL, + enabled BOOLEAN NOT NULL DEFAULT true, + last_fired_at TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + ); + """ + ) + op.execute( + "CREATE INDEX ix_automation_triggers_automation_id ON automation_triggers(automation_id);" + ) + op.execute( + "CREATE INDEX ix_automation_triggers_type ON automation_triggers(type);" + ) + op.execute( + "CREATE INDEX ix_automation_triggers_enabled ON automation_triggers(enabled);" + ) + op.execute( + "CREATE INDEX ix_automation_triggers_created_at ON automation_triggers(created_at);" + ) + + # automation_runs — the immutable per-fire execution record + op.execute( + """ + CREATE TABLE automation_runs ( + id SERIAL PRIMARY KEY, + automation_id INTEGER NOT NULL + REFERENCES automations(id) ON DELETE CASCADE, + trigger_id INTEGER + REFERENCES automation_triggers(id) ON DELETE SET NULL, + status automation_run_status NOT NULL DEFAULT 'pending', + definition_snapshot JSONB NOT NULL, + trigger_payload JSONB, + resolved_inputs JSONB NOT NULL DEFAULT '{}'::jsonb, + step_results JSONB NOT NULL DEFAULT '[]'::jsonb, + output JSONB, + artifacts JSONB NOT NULL DEFAULT '[]'::jsonb, + error JSONB, + started_at TIMESTAMP WITH TIME ZONE, + finished_at TIMESTAMP WITH TIME ZONE, + agent_session_id VARCHAR(200), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + ); + """ + ) + op.execute( + "CREATE INDEX ix_automation_runs_automation_id ON automation_runs(automation_id);" + ) + op.execute( + "CREATE INDEX ix_automation_runs_trigger_id ON automation_runs(trigger_id);" + ) + op.execute("CREATE INDEX ix_automation_runs_status ON automation_runs(status);") + op.execute( + "CREATE INDEX ix_automation_runs_created_at ON automation_runs(created_at);" + ) + + +def downgrade() -> None: + op.execute("DROP INDEX IF EXISTS ix_automation_runs_created_at;") + op.execute("DROP INDEX IF EXISTS ix_automation_runs_status;") + op.execute("DROP INDEX IF EXISTS ix_automation_runs_trigger_id;") + op.execute("DROP INDEX IF EXISTS ix_automation_runs_automation_id;") + op.execute("DROP TABLE IF EXISTS automation_runs;") + + op.execute("DROP INDEX IF EXISTS ix_automation_triggers_created_at;") + op.execute("DROP INDEX IF EXISTS ix_automation_triggers_enabled;") + op.execute("DROP INDEX IF EXISTS ix_automation_triggers_type;") + op.execute("DROP INDEX IF EXISTS ix_automation_triggers_automation_id;") + op.execute("DROP TABLE IF EXISTS automation_triggers;") + + op.execute("DROP INDEX IF EXISTS ix_automations_updated_at;") + op.execute("DROP INDEX IF EXISTS ix_automations_created_at;") + op.execute("DROP INDEX IF EXISTS ix_automations_status;") + op.execute("DROP INDEX IF EXISTS ix_automations_created_by_user_id;") + op.execute("DROP INDEX IF EXISTS ix_automations_search_space_id;") + op.execute("DROP TABLE IF EXISTS automations;") + + op.execute("DROP TYPE IF EXISTS automation_run_status;") + op.execute("DROP TYPE IF EXISTS automation_trigger_type;") + op.execute("DROP TYPE IF EXISTS automation_status;") From be4d43d6c9c41ea115c7a6e4a2bbf41afbf241b3 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:50:52 +0200 Subject: [PATCH 089/198] feat(automation): add Pydantic schemas for the automation definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three layers of Pydantic models under app/automations/schemas/, one file per concern (SRP), matching the envelope in automation-design-plan.md §5. definition/ — the editable envelope persisted in automations.definition: - envelope.py AutomationDefinition (top-level shape) - plan_step.py PlanStep (one step in the sequential plan) - inputs.py InputsBlock (the inputs JSON Schema wrapper) - execution.py ExecutionBlock (timeouts, retries, concurrency, budget cap, on_failure plan) - metadata.py MetadataBlock (tags + created_from_nl + extras) - trigger_spec.py TriggerSpec (one entry in triggers[]) triggers/ — per-trigger config schemas, dispatched by registry on the TriggerSpec.type discriminator: - schedule.py ScheduleTriggerConfig(cron, timezone) - manual.py ManualTriggerConfig() — empty in v1 actions/ — per-action config schemas, dispatched by registry on the PlanStep.action discriminator: - agent_task.py AgentTaskActionConfig(prompt, tools, model, output_schema) Design properties verified by an inline smoke test: - The §5 worked example round-trips through model_validate_json / model_dump_json byte-for-byte (InputsBlock uses serialize_by_alias so the JSON key stays "schema" not "schema_"). - Envelope rejects unknown top-level keys (extra="forbid"). - MetadataBlock tolerates unknown keys (extra="allow"). - ExecutionBlock defaults apply when the block is omitted. - retry_backoff and concurrency are typed as Literal — bogus values rejected at validation time. - Per-type configs enforce their required fields (cron + timezone on schedule; non-empty prompt on agent_task). The envelope keeps trigger and action configs as untyped dicts on purpose — per-type validation is a registry-driven dispatch (commit 10), keeping the envelope free of every-type-knows-every-type coupling. --- .../app/automations/schemas/__init__.py | 23 ++++- .../automations/schemas/actions/__init__.py | 6 +- .../automations/schemas/actions/agent_task.py | 66 ++++++++++++++ .../schemas/definition/__init__.py | 16 +++- .../schemas/definition/envelope.py | 89 +++++++++++++++++++ .../schemas/definition/execution.py | 76 ++++++++++++++++ .../automations/schemas/definition/inputs.py | 43 +++++++++ .../schemas/definition/metadata.py | 36 ++++++++ .../schemas/definition/plan_step.py | 86 ++++++++++++++++++ .../schemas/definition/trigger_spec.py | 40 +++++++++ .../automations/schemas/triggers/__init__.py | 8 +- .../automations/schemas/triggers/manual.py | 21 +++++ .../automations/schemas/triggers/schedule.py | 33 +++++++ 13 files changed, 539 insertions(+), 4 deletions(-) create mode 100644 surfsense_backend/app/automations/schemas/actions/agent_task.py create mode 100644 surfsense_backend/app/automations/schemas/definition/envelope.py create mode 100644 surfsense_backend/app/automations/schemas/definition/execution.py create mode 100644 surfsense_backend/app/automations/schemas/definition/inputs.py create mode 100644 surfsense_backend/app/automations/schemas/definition/metadata.py create mode 100644 surfsense_backend/app/automations/schemas/definition/plan_step.py create mode 100644 surfsense_backend/app/automations/schemas/definition/trigger_spec.py create mode 100644 surfsense_backend/app/automations/schemas/triggers/manual.py create mode 100644 surfsense_backend/app/automations/schemas/triggers/schedule.py diff --git a/surfsense_backend/app/automations/schemas/__init__.py b/surfsense_backend/app/automations/schemas/__init__.py index 67211b898..83a95a2a8 100644 --- a/surfsense_backend/app/automations/schemas/__init__.py +++ b/surfsense_backend/app/automations/schemas/__init__.py @@ -2,4 +2,25 @@ from __future__ import annotations -__all__: list[str] = [] +from .actions import AgentTaskActionConfig +from .definition import ( + AutomationDefinition, + ExecutionBlock, + InputsBlock, + MetadataBlock, + PlanStep, + TriggerSpec, +) +from .triggers import ManualTriggerConfig, ScheduleTriggerConfig + +__all__ = [ + "AgentTaskActionConfig", + "AutomationDefinition", + "ExecutionBlock", + "InputsBlock", + "ManualTriggerConfig", + "MetadataBlock", + "PlanStep", + "ScheduleTriggerConfig", + "TriggerSpec", +] diff --git a/surfsense_backend/app/automations/schemas/actions/__init__.py b/surfsense_backend/app/automations/schemas/actions/__init__.py index 1aa68b629..17c257562 100644 --- a/surfsense_backend/app/automations/schemas/actions/__init__.py +++ b/surfsense_backend/app/automations/schemas/actions/__init__.py @@ -2,4 +2,8 @@ from __future__ import annotations -__all__: list[str] = [] +from .agent_task import AgentTaskActionConfig + +__all__ = [ + "AgentTaskActionConfig", +] diff --git a/surfsense_backend/app/automations/schemas/actions/agent_task.py b/surfsense_backend/app/automations/schemas/actions/agent_task.py new file mode 100644 index 000000000..74e41166a --- /dev/null +++ b/surfsense_backend/app/automations/schemas/actions/agent_task.py @@ -0,0 +1,66 @@ +"""``AgentTaskActionConfig`` — config for the ``agent_task`` action type.""" + +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field + + +class AgentTaskActionConfig(BaseModel): + """Config for an ``agent_task`` plan step. + + Validated against ``PlanStep.config`` whenever the step's + ``action`` is ``agent_task``. The step instructs the LangGraph + Deep Agent runtime to: + + 1. Receive ``prompt`` (with all preceding-step outputs and inputs + already rendered by the template engine). + 2. Run the agent with access to *exactly* the capabilities named + in ``tools`` — nothing else from the registry is visible to + this agent invocation. + 3. Return a JSON object matching ``output_schema`` (recommended; + the executor validates and re-prompts on mismatch). + + ``output_schema`` is the design's "dynamic output contract" — + instead of locking the output shape on the ActionDefinition (as + tight actions do), the user declares the shape they want for this + specific step, and the agent has to match it. + """ + + model_config = ConfigDict(extra="forbid") + + prompt: str = Field( + ..., + description=( + "The task prompt rendered through the Jinja sandbox. May " + "reference automation inputs and prior-step outputs." + ), + min_length=1, + ) + tools: list[str] = Field( + default_factory=list, + description=( + "Allowlist of capability IDs the agent may call (e.g., " + "'search_space.query'). Empty list = no tool access; the " + "agent must answer from the prompt alone." + ), + ) + model: str | None = Field( + default=None, + description=( + "Optional LiteLLM model identifier (e.g., " + "'anthropic/claude-sonnet-4-7'). Omitted means the " + "automation falls back to the search space's default " + "agent_llm_id." + ), + ) + output_schema: dict[str, Any] | None = Field( + default=None, + description=( + "Optional JSON Schema declaring the shape the agent must " + "return. Strongly recommended; the editor warns when " + "missing. Validated by the executor before binding to " + "``output_as``." + ), + ) diff --git a/surfsense_backend/app/automations/schemas/definition/__init__.py b/surfsense_backend/app/automations/schemas/definition/__init__.py index 3fbda8cc8..14040c20a 100644 --- a/surfsense_backend/app/automations/schemas/definition/__init__.py +++ b/surfsense_backend/app/automations/schemas/definition/__init__.py @@ -2,4 +2,18 @@ from __future__ import annotations -__all__: list[str] = [] +from .envelope import AutomationDefinition +from .execution import ExecutionBlock +from .inputs import InputsBlock +from .metadata import MetadataBlock +from .plan_step import PlanStep +from .trigger_spec import TriggerSpec + +__all__ = [ + "AutomationDefinition", + "ExecutionBlock", + "InputsBlock", + "MetadataBlock", + "PlanStep", + "TriggerSpec", +] diff --git a/surfsense_backend/app/automations/schemas/definition/envelope.py b/surfsense_backend/app/automations/schemas/definition/envelope.py new file mode 100644 index 000000000..ccf4c53df --- /dev/null +++ b/surfsense_backend/app/automations/schemas/definition/envelope.py @@ -0,0 +1,89 @@ +"""``AutomationDefinition`` — the top-level envelope persisted in ``automations.definition``.""" + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + +from .execution import ExecutionBlock +from .inputs import InputsBlock +from .metadata import MetadataBlock +from .plan_step import PlanStep +from .trigger_spec import TriggerSpec + + +class AutomationDefinition(BaseModel): + """The top-level JSON shape stored in ``automations.definition``. + + This is the editable spec a user authors (or the NL generator + produces). The envelope is structural only — every nested + discriminator (``triggers[].type``, ``plan[].action``) is resolved + against the registries at validation time, so adding a new + trigger or action type does not require touching this schema. + + See ``automation-design-plan.md`` §5 for the worked example and + rationale. + """ + + model_config = ConfigDict(extra="forbid") + + schema_version: str = Field( + default="1.0", + description=( + "Schema version of the envelope itself. Migrations bump " + "this when the envelope shape changes; nested per-type " + "configs evolve independently via the registries." + ), + ) + name: str = Field( + ..., + description="Short, user-facing name shown in lists.", + min_length=1, + max_length=200, + ) + goal: str | None = Field( + default=None, + description=( + "Optional plain-language statement of what the " + "automation is for. Used by the NL generator's review " + "pass and by the UI's run dialog." + ), + ) + inputs: InputsBlock | None = Field( + default=None, + description=( + "Optional input contract. When omitted, the automation " + "accepts no inputs at fire time." + ), + ) + triggers: list[TriggerSpec] = Field( + default_factory=list, + description=( + "Triggers that fire this automation. Empty list means " + "the automation is only runnable via the manual " + "``Run now`` path." + ), + ) + plan: list[PlanStep] = Field( + ..., + description=( + "Ordered sequence of steps. Executed in array order — " + "no parallelism, no DAGs, no loops at the envelope " + "level." + ), + min_length=1, + ) + execution: ExecutionBlock = Field( + default_factory=ExecutionBlock, + description=( + "Execution defaults (timeouts, retries, concurrency, " + "budget). All fields default to safe values; the block " + "may be omitted entirely." + ), + ) + metadata: MetadataBlock = Field( + default_factory=MetadataBlock, + description=( + "Free-form metadata (tags, NL-generator breadcrumbs, " + "UI annotations). Tolerates unknown keys by design." + ), + ) diff --git a/surfsense_backend/app/automations/schemas/definition/execution.py b/surfsense_backend/app/automations/schemas/definition/execution.py new file mode 100644 index 000000000..bb80e7281 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/definition/execution.py @@ -0,0 +1,76 @@ +"""``ExecutionBlock`` — the ``execution`` section of the automation definition.""" + +from __future__ import annotations + +from typing import Literal + +from pydantic import BaseModel, ConfigDict, Field + +from .plan_step import PlanStep + + +class ExecutionBlock(BaseModel): + """The ``execution`` block of an ``AutomationDefinition``. + + Carries automation-wide defaults that individual ``PlanStep``s + can override. Every field has a sane default so an automation + definition may omit the block entirely; in that case all defaults + apply. + + ``on_failure`` is a secondary plan that runs only when the main + ``plan`` fails after retries exhaust. It uses the same + ``PlanStep`` shape as the main plan and shares the same execution + semantics. + """ + + model_config = ConfigDict(extra="forbid") + + timeout_seconds: int = Field( + default=600, + gt=0, + description=( + "Hard wall-clock cap for the entire run. The executor " + "transitions the run to ``timed_out`` when this is " + "exceeded." + ), + ) + max_retries: int = Field( + default=2, + ge=0, + description=( + "Per-step retry budget applied when a step raises a " + "retryable error. Steps may override per-step." + ), + ) + retry_backoff: Literal["exponential", "linear", "none"] = Field( + default="exponential", + description="Backoff policy between retries.", + ) + concurrency: Literal[ + "drop_if_running", "queue", "always" + ] = Field( + default="drop_if_running", + description=( + "Behaviour when a new fire arrives while a previous run " + "is still in progress. ``drop_if_running`` skips the new " + "fire, ``queue`` enqueues it, ``always`` runs it in " + "parallel." + ), + ) + budget_cap_usd: float | None = Field( + default=None, + gt=0, + description=( + "Optional mid-flight cost cap in USD. The executor kills " + "the run when accumulated cost exceeds this value. v1 " + "treats this as an advisory because cost tracking lands " + "with the executor in a later step." + ), + ) + on_failure: list[PlanStep] = Field( + default_factory=list, + description=( + "Secondary plan executed only when the main plan fails " + "after retries exhaust. Empty list means no fallback." + ), + ) diff --git a/surfsense_backend/app/automations/schemas/definition/inputs.py b/surfsense_backend/app/automations/schemas/definition/inputs.py new file mode 100644 index 000000000..279efc113 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/definition/inputs.py @@ -0,0 +1,43 @@ +"""``InputsBlock`` — the ``inputs`` section of the automation definition.""" + +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field + + +class InputsBlock(BaseModel): + """The ``inputs`` block of an ``AutomationDefinition``. + + Holds a JSON Schema describing what data the automation accepts at + fire time. The same schema is used by: + + - The form editor (to render the manual-run dialog). + - The dispatcher (to validate trigger payloads before enqueueing + executor work). + - The template engine (to expose ``{{ inputs.* }}`` references in + plan-step configs). + + The ``schema`` value is the JSON-Schema dict itself, not a + Pydantic model — automations express their input contract in pure + JSON Schema so it round-trips losslessly through the database and + the NL generator. + """ + + model_config = ConfigDict( + extra="forbid", + populate_by_name=True, + serialize_by_alias=True, + ) + + schema_: dict[str, Any] = Field( + ..., + alias="schema", + description=( + "JSON Schema (draft-07 compatible) describing the inputs " + "this automation accepts. Properties may use the special " + "``$last_fired_at`` default literal to bind to the " + "trigger's last fire time." + ), + ) diff --git a/surfsense_backend/app/automations/schemas/definition/metadata.py b/surfsense_backend/app/automations/schemas/definition/metadata.py new file mode 100644 index 000000000..dc6541983 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/definition/metadata.py @@ -0,0 +1,36 @@ +"""``MetadataBlock`` — the ``metadata`` section of the automation definition.""" + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class MetadataBlock(BaseModel): + """Free-form metadata attached to the automation definition. + + Unlike the rest of the envelope this block tolerates unknown keys + (``extra='allow'``) — it's a deliberate extension point for + UI annotations, NL-generator breadcrumbs, custom tags, etc. + + Two fields are first-class so the rest of the system can rely on + them without reaching into the loose extras: + + ``tags`` — used by the UI for filtering and grouping. + ``created_from_nl`` — set by the NL generator so we can later + measure how many runs came from natural-language authoring. + """ + + model_config = ConfigDict(extra="allow") + + tags: list[str] = Field( + default_factory=list, + description="UI-facing tags. No semantic meaning to the engine.", + ) + created_from_nl: bool = Field( + default=False, + description=( + "True when the definition was produced by the NL " + "generator (set automatically by the generator path; " + "human-authored definitions keep this false)." + ), + ) diff --git a/surfsense_backend/app/automations/schemas/definition/plan_step.py b/surfsense_backend/app/automations/schemas/definition/plan_step.py new file mode 100644 index 000000000..6898a0914 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/definition/plan_step.py @@ -0,0 +1,86 @@ +"""``PlanStep`` — one entry in the envelope's ``plan`` array.""" + +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field + + +class PlanStep(BaseModel): + """One step in an automation's sequential plan. + + Steps run in array order, no parallelism, no DAGs, no loops. The + ``when`` Jinja expression provides conditional skip; branching is + achieved by ``when`` clauses on multiple steps. For looping or + parallel work, the user routes through ``agent_task`` and lets the + agent reason about it. + + ``config`` is dispatched against the action registry at + validation time — its shape is determined by + ``ActionDefinition.config_schema`` for the ``action`` value. + + ``output_as`` binds the step's typed output into the template + namespace for later steps, e.g. ``output_as: 'summary'`` then + ``{{ summary.bullets }}`` in a downstream step's config. + """ + + model_config = ConfigDict(extra="forbid") + + step_id: str = Field( + ..., + description=( + "Unique-within-plan identifier. Used in run logs and as " + "the default for ``output_as`` when not provided." + ), + min_length=1, + ) + action: str = Field( + ..., + description=( + "Action-type discriminator (e.g., ``agent_task``). " + "Resolved against the action registry." + ), + min_length=1, + ) + when: str | None = Field( + default=None, + description=( + "Optional Jinja expression evaluated against the run " + "context. Step is skipped when the expression is " + "falsy." + ), + ) + config: dict[str, Any] = Field( + default_factory=dict, + description=( + "Action-type-specific config. Validated against the " + "registered ``ActionDefinition.config_schema`` for " + "``action`` at definition-save time. Jinja templates " + "inside config are rendered at step-execute time." + ), + ) + output_as: str | None = Field( + default=None, + description=( + "Name to bind the step output under for downstream " + "steps. Defaults to ``step_id`` when omitted." + ), + ) + max_retries: int | None = Field( + default=None, + ge=0, + description=( + "Per-step override of the automation-level ``max_retries``. " + "Omitted means inherit from execution block." + ), + ) + timeout_seconds: int | None = Field( + default=None, + gt=0, + description=( + "Per-step override of the automation-level " + "``timeout_seconds``. Omitted means inherit from " + "execution block." + ), + ) diff --git a/surfsense_backend/app/automations/schemas/definition/trigger_spec.py b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py new file mode 100644 index 000000000..827b0a315 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py @@ -0,0 +1,40 @@ +"""``TriggerSpec`` — one entry in the envelope's ``triggers`` array.""" + +from __future__ import annotations + +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field + + +class TriggerSpec(BaseModel): + """One trigger attached to an automation, as it appears in the definition. + + The envelope keeps ``config`` as an untyped JSON object on purpose + — the per-type config schemas live in + ``app.automations.schemas.triggers`` and are dispatched at + validation time by looking up ``type`` in the trigger registry. + + This mirrors the design's "definitions are pure data" principle: + the envelope describes shape, the registry resolves names to + behaviour. + """ + + model_config = ConfigDict(extra="forbid") + + type: str = Field( + ..., + description=( + "Trigger-type discriminator (e.g., ``schedule``, ``manual``). " + "Resolved against the trigger registry." + ), + min_length=1, + ) + config: dict[str, Any] = Field( + default_factory=dict, + description=( + "Trigger-type-specific config. Validated against the " + "registered ``TriggerDefinition.config_schema`` for " + "``type`` at definition-save time." + ), + ) diff --git a/surfsense_backend/app/automations/schemas/triggers/__init__.py b/surfsense_backend/app/automations/schemas/triggers/__init__.py index 2da765bc3..847c7443b 100644 --- a/surfsense_backend/app/automations/schemas/triggers/__init__.py +++ b/surfsense_backend/app/automations/schemas/triggers/__init__.py @@ -2,4 +2,10 @@ from __future__ import annotations -__all__: list[str] = [] +from .manual import ManualTriggerConfig +from .schedule import ScheduleTriggerConfig + +__all__ = [ + "ManualTriggerConfig", + "ScheduleTriggerConfig", +] diff --git a/surfsense_backend/app/automations/schemas/triggers/manual.py b/surfsense_backend/app/automations/schemas/triggers/manual.py new file mode 100644 index 000000000..6e04ba062 --- /dev/null +++ b/surfsense_backend/app/automations/schemas/triggers/manual.py @@ -0,0 +1,21 @@ +"""``ManualTriggerConfig`` — config for the ``manual`` trigger type (empty in v1).""" + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict + + +class ManualTriggerConfig(BaseModel): + """Config for the UI-driven ``manual`` trigger. + + Validated against ``AutomationTrigger.config`` whenever the + persisted ``type`` is ``manual``. v1 carries no configurable + fields — the "Run now" affordance simply fires this trigger with + an empty config object. The model exists so the registry dispatch + is uniform across all trigger types. + + Future versions may add fields here (e.g., a fixed prompt to + pre-fill the run dialog with) without breaking v1 payloads. + """ + + model_config = ConfigDict(extra="forbid") diff --git a/surfsense_backend/app/automations/schemas/triggers/schedule.py b/surfsense_backend/app/automations/schemas/triggers/schedule.py new file mode 100644 index 000000000..e7c20da3a --- /dev/null +++ b/surfsense_backend/app/automations/schemas/triggers/schedule.py @@ -0,0 +1,33 @@ +"""``ScheduleTriggerConfig`` — config for the ``schedule`` trigger type.""" + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, Field + + +class ScheduleTriggerConfig(BaseModel): + """Config for a cron-driven trigger. + + Validated against ``AutomationTrigger.config`` whenever the + persisted ``type`` is ``schedule``. The cron expression is + evaluated by Celery Beat's source; the timezone is an IANA name + (e.g., ``Africa/Kigali``) and is required so the user's cron is + unambiguous across DST boundaries. + """ + + model_config = ConfigDict(extra="forbid") + + cron: str = Field( + ..., + description=( + "Five-field cron expression. Minimum resolution is one " + "minute; the form editor warns when intervals tighter " + "than 15 minutes are used." + ), + examples=["0 9 * * 1-5"], + ) + timezone: str = Field( + ..., + description="IANA timezone name (e.g., 'Africa/Kigali', 'UTC').", + examples=["Africa/Kigali"], + ) From 7a96c0e29c2ff7521eab2289a618a95c0aa4a7c1 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 22:54:17 +0200 Subject: [PATCH 090/198] feat(automation): add empty Capability / Action / Trigger registries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three registries under app/automations/registries/, each as its own folder with the same SRP-per-file split (types.py for the dataclass, store.py for the in-memory dict + register/get/all functions). All three start empty; concrete entries land when the user signs off on which capabilities / actions / triggers to include (step 2). Capability (locked at v1-minimum five fields — see commit 2): - id, description, input_schema, output_schema, handler - CapabilityHandler = Callable[[dict[str, Any]], Awaitable[Any]] - Frozen, slotted dataclass (immutable post-registration). ActionDefinition (v1-trim of design plan §4): - type, name, description, config_schema, handler - Defers output_contract (handled per-step by agent_task's config.output_schema), uses_capabilities (no static analysis needed until >1 action ships), and produces_artifacts (deferred alongside the artifact pipeline). TriggerDefinition (declarative, no handler): - type, description, config_schema, payload_schema - No handler field — firing is a single dispatcher's responsibility, not a per-trigger one. store.py contract for all three: - register_*: idempotent at process startup, raises on duplicate - get_*: returns None on miss - all_*: returns a defensive copy of the registry dict Verified by an inline smoke test (10 checks): empty initial state, registration and lookup work, duplicates raise, frozen dataclasses reject mutation, snapshots are copies, handlers are awaitable. Isolation invariant audit: grep across the full app/automations/ tree shows only three app.* imports, all of them ``from app.db import BaseModel, TimestampMixin`` in the model files. No imports from app.agents.*, app.services.*, app.tasks.*, app.routes.*, or any other business-logic module. --- .../app/automations/registries/__init__.py | 38 +++++++++++++++- .../registries/actions/__init__.py | 11 ++++- .../automations/registries/actions/store.py | 33 ++++++++++++++ .../automations/registries/actions/types.py | 44 +++++++++++++++++++ .../registries/capabilities/__init__.py | 11 ++++- .../registries/capabilities/store.py | 40 +++++++++++++++++ .../registries/capabilities/types.py | 40 +++++++++++++++++ .../registries/triggers/__init__.py | 10 ++++- .../automations/registries/triggers/store.py | 33 ++++++++++++++ .../automations/registries/triggers/types.py | 35 +++++++++++++++ 10 files changed, 291 insertions(+), 4 deletions(-) create mode 100644 surfsense_backend/app/automations/registries/actions/store.py create mode 100644 surfsense_backend/app/automations/registries/actions/types.py create mode 100644 surfsense_backend/app/automations/registries/capabilities/store.py create mode 100644 surfsense_backend/app/automations/registries/capabilities/types.py create mode 100644 surfsense_backend/app/automations/registries/triggers/store.py create mode 100644 surfsense_backend/app/automations/registries/triggers/types.py diff --git a/surfsense_backend/app/automations/registries/__init__.py b/surfsense_backend/app/automations/registries/__init__.py index e7334cca8..47023f903 100644 --- a/surfsense_backend/app/automations/registries/__init__.py +++ b/surfsense_backend/app/automations/registries/__init__.py @@ -2,4 +2,40 @@ from __future__ import annotations -__all__: list[str] = [] +from .actions import ( + ActionDefinition, + ActionHandler, + all_actions, + get_action, + register_action, +) +from .capabilities import ( + Capability, + CapabilityHandler, + all_capabilities, + get_capability, + register_capability, +) +from .triggers import ( + TriggerDefinition, + all_triggers, + get_trigger, + register_trigger, +) + +__all__ = [ + "ActionDefinition", + "ActionHandler", + "Capability", + "CapabilityHandler", + "TriggerDefinition", + "all_actions", + "all_capabilities", + "all_triggers", + "get_action", + "get_capability", + "get_trigger", + "register_action", + "register_capability", + "register_trigger", +] diff --git a/surfsense_backend/app/automations/registries/actions/__init__.py b/surfsense_backend/app/automations/registries/actions/__init__.py index 6b19b7091..c6b550096 100644 --- a/surfsense_backend/app/automations/registries/actions/__init__.py +++ b/surfsense_backend/app/automations/registries/actions/__init__.py @@ -2,4 +2,13 @@ from __future__ import annotations -__all__: list[str] = [] +from .store import all_actions, get_action, register_action +from .types import ActionDefinition, ActionHandler + +__all__ = [ + "ActionDefinition", + "ActionHandler", + "all_actions", + "get_action", + "register_action", +] diff --git a/surfsense_backend/app/automations/registries/actions/store.py b/surfsense_backend/app/automations/registries/actions/store.py new file mode 100644 index 000000000..720243b83 --- /dev/null +++ b/surfsense_backend/app/automations/registries/actions/store.py @@ -0,0 +1,33 @@ +"""Action registry: in-memory dict + ``register_action`` API.""" + +from __future__ import annotations + +from .types import ActionDefinition + +_REGISTRY: dict[str, ActionDefinition] = {} + + +def register_action(action: ActionDefinition) -> None: + """Add an action to the in-memory registry. + + Raises ``ValueError`` on duplicate ``type`` — registration runs + once per process, so a duplicate is always a bug. + """ + + if action.type in _REGISTRY: + raise ValueError( + f"Action already registered: {action.type!r}" + ) + _REGISTRY[action.type] = action + + +def get_action(action_type: str) -> ActionDefinition | None: + """Look up one action by type. Returns ``None`` on miss.""" + + return _REGISTRY.get(action_type) + + +def all_actions() -> dict[str, ActionDefinition]: + """Snapshot of the registry as a defensive copy.""" + + return dict(_REGISTRY) diff --git a/surfsense_backend/app/automations/registries/actions/types.py b/surfsense_backend/app/automations/registries/actions/types.py new file mode 100644 index 000000000..2ab2906b1 --- /dev/null +++ b/surfsense_backend/app/automations/registries/actions/types.py @@ -0,0 +1,44 @@ +"""``ActionDefinition`` dataclass — the v1-minimum action shape.""" + +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Any + +ActionHandler = Callable[[dict[str, Any]], Awaitable[Any]] +"""The signature every action handler must satisfy. + +Identical in shape to ``CapabilityHandler`` — both receive a +caller-validated input dict and return an arbitrary output. The +distinction is purely architectural: capabilities are the low-level +"what SurfSense can do" surface, actions are the user-facing +building blocks composed into a plan. +""" + + +@dataclass(frozen=True, slots=True) +class ActionDefinition: + """A user-facing step type the plan editor can compose. + + v1 trims the dataclass to the five fields necessary for + registry dispatch and form rendering. The full design (§4) + includes ``output_contract``, ``uses_capabilities``, and + ``produces_artifacts``; all three are deferred until a consumer + feature requires them: + + - ``output_contract`` — the loose ``agent_task`` action declares + its output shape per-step via ``config.output_schema``, so the + action-level contract is not needed in v1. + - ``uses_capabilities`` — would let the NL generator do static + analysis of which capabilities each action invokes; deferred + because v1 ships a single (``agent_task``) action. + - ``produces_artifacts`` — deferred alongside the artifact + pipeline (see §13 decision 26). + """ + + type: str + name: str + description: str + config_schema: dict[str, Any] + handler: ActionHandler diff --git a/surfsense_backend/app/automations/registries/capabilities/__init__.py b/surfsense_backend/app/automations/registries/capabilities/__init__.py index 77f9f88b7..6fa2c8246 100644 --- a/surfsense_backend/app/automations/registries/capabilities/__init__.py +++ b/surfsense_backend/app/automations/registries/capabilities/__init__.py @@ -2,4 +2,13 @@ from __future__ import annotations -__all__: list[str] = [] +from .store import all_capabilities, get_capability, register_capability +from .types import Capability, CapabilityHandler + +__all__ = [ + "Capability", + "CapabilityHandler", + "all_capabilities", + "get_capability", + "register_capability", +] diff --git a/surfsense_backend/app/automations/registries/capabilities/store.py b/surfsense_backend/app/automations/registries/capabilities/store.py new file mode 100644 index 000000000..3c8822d76 --- /dev/null +++ b/surfsense_backend/app/automations/registries/capabilities/store.py @@ -0,0 +1,40 @@ +"""Capability registry: in-memory dict + ``register_capability`` API.""" + +from __future__ import annotations + +from .types import Capability + +_REGISTRY: dict[str, Capability] = {} + + +def register_capability(capability: Capability) -> None: + """Add a capability to the in-memory registry. + + Raises ``ValueError`` on duplicate ``id`` — registration is + idempotent only at the module level (a module's + ``register_capability`` call runs once per process), so a + duplicate is always a bug. + """ + + if capability.id in _REGISTRY: + raise ValueError( + f"Capability already registered: {capability.id!r}" + ) + _REGISTRY[capability.id] = capability + + +def get_capability(capability_id: str) -> Capability | None: + """Look up one capability by id. Returns ``None`` on miss.""" + + return _REGISTRY.get(capability_id) + + +def all_capabilities() -> dict[str, Capability]: + """Snapshot of the registry as a defensive copy. + + Returned dict is safe to iterate while other code calls + ``register_capability`` (which v1 never does post-startup, but + the contract holds anyway). + """ + + return dict(_REGISTRY) diff --git a/surfsense_backend/app/automations/registries/capabilities/types.py b/surfsense_backend/app/automations/registries/capabilities/types.py new file mode 100644 index 000000000..001f26ac1 --- /dev/null +++ b/surfsense_backend/app/automations/registries/capabilities/types.py @@ -0,0 +1,40 @@ +"""``Capability`` dataclass — the v1-minimum five-field shape.""" + +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Any + +CapabilityHandler = Callable[[dict[str, Any]], Awaitable[Any]] +"""The signature every capability handler must satisfy. + +The handler is a closure that already holds whatever runtime context +it needs (DB session, search-space scope, logger, etc.). The +registry only passes through the caller's input dict — the same dict +that was validated against ``input_schema``. +""" + + +@dataclass(frozen=True, slots=True) +class Capability: + """The unit of "what SurfSense can do," consumed by every layer. + + v1 keeps the dataclass to exactly five fields. Earlier drafts + considered ``name``, ``required_credentials``, ``side_effects``, + ``expected_duration_seconds``, and ``cost_estimate``; every one + of those has been removed until a concrete consumer feature + requires it (see ``automation-design-plan.md`` §3, decision v1). + + The handler is a ready-to-call function. It does not receive a + context argument — context is bound at registration time by the + factory that builds the closure (so a capability returned to an + agent's tool list looks identical to one returned to an + automation's action runtime). + """ + + id: str + description: str + input_schema: dict[str, Any] + output_schema: dict[str, Any] + handler: CapabilityHandler diff --git a/surfsense_backend/app/automations/registries/triggers/__init__.py b/surfsense_backend/app/automations/registries/triggers/__init__.py index bc795b61a..f69c6fe8d 100644 --- a/surfsense_backend/app/automations/registries/triggers/__init__.py +++ b/surfsense_backend/app/automations/registries/triggers/__init__.py @@ -2,4 +2,12 @@ from __future__ import annotations -__all__: list[str] = [] +from .store import all_triggers, get_trigger, register_trigger +from .types import TriggerDefinition + +__all__ = [ + "TriggerDefinition", + "all_triggers", + "get_trigger", + "register_trigger", +] diff --git a/surfsense_backend/app/automations/registries/triggers/store.py b/surfsense_backend/app/automations/registries/triggers/store.py new file mode 100644 index 000000000..0a5fbdadb --- /dev/null +++ b/surfsense_backend/app/automations/registries/triggers/store.py @@ -0,0 +1,33 @@ +"""Trigger registry: in-memory dict + ``register_trigger`` API.""" + +from __future__ import annotations + +from .types import TriggerDefinition + +_REGISTRY: dict[str, TriggerDefinition] = {} + + +def register_trigger(trigger: TriggerDefinition) -> None: + """Add a trigger to the in-memory registry. + + Raises ``ValueError`` on duplicate ``type`` — registration runs + once per process, so a duplicate is always a bug. + """ + + if trigger.type in _REGISTRY: + raise ValueError( + f"Trigger already registered: {trigger.type!r}" + ) + _REGISTRY[trigger.type] = trigger + + +def get_trigger(trigger_type: str) -> TriggerDefinition | None: + """Look up one trigger by type. Returns ``None`` on miss.""" + + return _REGISTRY.get(trigger_type) + + +def all_triggers() -> dict[str, TriggerDefinition]: + """Snapshot of the registry as a defensive copy.""" + + return dict(_REGISTRY) diff --git a/surfsense_backend/app/automations/registries/triggers/types.py b/surfsense_backend/app/automations/registries/triggers/types.py new file mode 100644 index 000000000..256944823 --- /dev/null +++ b/surfsense_backend/app/automations/registries/triggers/types.py @@ -0,0 +1,35 @@ +"""``TriggerDefinition`` dataclass — declarative trigger metadata, no handler.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + + +@dataclass(frozen=True, slots=True) +class TriggerDefinition: + """A trigger type the dispatcher knows how to fire. + + Triggers are purely declarative: the dispatcher (a single + process-wide component, not a per-type handler) reads the + ``automation_triggers`` table and decides when each row should + fire. The trigger's job here is to declare its input/output + contract: + + - ``config_schema``: JSON Schema for the persisted + ``AutomationTrigger.config`` — used by the form editor and + validated on save. + - ``payload_schema``: JSON Schema for the payload the dispatcher + will deliver to the executor at fire time (e.g., a schedule + trigger emits ``fired_at`` / ``scheduled_for`` / + ``last_fired_at``). + + No ``handler`` field — firing is a dispatcher responsibility, + not a per-trigger one. This keeps the dispatcher single and + leaves trigger types as pure metadata. + """ + + type: str + description: str + config_schema: dict[str, Any] + payload_schema: dict[str, Any] From f0e00bd3ee0ec5366ea6cea039723a68d08b9a9e Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Tue, 26 May 2026 23:01:22 +0200 Subject: [PATCH 091/198] chore(automation): trim docstrings to intent only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cut the docstrings and Field(description=...) text across the entire automations/ tree down to single-line intent statements, matching the multi_agent_chat conciseness style: - Module docstrings: one line stating what the file is. - Class docstrings: deleted when the class name + module docstring already cover intent; kept only where they add a constraint or rationale not visible in the signature. - Pydantic Field descriptions: short noun phrases / clauses, not full sentences. Reasoning that belonged in the design plan moved out of the code. - Enum values: per-value docstrings replaced with terse inline comments where the meaning isn't obvious from the name. Behaviour is unchanged. The same 33 files, same public surface, same imports — verified by re-running the 10-point registry smoke test and the 8-point schema round-trip / constraint suite from commits 9 and 10. LOC: 1180 → 691 (-42%). --- surfsense_backend/app/automations/__init__.py | 2 +- .../app/automations/persistence/__init__.py | 2 +- .../automations/persistence/enums/__init__.py | 2 +- .../persistence/enums/automation_status.py | 15 +--- .../persistence/enums/run_status.py | 16 +--- .../persistence/enums/trigger_type.py | 13 +-- .../persistence/models/__init__.py | 2 +- .../persistence/models/automation.py | 18 +--- .../app/automations/persistence/models/run.py | 18 +--- .../automations/persistence/models/trigger.py | 15 +--- .../app/automations/registries/__init__.py | 2 +- .../registries/actions/__init__.py | 2 +- .../automations/registries/actions/store.py | 18 +--- .../automations/registries/actions/types.py | 28 +------ .../registries/capabilities/__init__.py | 2 +- .../registries/capabilities/store.py | 25 +----- .../registries/capabilities/types.py | 24 +----- .../registries/triggers/__init__.py | 2 +- .../automations/registries/triggers/store.py | 18 +--- .../automations/registries/triggers/types.py | 23 +---- .../app/automations/schemas/__init__.py | 2 +- .../automations/schemas/actions/__init__.py | 2 +- .../automations/schemas/actions/agent_task.py | 49 ++--------- .../schemas/definition/__init__.py | 2 +- .../schemas/definition/envelope.py | 83 +++---------------- .../schemas/definition/execution.py | 65 ++------------- .../automations/schemas/definition/inputs.py | 26 +----- .../schemas/definition/metadata.py | 28 +------ .../schemas/definition/plan_step.py | 74 ++--------------- .../schemas/definition/trigger_spec.py | 29 +------ .../automations/schemas/triggers/__init__.py | 2 +- .../automations/schemas/triggers/manual.py | 14 +--- .../automations/schemas/triggers/schedule.py | 25 +----- 33 files changed, 80 insertions(+), 568 deletions(-) diff --git a/surfsense_backend/app/automations/__init__.py b/surfsense_backend/app/automations/__init__.py index edb7891ea..a4ce8ecc9 100644 --- a/surfsense_backend/app/automations/__init__.py +++ b/surfsense_backend/app/automations/__init__.py @@ -1,4 +1,4 @@ -"""Automations: scheduled / triggered runs of capabilities — see automation-design-plan.md.""" +"""Automations engine — see automation-design-plan.md.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/persistence/__init__.py b/surfsense_backend/app/automations/persistence/__init__.py index 265742a85..4c1ea3423 100644 --- a/surfsense_backend/app/automations/persistence/__init__.py +++ b/surfsense_backend/app/automations/persistence/__init__.py @@ -1,4 +1,4 @@ -"""Persistence layer: SQLAlchemy enums under ``enums/`` and models under ``models/``.""" +"""SQLAlchemy models and enums for the automation tables.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/persistence/enums/__init__.py b/surfsense_backend/app/automations/persistence/enums/__init__.py index cf9e7dd1b..6c2cfcf1f 100644 --- a/surfsense_backend/app/automations/persistence/enums/__init__.py +++ b/surfsense_backend/app/automations/persistence/enums/__init__.py @@ -1,4 +1,4 @@ -"""SQLAlchemy / Python enums backing the three automation tables.""" +"""Enums for the automation tables.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/persistence/enums/automation_status.py b/surfsense_backend/app/automations/persistence/enums/automation_status.py index 3f2ca9621..aff6f4683 100644 --- a/surfsense_backend/app/automations/persistence/enums/automation_status.py +++ b/surfsense_backend/app/automations/persistence/enums/automation_status.py @@ -1,4 +1,4 @@ -"""``AutomationStatus`` — lifecycle of a stored automation definition.""" +"""Automation lifecycle status.""" from __future__ import annotations @@ -6,13 +6,6 @@ from enum import StrEnum class AutomationStatus(StrEnum): - """Status of an automation in the registry. - - ``active`` — eligible to fire from its triggers. - ``paused`` — definition retained, triggers do not fire. - ``archived`` — kept for run history only; no edits, no fires. - """ - - ACTIVE = "active" - PAUSED = "paused" - ARCHIVED = "archived" + ACTIVE = "active" # eligible to fire + PAUSED = "paused" # kept, but triggers don't fire + ARCHIVED = "archived" # read-only history diff --git a/surfsense_backend/app/automations/persistence/enums/run_status.py b/surfsense_backend/app/automations/persistence/enums/run_status.py index 0f619bd82..64dcd49e8 100644 --- a/surfsense_backend/app/automations/persistence/enums/run_status.py +++ b/surfsense_backend/app/automations/persistence/enums/run_status.py @@ -1,4 +1,4 @@ -"""``RunStatus`` — the state machine of a single ``AutomationRun``.""" +"""AutomationRun state machine: pending → running → (succeeded|failed|cancelled|timed_out).""" from __future__ import annotations @@ -6,20 +6,6 @@ from enum import StrEnum class RunStatus(StrEnum): - """Lifecycle states of an ``AutomationRun`` row. - - Transitions are linear with three terminal branches: - - pending → running → (succeeded | failed | cancelled | timed_out) - - ``pending`` — row created, executor task enqueued, work not started. - ``running`` — executor has picked up the run. - ``succeeded`` — terminal: plan completed without error. - ``failed`` — terminal: at least one step raised an unrecoverable error. - ``cancelled`` — terminal: caller asked for cancellation. - ``timed_out`` — terminal: run exceeded its configured timeout. - """ - PENDING = "pending" RUNNING = "running" SUCCEEDED = "succeeded" diff --git a/surfsense_backend/app/automations/persistence/enums/trigger_type.py b/surfsense_backend/app/automations/persistence/enums/trigger_type.py index eb06fe773..8318bfdee 100644 --- a/surfsense_backend/app/automations/persistence/enums/trigger_type.py +++ b/surfsense_backend/app/automations/persistence/enums/trigger_type.py @@ -1,4 +1,4 @@ -"""``TriggerType`` — the trigger-kind discriminator (v1 = schedule, manual).""" +"""Trigger-kind discriminator. v1: schedule | manual; webhook/event in Phase 2/3.""" from __future__ import annotations @@ -6,16 +6,5 @@ from enum import StrEnum class TriggerType(StrEnum): - """Kind of trigger an ``AutomationTrigger`` row represents. - - v1 ships two kinds: - - ``schedule`` — fires on a cron expression managed by Celery Beat. - ``manual`` — fires on demand from the UI's "Run now" affordance. - - ``webhook`` and ``event`` are deferred to Phase 2 and Phase 3 - respectively; adding them is an enum-value extension only. - """ - SCHEDULE = "schedule" MANUAL = "manual" diff --git a/surfsense_backend/app/automations/persistence/models/__init__.py b/surfsense_backend/app/automations/persistence/models/__init__.py index 4aca02a03..4bc023ea3 100644 --- a/surfsense_backend/app/automations/persistence/models/__init__.py +++ b/surfsense_backend/app/automations/persistence/models/__init__.py @@ -1,4 +1,4 @@ -"""SQLAlchemy models: one file per table (``automation.py``, ``trigger.py``, ``run.py``).""" +"""SQLAlchemy models, one per table.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/persistence/models/automation.py b/surfsense_backend/app/automations/persistence/models/automation.py index fc4a1ed93..637fd2282 100644 --- a/surfsense_backend/app/automations/persistence/models/automation.py +++ b/surfsense_backend/app/automations/persistence/models/automation.py @@ -1,4 +1,4 @@ -"""``Automation`` table — the editable, versioned automation definition.""" +"""``automations`` table — editable, versioned automation definition.""" from __future__ import annotations @@ -21,15 +21,6 @@ from ..enums.automation_status import AutomationStatus class Automation(BaseModel, TimestampMixin): - """The editable, versioned spec a user authors. - - The ``definition`` JSON is what the user (or the NL generator) writes - and edits. Each save bumps ``version`` by one; the previous JSON is - not kept in this row — version history is reconstructed from the - ``definition_snapshot`` column on every ``AutomationRun`` that fired - against a given version. - """ - __tablename__ = "automations" search_space_id = Column( @@ -59,12 +50,7 @@ class Automation(BaseModel, TimestampMixin): definition = Column(JSONB, nullable=False) - version = Column( - Integer, - nullable=False, - default=1, - server_default="1", - ) + version = Column(Integer, nullable=False, default=1, server_default="1") updated_at = Column( TIMESTAMP(timezone=True), diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index 5c6ec93ec..9291e5da0 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -1,4 +1,4 @@ -"""``AutomationRun`` table — the immutable per-fire execution record.""" +"""``automation_runs`` table — immutable per-fire execution record.""" from __future__ import annotations @@ -18,16 +18,6 @@ from ..enums.run_status import RunStatus class AutomationRun(BaseModel, TimestampMixin): - """One execution of an automation. - - Every fire of any trigger inserts exactly one row here. The row is - immutable from the user's perspective — the executor only updates - ``status``, ``step_results``, ``output``, ``artifacts``, ``error``, - ``started_at``, ``finished_at`` as the run progresses; the - ``definition_snapshot`` is locked at fire time so the user can always - see exactly what code path executed for any historical run. - """ - __tablename__ = "automation_runs" automation_id = Column( @@ -52,18 +42,14 @@ class AutomationRun(BaseModel, TimestampMixin): index=True, ) + # locked at fire time so historical runs always show the exact code path definition_snapshot = Column(JSONB, nullable=False) trigger_payload = Column(JSONB, nullable=True) - resolved_inputs = Column(JSONB, nullable=False, server_default="{}") - step_results = Column(JSONB, nullable=False, server_default="[]") - output = Column(JSONB, nullable=True) - artifacts = Column(JSONB, nullable=False, server_default="[]") - error = Column(JSONB, nullable=True) started_at = Column(TIMESTAMP(timezone=True), nullable=True) diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py index 3173770d6..8dab48a6b 100644 --- a/surfsense_backend/app/automations/persistence/models/trigger.py +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -1,4 +1,4 @@ -"""``AutomationTrigger`` table — one row per (automation, trigger-instance) pair.""" +"""``automation_triggers`` table — one row per (automation, trigger-instance) pair.""" from __future__ import annotations @@ -18,14 +18,6 @@ from ..enums.trigger_type import TriggerType class AutomationTrigger(BaseModel, TimestampMixin): - """One trigger attached to an automation. - - An automation may have multiple triggers — e.g. a ``schedule`` trigger - for the autonomous path and a ``manual`` trigger backing the UI's - "Run now" affordance. Each trigger's ``config`` is validated against - the registered ``TriggerDefinition.config_schema`` for its ``type``. - """ - __tablename__ = "automation_triggers" automation_id = Column( @@ -51,7 +43,4 @@ class AutomationTrigger(BaseModel, TimestampMixin): index=True, ) - last_fired_at = Column( - TIMESTAMP(timezone=True), - nullable=True, - ) + last_fired_at = Column(TIMESTAMP(timezone=True), nullable=True) diff --git a/surfsense_backend/app/automations/registries/__init__.py b/surfsense_backend/app/automations/registries/__init__.py index 47023f903..a97595ced 100644 --- a/surfsense_backend/app/automations/registries/__init__.py +++ b/surfsense_backend/app/automations/registries/__init__.py @@ -1,4 +1,4 @@ -"""Three registries — ``capabilities/``, ``actions/``, ``triggers/`` — populated at import time.""" +"""Capability, action, and trigger registries — populated at process startup.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/registries/actions/__init__.py b/surfsense_backend/app/automations/registries/actions/__init__.py index c6b550096..1bb3ae9cc 100644 --- a/surfsense_backend/app/automations/registries/actions/__init__.py +++ b/surfsense_backend/app/automations/registries/actions/__init__.py @@ -1,4 +1,4 @@ -"""Action registry: ``types.py`` (dataclass), ``store.py`` (dict + register fn).""" +"""Action registry.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/registries/actions/store.py b/surfsense_backend/app/automations/registries/actions/store.py index 720243b83..eff66c4c7 100644 --- a/surfsense_backend/app/automations/registries/actions/store.py +++ b/surfsense_backend/app/automations/registries/actions/store.py @@ -1,4 +1,4 @@ -"""Action registry: in-memory dict + ``register_action`` API.""" +"""In-memory action registry. Populated once at process startup.""" from __future__ import annotations @@ -8,26 +8,16 @@ _REGISTRY: dict[str, ActionDefinition] = {} def register_action(action: ActionDefinition) -> None: - """Add an action to the in-memory registry. - - Raises ``ValueError`` on duplicate ``type`` — registration runs - once per process, so a duplicate is always a bug. - """ - + """Register an action. Raises on duplicate type.""" if action.type in _REGISTRY: - raise ValueError( - f"Action already registered: {action.type!r}" - ) + raise ValueError(f"Action already registered: {action.type!r}") _REGISTRY[action.type] = action def get_action(action_type: str) -> ActionDefinition | None: - """Look up one action by type. Returns ``None`` on miss.""" - return _REGISTRY.get(action_type) def all_actions() -> dict[str, ActionDefinition]: - """Snapshot of the registry as a defensive copy.""" - + """Defensive snapshot of the registry.""" return dict(_REGISTRY) diff --git a/surfsense_backend/app/automations/registries/actions/types.py b/surfsense_backend/app/automations/registries/actions/types.py index 2ab2906b1..13c826c66 100644 --- a/surfsense_backend/app/automations/registries/actions/types.py +++ b/surfsense_backend/app/automations/registries/actions/types.py @@ -1,4 +1,4 @@ -"""``ActionDefinition`` dataclass — the v1-minimum action shape.""" +"""``ActionDefinition`` dataclass and handler signature.""" from __future__ import annotations @@ -7,36 +7,10 @@ from dataclasses import dataclass from typing import Any ActionHandler = Callable[[dict[str, Any]], Awaitable[Any]] -"""The signature every action handler must satisfy. - -Identical in shape to ``CapabilityHandler`` — both receive a -caller-validated input dict and return an arbitrary output. The -distinction is purely architectural: capabilities are the low-level -"what SurfSense can do" surface, actions are the user-facing -building blocks composed into a plan. -""" @dataclass(frozen=True, slots=True) class ActionDefinition: - """A user-facing step type the plan editor can compose. - - v1 trims the dataclass to the five fields necessary for - registry dispatch and form rendering. The full design (§4) - includes ``output_contract``, ``uses_capabilities``, and - ``produces_artifacts``; all three are deferred until a consumer - feature requires them: - - - ``output_contract`` — the loose ``agent_task`` action declares - its output shape per-step via ``config.output_schema``, so the - action-level contract is not needed in v1. - - ``uses_capabilities`` — would let the NL generator do static - analysis of which capabilities each action invokes; deferred - because v1 ships a single (``agent_task``) action. - - ``produces_artifacts`` — deferred alongside the artifact - pipeline (see §13 decision 26). - """ - type: str name: str description: str diff --git a/surfsense_backend/app/automations/registries/capabilities/__init__.py b/surfsense_backend/app/automations/registries/capabilities/__init__.py index 6fa2c8246..213303fc0 100644 --- a/surfsense_backend/app/automations/registries/capabilities/__init__.py +++ b/surfsense_backend/app/automations/registries/capabilities/__init__.py @@ -1,4 +1,4 @@ -"""Capability registry: ``types.py`` (dataclass), ``store.py`` (dict + register fn).""" +"""Capability registry.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/registries/capabilities/store.py b/surfsense_backend/app/automations/registries/capabilities/store.py index 3c8822d76..4d87abe47 100644 --- a/surfsense_backend/app/automations/registries/capabilities/store.py +++ b/surfsense_backend/app/automations/registries/capabilities/store.py @@ -1,4 +1,4 @@ -"""Capability registry: in-memory dict + ``register_capability`` API.""" +"""In-memory capability registry. Populated once at process startup.""" from __future__ import annotations @@ -8,33 +8,16 @@ _REGISTRY: dict[str, Capability] = {} def register_capability(capability: Capability) -> None: - """Add a capability to the in-memory registry. - - Raises ``ValueError`` on duplicate ``id`` — registration is - idempotent only at the module level (a module's - ``register_capability`` call runs once per process), so a - duplicate is always a bug. - """ - + """Register a capability. Raises on duplicate id.""" if capability.id in _REGISTRY: - raise ValueError( - f"Capability already registered: {capability.id!r}" - ) + raise ValueError(f"Capability already registered: {capability.id!r}") _REGISTRY[capability.id] = capability def get_capability(capability_id: str) -> Capability | None: - """Look up one capability by id. Returns ``None`` on miss.""" - return _REGISTRY.get(capability_id) def all_capabilities() -> dict[str, Capability]: - """Snapshot of the registry as a defensive copy. - - Returned dict is safe to iterate while other code calls - ``register_capability`` (which v1 never does post-startup, but - the contract holds anyway). - """ - + """Defensive snapshot of the registry.""" return dict(_REGISTRY) diff --git a/surfsense_backend/app/automations/registries/capabilities/types.py b/surfsense_backend/app/automations/registries/capabilities/types.py index 001f26ac1..2759bc809 100644 --- a/surfsense_backend/app/automations/registries/capabilities/types.py +++ b/surfsense_backend/app/automations/registries/capabilities/types.py @@ -1,4 +1,4 @@ -"""``Capability`` dataclass — the v1-minimum five-field shape.""" +"""``Capability`` dataclass and handler signature. Locked at five fields for v1.""" from __future__ import annotations @@ -7,32 +7,10 @@ from dataclasses import dataclass from typing import Any CapabilityHandler = Callable[[dict[str, Any]], Awaitable[Any]] -"""The signature every capability handler must satisfy. - -The handler is a closure that already holds whatever runtime context -it needs (DB session, search-space scope, logger, etc.). The -registry only passes through the caller's input dict — the same dict -that was validated against ``input_schema``. -""" @dataclass(frozen=True, slots=True) class Capability: - """The unit of "what SurfSense can do," consumed by every layer. - - v1 keeps the dataclass to exactly five fields. Earlier drafts - considered ``name``, ``required_credentials``, ``side_effects``, - ``expected_duration_seconds``, and ``cost_estimate``; every one - of those has been removed until a concrete consumer feature - requires it (see ``automation-design-plan.md`` §3, decision v1). - - The handler is a ready-to-call function. It does not receive a - context argument — context is bound at registration time by the - factory that builds the closure (so a capability returned to an - agent's tool list looks identical to one returned to an - automation's action runtime). - """ - id: str description: str input_schema: dict[str, Any] diff --git a/surfsense_backend/app/automations/registries/triggers/__init__.py b/surfsense_backend/app/automations/registries/triggers/__init__.py index f69c6fe8d..843da5e70 100644 --- a/surfsense_backend/app/automations/registries/triggers/__init__.py +++ b/surfsense_backend/app/automations/registries/triggers/__init__.py @@ -1,4 +1,4 @@ -"""Trigger registry: ``types.py`` (dataclass), ``store.py`` (dict + register fn).""" +"""Trigger registry.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/registries/triggers/store.py b/surfsense_backend/app/automations/registries/triggers/store.py index 0a5fbdadb..af0fafac7 100644 --- a/surfsense_backend/app/automations/registries/triggers/store.py +++ b/surfsense_backend/app/automations/registries/triggers/store.py @@ -1,4 +1,4 @@ -"""Trigger registry: in-memory dict + ``register_trigger`` API.""" +"""In-memory trigger registry. Populated once at process startup.""" from __future__ import annotations @@ -8,26 +8,16 @@ _REGISTRY: dict[str, TriggerDefinition] = {} def register_trigger(trigger: TriggerDefinition) -> None: - """Add a trigger to the in-memory registry. - - Raises ``ValueError`` on duplicate ``type`` — registration runs - once per process, so a duplicate is always a bug. - """ - + """Register a trigger. Raises on duplicate type.""" if trigger.type in _REGISTRY: - raise ValueError( - f"Trigger already registered: {trigger.type!r}" - ) + raise ValueError(f"Trigger already registered: {trigger.type!r}") _REGISTRY[trigger.type] = trigger def get_trigger(trigger_type: str) -> TriggerDefinition | None: - """Look up one trigger by type. Returns ``None`` on miss.""" - return _REGISTRY.get(trigger_type) def all_triggers() -> dict[str, TriggerDefinition]: - """Snapshot of the registry as a defensive copy.""" - + """Defensive snapshot of the registry.""" return dict(_REGISTRY) diff --git a/surfsense_backend/app/automations/registries/triggers/types.py b/surfsense_backend/app/automations/registries/triggers/types.py index 256944823..5da081343 100644 --- a/surfsense_backend/app/automations/registries/triggers/types.py +++ b/surfsense_backend/app/automations/registries/triggers/types.py @@ -1,4 +1,4 @@ -"""``TriggerDefinition`` dataclass — declarative trigger metadata, no handler.""" +"""``TriggerDefinition`` dataclass. Declarative; firing is the dispatcher's job.""" from __future__ import annotations @@ -8,27 +8,6 @@ from typing import Any @dataclass(frozen=True, slots=True) class TriggerDefinition: - """A trigger type the dispatcher knows how to fire. - - Triggers are purely declarative: the dispatcher (a single - process-wide component, not a per-type handler) reads the - ``automation_triggers`` table and decides when each row should - fire. The trigger's job here is to declare its input/output - contract: - - - ``config_schema``: JSON Schema for the persisted - ``AutomationTrigger.config`` — used by the form editor and - validated on save. - - ``payload_schema``: JSON Schema for the payload the dispatcher - will deliver to the executor at fire time (e.g., a schedule - trigger emits ``fired_at`` / ``scheduled_for`` / - ``last_fired_at``). - - No ``handler`` field — firing is a dispatcher responsibility, - not a per-trigger one. This keeps the dispatcher single and - leaves trigger types as pure metadata. - """ - type: str description: str config_schema: dict[str, Any] diff --git a/surfsense_backend/app/automations/schemas/__init__.py b/surfsense_backend/app/automations/schemas/__init__.py index 83a95a2a8..23f0232fb 100644 --- a/surfsense_backend/app/automations/schemas/__init__.py +++ b/surfsense_backend/app/automations/schemas/__init__.py @@ -1,4 +1,4 @@ -"""Pydantic schemas: definition envelope, trigger configs, action configs.""" +"""Pydantic schemas for the automation definition and per-type configs.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/schemas/actions/__init__.py b/surfsense_backend/app/automations/schemas/actions/__init__.py index 17c257562..4149206d7 100644 --- a/surfsense_backend/app/automations/schemas/actions/__init__.py +++ b/surfsense_backend/app/automations/schemas/actions/__init__.py @@ -1,4 +1,4 @@ -"""Per-action config schemas: one file per action type registered in v1.""" +"""Per-action config schemas, one per action type.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/schemas/actions/agent_task.py b/surfsense_backend/app/automations/schemas/actions/agent_task.py index 74e41166a..fe9d5fcef 100644 --- a/surfsense_backend/app/automations/schemas/actions/agent_task.py +++ b/surfsense_backend/app/automations/schemas/actions/agent_task.py @@ -8,59 +8,20 @@ from pydantic import BaseModel, ConfigDict, Field class AgentTaskActionConfig(BaseModel): - """Config for an ``agent_task`` plan step. - - Validated against ``PlanStep.config`` whenever the step's - ``action`` is ``agent_task``. The step instructs the LangGraph - Deep Agent runtime to: - - 1. Receive ``prompt`` (with all preceding-step outputs and inputs - already rendered by the template engine). - 2. Run the agent with access to *exactly* the capabilities named - in ``tools`` — nothing else from the registry is visible to - this agent invocation. - 3. Return a JSON object matching ``output_schema`` (recommended; - the executor validates and re-prompts on mismatch). - - ``output_schema`` is the design's "dynamic output contract" — - instead of locking the output shape on the ActionDefinition (as - tight actions do), the user declares the shape they want for this - specific step, and the agent has to match it. - """ + """Run a LangGraph Deep Agent restricted to a scoped capability list.""" model_config = ConfigDict(extra="forbid") - prompt: str = Field( - ..., - description=( - "The task prompt rendered through the Jinja sandbox. May " - "reference automation inputs and prior-step outputs." - ), - min_length=1, - ) + prompt: str = Field(..., min_length=1, description="Task prompt; Jinja-rendered.") tools: list[str] = Field( default_factory=list, - description=( - "Allowlist of capability IDs the agent may call (e.g., " - "'search_space.query'). Empty list = no tool access; the " - "agent must answer from the prompt alone." - ), + description="Capability IDs the agent may call. Empty = no tool access.", ) model: str | None = Field( default=None, - description=( - "Optional LiteLLM model identifier (e.g., " - "'anthropic/claude-sonnet-4-7'). Omitted means the " - "automation falls back to the search space's default " - "agent_llm_id." - ), + description="LiteLLM model id. Defaults to the search space's agent_llm_id.", ) output_schema: dict[str, Any] | None = Field( default=None, - description=( - "Optional JSON Schema declaring the shape the agent must " - "return. Strongly recommended; the editor warns when " - "missing. Validated by the executor before binding to " - "``output_as``." - ), + description="JSON Schema the agent must return. Recommended.", ) diff --git a/surfsense_backend/app/automations/schemas/definition/__init__.py b/surfsense_backend/app/automations/schemas/definition/__init__.py index 14040c20a..838e72f86 100644 --- a/surfsense_backend/app/automations/schemas/definition/__init__.py +++ b/surfsense_backend/app/automations/schemas/definition/__init__.py @@ -1,4 +1,4 @@ -"""Automation definition envelope: the editable structured spec users author and run.""" +"""Automation definition envelope and its building blocks.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/schemas/definition/envelope.py b/surfsense_backend/app/automations/schemas/definition/envelope.py index ccf4c53df..ccd76d612 100644 --- a/surfsense_backend/app/automations/schemas/definition/envelope.py +++ b/surfsense_backend/app/automations/schemas/definition/envelope.py @@ -1,4 +1,4 @@ -"""``AutomationDefinition`` — the top-level envelope persisted in ``automations.definition``.""" +"""``AutomationDefinition`` — top-level envelope persisted in ``automations.definition``.""" from __future__ import annotations @@ -12,78 +12,15 @@ from .trigger_spec import TriggerSpec class AutomationDefinition(BaseModel): - """The top-level JSON shape stored in ``automations.definition``. - - This is the editable spec a user authors (or the NL generator - produces). The envelope is structural only — every nested - discriminator (``triggers[].type``, ``plan[].action``) is resolved - against the registries at validation time, so adding a new - trigger or action type does not require touching this schema. - - See ``automation-design-plan.md`` §5 for the worked example and - rationale. - """ + """Top-level shape of an automation. See automation-design-plan.md §5.""" model_config = ConfigDict(extra="forbid") - schema_version: str = Field( - default="1.0", - description=( - "Schema version of the envelope itself. Migrations bump " - "this when the envelope shape changes; nested per-type " - "configs evolve independently via the registries." - ), - ) - name: str = Field( - ..., - description="Short, user-facing name shown in lists.", - min_length=1, - max_length=200, - ) - goal: str | None = Field( - default=None, - description=( - "Optional plain-language statement of what the " - "automation is for. Used by the NL generator's review " - "pass and by the UI's run dialog." - ), - ) - inputs: InputsBlock | None = Field( - default=None, - description=( - "Optional input contract. When omitted, the automation " - "accepts no inputs at fire time." - ), - ) - triggers: list[TriggerSpec] = Field( - default_factory=list, - description=( - "Triggers that fire this automation. Empty list means " - "the automation is only runnable via the manual " - "``Run now`` path." - ), - ) - plan: list[PlanStep] = Field( - ..., - description=( - "Ordered sequence of steps. Executed in array order — " - "no parallelism, no DAGs, no loops at the envelope " - "level." - ), - min_length=1, - ) - execution: ExecutionBlock = Field( - default_factory=ExecutionBlock, - description=( - "Execution defaults (timeouts, retries, concurrency, " - "budget). All fields default to safe values; the block " - "may be omitted entirely." - ), - ) - metadata: MetadataBlock = Field( - default_factory=MetadataBlock, - description=( - "Free-form metadata (tags, NL-generator breadcrumbs, " - "UI annotations). Tolerates unknown keys by design." - ), - ) + schema_version: str = "1.0" + name: str = Field(..., min_length=1, max_length=200) + goal: str | None = None + inputs: InputsBlock | None = None + triggers: list[TriggerSpec] = Field(default_factory=list) + plan: list[PlanStep] = Field(..., min_length=1) + execution: ExecutionBlock = Field(default_factory=ExecutionBlock) + metadata: MetadataBlock = Field(default_factory=MetadataBlock) diff --git a/surfsense_backend/app/automations/schemas/definition/execution.py b/surfsense_backend/app/automations/schemas/definition/execution.py index bb80e7281..2fcbc611e 100644 --- a/surfsense_backend/app/automations/schemas/definition/execution.py +++ b/surfsense_backend/app/automations/schemas/definition/execution.py @@ -1,4 +1,4 @@ -"""``ExecutionBlock`` — the ``execution`` section of the automation definition.""" +"""``ExecutionBlock`` — automation-wide execution defaults (overridable per step).""" from __future__ import annotations @@ -10,67 +10,16 @@ from .plan_step import PlanStep class ExecutionBlock(BaseModel): - """The ``execution`` block of an ``AutomationDefinition``. - - Carries automation-wide defaults that individual ``PlanStep``s - can override. Every field has a sane default so an automation - definition may omit the block entirely; in that case all defaults - apply. - - ``on_failure`` is a secondary plan that runs only when the main - ``plan`` fails after retries exhaust. It uses the same - ``PlanStep`` shape as the main plan and shares the same execution - semantics. - """ - model_config = ConfigDict(extra="forbid") - timeout_seconds: int = Field( - default=600, - gt=0, - description=( - "Hard wall-clock cap for the entire run. The executor " - "transitions the run to ``timed_out`` when this is " - "exceeded." - ), - ) - max_retries: int = Field( - default=2, - ge=0, - description=( - "Per-step retry budget applied when a step raises a " - "retryable error. Steps may override per-step." - ), - ) - retry_backoff: Literal["exponential", "linear", "none"] = Field( - default="exponential", - description="Backoff policy between retries.", - ) - concurrency: Literal[ - "drop_if_running", "queue", "always" - ] = Field( - default="drop_if_running", - description=( - "Behaviour when a new fire arrives while a previous run " - "is still in progress. ``drop_if_running`` skips the new " - "fire, ``queue`` enqueues it, ``always`` runs it in " - "parallel." - ), - ) + timeout_seconds: int = Field(default=600, gt=0, description="Wall-clock cap for the run.") + max_retries: int = Field(default=2, ge=0, description="Per-step retry budget.") + retry_backoff: Literal["exponential", "linear", "none"] = "exponential" + concurrency: Literal["drop_if_running", "queue", "always"] = "drop_if_running" budget_cap_usd: float | None = Field( - default=None, - gt=0, - description=( - "Optional mid-flight cost cap in USD. The executor kills " - "the run when accumulated cost exceeds this value. v1 " - "treats this as an advisory because cost tracking lands " - "with the executor in a later step." - ), + default=None, gt=0, description="Kill the run when accumulated cost exceeds this." ) on_failure: list[PlanStep] = Field( default_factory=list, - description=( - "Secondary plan executed only when the main plan fails " - "after retries exhaust. Empty list means no fallback." - ), + description="Steps run when the main plan fails after retries.", ) diff --git a/surfsense_backend/app/automations/schemas/definition/inputs.py b/surfsense_backend/app/automations/schemas/definition/inputs.py index 279efc113..52aed4e90 100644 --- a/surfsense_backend/app/automations/schemas/definition/inputs.py +++ b/surfsense_backend/app/automations/schemas/definition/inputs.py @@ -1,4 +1,4 @@ -"""``InputsBlock`` — the ``inputs`` section of the automation definition.""" +"""``InputsBlock`` — JSON Schema for inputs an automation accepts at fire time.""" from __future__ import annotations @@ -8,23 +8,6 @@ from pydantic import BaseModel, ConfigDict, Field class InputsBlock(BaseModel): - """The ``inputs`` block of an ``AutomationDefinition``. - - Holds a JSON Schema describing what data the automation accepts at - fire time. The same schema is used by: - - - The form editor (to render the manual-run dialog). - - The dispatcher (to validate trigger payloads before enqueueing - executor work). - - The template engine (to expose ``{{ inputs.* }}`` references in - plan-step configs). - - The ``schema`` value is the JSON-Schema dict itself, not a - Pydantic model — automations express their input contract in pure - JSON Schema so it round-trips losslessly through the database and - the NL generator. - """ - model_config = ConfigDict( extra="forbid", populate_by_name=True, @@ -34,10 +17,5 @@ class InputsBlock(BaseModel): schema_: dict[str, Any] = Field( ..., alias="schema", - description=( - "JSON Schema (draft-07 compatible) describing the inputs " - "this automation accepts. Properties may use the special " - "``$last_fired_at`` default literal to bind to the " - "trigger's last fire time." - ), + description="JSON Schema (draft-07) for accepted inputs.", ) diff --git a/surfsense_backend/app/automations/schemas/definition/metadata.py b/surfsense_backend/app/automations/schemas/definition/metadata.py index dc6541983..61d7af390 100644 --- a/surfsense_backend/app/automations/schemas/definition/metadata.py +++ b/surfsense_backend/app/automations/schemas/definition/metadata.py @@ -1,4 +1,4 @@ -"""``MetadataBlock`` — the ``metadata`` section of the automation definition.""" +"""``MetadataBlock`` — free-form metadata on a definition. Extra keys allowed.""" from __future__ import annotations @@ -6,31 +6,9 @@ from pydantic import BaseModel, ConfigDict, Field class MetadataBlock(BaseModel): - """Free-form metadata attached to the automation definition. - - Unlike the rest of the envelope this block tolerates unknown keys - (``extra='allow'``) — it's a deliberate extension point for - UI annotations, NL-generator breadcrumbs, custom tags, etc. - - Two fields are first-class so the rest of the system can rely on - them without reaching into the loose extras: - - ``tags`` — used by the UI for filtering and grouping. - ``created_from_nl`` — set by the NL generator so we can later - measure how many runs came from natural-language authoring. - """ - model_config = ConfigDict(extra="allow") - tags: list[str] = Field( - default_factory=list, - description="UI-facing tags. No semantic meaning to the engine.", - ) + tags: list[str] = Field(default_factory=list) created_from_nl: bool = Field( - default=False, - description=( - "True when the definition was produced by the NL " - "generator (set automatically by the generator path; " - "human-authored definitions keep this false)." - ), + default=False, description="True when produced by the NL generator." ) diff --git a/surfsense_backend/app/automations/schemas/definition/plan_step.py b/surfsense_backend/app/automations/schemas/definition/plan_step.py index 6898a0914..6a0bf9a1b 100644 --- a/surfsense_backend/app/automations/schemas/definition/plan_step.py +++ b/surfsense_backend/app/automations/schemas/definition/plan_step.py @@ -1,4 +1,4 @@ -"""``PlanStep`` — one entry in the envelope's ``plan`` array.""" +"""``PlanStep`` — one step in the sequential plan.""" from __future__ import annotations @@ -8,79 +8,21 @@ from pydantic import BaseModel, ConfigDict, Field class PlanStep(BaseModel): - """One step in an automation's sequential plan. - - Steps run in array order, no parallelism, no DAGs, no loops. The - ``when`` Jinja expression provides conditional skip; branching is - achieved by ``when`` clauses on multiple steps. For looping or - parallel work, the user routes through ``agent_task`` and lets the - agent reason about it. - - ``config`` is dispatched against the action registry at - validation time — its shape is determined by - ``ActionDefinition.config_schema`` for the ``action`` value. - - ``output_as`` binds the step's typed output into the template - namespace for later steps, e.g. ``output_as: 'summary'`` then - ``{{ summary.bullets }}`` in a downstream step's config. - """ - model_config = ConfigDict(extra="forbid") - step_id: str = Field( - ..., - description=( - "Unique-within-plan identifier. Used in run logs and as " - "the default for ``output_as`` when not provided." - ), - min_length=1, - ) - action: str = Field( - ..., - description=( - "Action-type discriminator (e.g., ``agent_task``). " - "Resolved against the action registry." - ), - min_length=1, - ) + step_id: str = Field(..., min_length=1, description="Unique within the plan.") + action: str = Field(..., min_length=1, description="Action type; resolved via registry.") when: str | None = Field( default=None, - description=( - "Optional Jinja expression evaluated against the run " - "context. Step is skipped when the expression is " - "falsy." - ), + description="Optional Jinja expression; step is skipped when falsy.", ) config: dict[str, Any] = Field( default_factory=dict, - description=( - "Action-type-specific config. Validated against the " - "registered ``ActionDefinition.config_schema`` for " - "``action`` at definition-save time. Jinja templates " - "inside config are rendered at step-execute time." - ), + description="Action-type-specific config; Jinja-rendered at execute time.", ) output_as: str | None = Field( default=None, - description=( - "Name to bind the step output under for downstream " - "steps. Defaults to ``step_id`` when omitted." - ), - ) - max_retries: int | None = Field( - default=None, - ge=0, - description=( - "Per-step override of the automation-level ``max_retries``. " - "Omitted means inherit from execution block." - ), - ) - timeout_seconds: int | None = Field( - default=None, - gt=0, - description=( - "Per-step override of the automation-level " - "``timeout_seconds``. Omitted means inherit from " - "execution block." - ), + description="Bind step output under this name. Defaults to step_id.", ) + max_retries: int | None = Field(default=None, ge=0) + timeout_seconds: int | None = Field(default=None, gt=0) diff --git a/surfsense_backend/app/automations/schemas/definition/trigger_spec.py b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py index 827b0a315..0fdf1f35a 100644 --- a/surfsense_backend/app/automations/schemas/definition/trigger_spec.py +++ b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py @@ -1,4 +1,4 @@ -"""``TriggerSpec`` — one entry in the envelope's ``triggers`` array.""" +"""``TriggerSpec`` — one entry in the definition's ``triggers[]`` array.""" from __future__ import annotations @@ -8,33 +8,10 @@ from pydantic import BaseModel, ConfigDict, Field class TriggerSpec(BaseModel): - """One trigger attached to an automation, as it appears in the definition. - - The envelope keeps ``config`` as an untyped JSON object on purpose - — the per-type config schemas live in - ``app.automations.schemas.triggers`` and are dispatched at - validation time by looking up ``type`` in the trigger registry. - - This mirrors the design's "definitions are pure data" principle: - the envelope describes shape, the registry resolves names to - behaviour. - """ - model_config = ConfigDict(extra="forbid") - type: str = Field( - ..., - description=( - "Trigger-type discriminator (e.g., ``schedule``, ``manual``). " - "Resolved against the trigger registry." - ), - min_length=1, - ) + type: str = Field(..., min_length=1, description="Trigger type; resolved via registry.") config: dict[str, Any] = Field( default_factory=dict, - description=( - "Trigger-type-specific config. Validated against the " - "registered ``TriggerDefinition.config_schema`` for " - "``type`` at definition-save time." - ), + description="Type-specific config; validated against the trigger's schema.", ) diff --git a/surfsense_backend/app/automations/schemas/triggers/__init__.py b/surfsense_backend/app/automations/schemas/triggers/__init__.py index 847c7443b..0cd8bc38e 100644 --- a/surfsense_backend/app/automations/schemas/triggers/__init__.py +++ b/surfsense_backend/app/automations/schemas/triggers/__init__.py @@ -1,4 +1,4 @@ -"""Per-trigger config schemas: one file per trigger type registered in v1.""" +"""Per-trigger config schemas, one per trigger type.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/schemas/triggers/manual.py b/surfsense_backend/app/automations/schemas/triggers/manual.py index 6e04ba062..bf14f80b6 100644 --- a/surfsense_backend/app/automations/schemas/triggers/manual.py +++ b/surfsense_backend/app/automations/schemas/triggers/manual.py @@ -1,4 +1,4 @@ -"""``ManualTriggerConfig`` — config for the ``manual`` trigger type (empty in v1).""" +"""``ManualTriggerConfig`` — config for the ``manual`` trigger (empty in v1).""" from __future__ import annotations @@ -6,16 +6,4 @@ from pydantic import BaseModel, ConfigDict class ManualTriggerConfig(BaseModel): - """Config for the UI-driven ``manual`` trigger. - - Validated against ``AutomationTrigger.config`` whenever the - persisted ``type`` is ``manual``. v1 carries no configurable - fields — the "Run now" affordance simply fires this trigger with - an empty config object. The model exists so the registry dispatch - is uniform across all trigger types. - - Future versions may add fields here (e.g., a fixed prompt to - pre-fill the run dialog with) without breaking v1 payloads. - """ - model_config = ConfigDict(extra="forbid") diff --git a/surfsense_backend/app/automations/schemas/triggers/schedule.py b/surfsense_backend/app/automations/schemas/triggers/schedule.py index e7c20da3a..9d8c7d38d 100644 --- a/surfsense_backend/app/automations/schemas/triggers/schedule.py +++ b/surfsense_backend/app/automations/schemas/triggers/schedule.py @@ -6,28 +6,7 @@ from pydantic import BaseModel, ConfigDict, Field class ScheduleTriggerConfig(BaseModel): - """Config for a cron-driven trigger. - - Validated against ``AutomationTrigger.config`` whenever the - persisted ``type`` is ``schedule``. The cron expression is - evaluated by Celery Beat's source; the timezone is an IANA name - (e.g., ``Africa/Kigali``) and is required so the user's cron is - unambiguous across DST boundaries. - """ - model_config = ConfigDict(extra="forbid") - cron: str = Field( - ..., - description=( - "Five-field cron expression. Minimum resolution is one " - "minute; the form editor warns when intervals tighter " - "than 15 minutes are used." - ), - examples=["0 9 * * 1-5"], - ) - timezone: str = Field( - ..., - description="IANA timezone name (e.g., 'Africa/Kigali', 'UTC').", - examples=["Africa/Kigali"], - ) + cron: str = Field(..., description="Five-field cron expression.", examples=["0 9 * * 1-5"]) + timezone: str = Field(..., description="IANA timezone.", examples=["Africa/Kigali"]) From 35117a952dd9ec5b6709f668dff0109e3c3d0d0e Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 11:41:32 +0200 Subject: [PATCH 092/198] refactor(automation): drop agent_session_id from AutomationRun MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A run can contain zero, one, or N agent_task steps. A single agent_session_id at the run level holds at most one of them, so the column is the wrong shape for the data. Per-step session ids (LangGraph thread/checkpoint reference for an agent_task step) live inside step_results[i] alongside the rest of the per-step bag (status, timings, output). Each agent step records its own; non-agent steps record nothing. Run-level "primary session" is a UI concern, not a schema concern. Trade-off: trace -> run reverse lookup is now a JSONB query, not an index hit. Usually traversal goes run -> trace; if the reverse becomes hot we add a GIN index on step_results or a generated column — both additive. Changes: - AutomationRun: drop the agent_session_id column; module docstring notes where per-step session ids now live. - Migration 144: drop the column from the CREATE TABLE; downgrade unchanged. Safe to edit migration 144 in place (vs. add 145 with ALTER ... DROP): this branch has not shipped and the table has never existed in any deployed database. --- .../alembic/versions/144_add_automation_tables.py | 1 - .../app/automations/persistence/models/run.py | 10 ++++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/surfsense_backend/alembic/versions/144_add_automation_tables.py b/surfsense_backend/alembic/versions/144_add_automation_tables.py index 6aa208dc1..acfa3db77 100644 --- a/surfsense_backend/alembic/versions/144_add_automation_tables.py +++ b/surfsense_backend/alembic/versions/144_add_automation_tables.py @@ -125,7 +125,6 @@ def upgrade() -> None: error JSONB, started_at TIMESTAMP WITH TIME ZONE, finished_at TIMESTAMP WITH TIME ZONE, - agent_session_id VARCHAR(200), created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); """ diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index 9291e5da0..43a1de07d 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -1,4 +1,9 @@ -"""``automation_runs`` table — immutable per-fire execution record.""" +"""``automation_runs`` table — immutable per-fire execution record. + +Per-step metadata (incl. any LangGraph session id for an ``agent_task`` step) +lives inside ``step_results[i]``, since a single run may contain zero, one, +or N agent steps. +""" from __future__ import annotations @@ -8,7 +13,6 @@ from sqlalchemy import ( Enum as SQLAlchemyEnum, ForeignKey, Integer, - String, ) from sqlalchemy.dialects.postgresql import JSONB @@ -54,5 +58,3 @@ class AutomationRun(BaseModel, TimestampMixin): started_at = Column(TIMESTAMP(timezone=True), nullable=True) finished_at = Column(TIMESTAMP(timezone=True), nullable=True) - - agent_session_id = Column(String(200), nullable=True) From a4fbfd8c0d77bc9f11ef26d87c5d64b283109829 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 11:45:04 +0200 Subject: [PATCH 093/198] chore(automation): tighten run.py + envelope.py docstrings Re-apply the trim style after the prior refactor commit re-introduced a multi-line docstring on AutomationRun. - AutomationRun: drop the four-line docstring explaining where per-step session ids live; move the note to a single-line inline comment right above ``step_results`` where it's actionable. - AutomationDefinition: drop the design-plan cross-reference; the module docstring already establishes what the file is. No behaviour change. --- .../app/automations/persistence/models/run.py | 9 +++------ .../app/automations/schemas/definition/envelope.py | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index 43a1de07d..45da6a39d 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -1,9 +1,4 @@ -"""``automation_runs`` table — immutable per-fire execution record. - -Per-step metadata (incl. any LangGraph session id for an ``agent_task`` step) -lives inside ``step_results[i]``, since a single run may contain zero, one, -or N agent steps. -""" +"""``automation_runs`` table — immutable per-fire execution record.""" from __future__ import annotations @@ -51,6 +46,8 @@ class AutomationRun(BaseModel, TimestampMixin): trigger_payload = Column(JSONB, nullable=True) resolved_inputs = Column(JSONB, nullable=False, server_default="{}") + # one entry per executed step; agent_task entries carry their own + # `agent_session_id` (LangGraph thread reference) inside this JSONB step_results = Column(JSONB, nullable=False, server_default="[]") output = Column(JSONB, nullable=True) artifacts = Column(JSONB, nullable=False, server_default="[]") diff --git a/surfsense_backend/app/automations/schemas/definition/envelope.py b/surfsense_backend/app/automations/schemas/definition/envelope.py index ccd76d612..ffc45a0cd 100644 --- a/surfsense_backend/app/automations/schemas/definition/envelope.py +++ b/surfsense_backend/app/automations/schemas/definition/envelope.py @@ -12,7 +12,7 @@ from .trigger_spec import TriggerSpec class AutomationDefinition(BaseModel): - """Top-level shape of an automation. See automation-design-plan.md §5.""" + """Top-level shape of an automation.""" model_config = ConfigDict(extra="forbid") From fe32cd35ed9e1f407ac8b9cc46b9acaf57742790 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 13:29:18 +0200 Subject: [PATCH 094/198] refactor(automation): rename trigger config column to params --- surfsense_backend/alembic/versions/144_add_automation_tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surfsense_backend/alembic/versions/144_add_automation_tables.py b/surfsense_backend/alembic/versions/144_add_automation_tables.py index acfa3db77..8b59ee969 100644 --- a/surfsense_backend/alembic/versions/144_add_automation_tables.py +++ b/surfsense_backend/alembic/versions/144_add_automation_tables.py @@ -86,7 +86,7 @@ def upgrade() -> None: automation_id INTEGER NOT NULL REFERENCES automations(id) ON DELETE CASCADE, type automation_trigger_type NOT NULL, - config JSONB NOT NULL, + params JSONB NOT NULL, enabled BOOLEAN NOT NULL DEFAULT true, last_fired_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() From c8a89ccac8536d42d679020d3fcc70d8ca234184 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 13:29:22 +0200 Subject: [PATCH 095/198] refactor(automation): rename trigger model config to params --- surfsense_backend/app/automations/persistence/__init__.py | 2 +- .../app/automations/persistence/models/__init__.py | 2 +- surfsense_backend/app/automations/persistence/models/run.py | 2 +- surfsense_backend/app/automations/persistence/models/trigger.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/surfsense_backend/app/automations/persistence/__init__.py b/surfsense_backend/app/automations/persistence/__init__.py index 4c1ea3423..b10aef03d 100644 --- a/surfsense_backend/app/automations/persistence/__init__.py +++ b/surfsense_backend/app/automations/persistence/__init__.py @@ -1,4 +1,4 @@ -"""SQLAlchemy models and enums for the automation tables.""" +"""Models and enums for the automation tables.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/persistence/models/__init__.py b/surfsense_backend/app/automations/persistence/models/__init__.py index 4bc023ea3..8b985f025 100644 --- a/surfsense_backend/app/automations/persistence/models/__init__.py +++ b/surfsense_backend/app/automations/persistence/models/__init__.py @@ -1,4 +1,4 @@ -"""SQLAlchemy models, one per table.""" +"""Models, one per table.""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index 45da6a39d..118085b1d 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -47,7 +47,7 @@ class AutomationRun(BaseModel, TimestampMixin): trigger_payload = Column(JSONB, nullable=True) resolved_inputs = Column(JSONB, nullable=False, server_default="{}") # one entry per executed step; agent_task entries carry their own - # `agent_session_id` (LangGraph thread reference) inside this JSONB + # `agent_session_id` inside their entry step_results = Column(JSONB, nullable=False, server_default="[]") output = Column(JSONB, nullable=True) artifacts = Column(JSONB, nullable=False, server_default="[]") diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py index 8dab48a6b..55affeabc 100644 --- a/surfsense_backend/app/automations/persistence/models/trigger.py +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -33,7 +33,7 @@ class AutomationTrigger(BaseModel, TimestampMixin): index=True, ) - config = Column(JSONB, nullable=False) + params = Column(JSONB, nullable=False) enabled = Column( Boolean, From 9fa35f21cfea0d09c2a148502810fea7f31d0645 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 13:29:26 +0200 Subject: [PATCH 096/198] refactor(automation): rename schema config to params, drop dead fields --- .../app/automations/schemas/__init__.py | 12 ++++++------ .../app/automations/schemas/actions/__init__.py | 6 +++--- .../app/automations/schemas/actions/agent_task.py | 14 +++++++------- .../automations/schemas/definition/execution.py | 3 --- .../app/automations/schemas/definition/inputs.py | 2 +- .../app/automations/schemas/definition/metadata.py | 3 --- .../automations/schemas/definition/plan_step.py | 6 +++--- .../automations/schemas/definition/trigger_spec.py | 4 ++-- .../app/automations/schemas/triggers/__init__.py | 10 +++++----- .../app/automations/schemas/triggers/manual.py | 4 ++-- .../app/automations/schemas/triggers/schedule.py | 4 ++-- 11 files changed, 31 insertions(+), 37 deletions(-) diff --git a/surfsense_backend/app/automations/schemas/__init__.py b/surfsense_backend/app/automations/schemas/__init__.py index 23f0232fb..2bb0060ba 100644 --- a/surfsense_backend/app/automations/schemas/__init__.py +++ b/surfsense_backend/app/automations/schemas/__init__.py @@ -1,8 +1,8 @@ -"""Pydantic schemas for the automation definition and per-type configs.""" +"""Schemas for the automation definition and per-type configs.""" from __future__ import annotations -from .actions import AgentTaskActionConfig +from .actions import AgentTaskActionParams from .definition import ( AutomationDefinition, ExecutionBlock, @@ -11,16 +11,16 @@ from .definition import ( PlanStep, TriggerSpec, ) -from .triggers import ManualTriggerConfig, ScheduleTriggerConfig +from .triggers import ManualTriggerParams, ScheduleTriggerParams __all__ = [ - "AgentTaskActionConfig", + "AgentTaskActionParams", "AutomationDefinition", "ExecutionBlock", "InputsBlock", - "ManualTriggerConfig", + "ManualTriggerParams", "MetadataBlock", "PlanStep", - "ScheduleTriggerConfig", + "ScheduleTriggerParams", "TriggerSpec", ] diff --git a/surfsense_backend/app/automations/schemas/actions/__init__.py b/surfsense_backend/app/automations/schemas/actions/__init__.py index 4149206d7..c51d33b6a 100644 --- a/surfsense_backend/app/automations/schemas/actions/__init__.py +++ b/surfsense_backend/app/automations/schemas/actions/__init__.py @@ -1,9 +1,9 @@ -"""Per-action config schemas, one per action type.""" +"""Per-action params schemas, one per action type.""" from __future__ import annotations -from .agent_task import AgentTaskActionConfig +from .agent_task import AgentTaskActionParams __all__ = [ - "AgentTaskActionConfig", + "AgentTaskActionParams", ] diff --git a/surfsense_backend/app/automations/schemas/actions/agent_task.py b/surfsense_backend/app/automations/schemas/actions/agent_task.py index fe9d5fcef..348db8095 100644 --- a/surfsense_backend/app/automations/schemas/actions/agent_task.py +++ b/surfsense_backend/app/automations/schemas/actions/agent_task.py @@ -1,4 +1,4 @@ -"""``AgentTaskActionConfig`` — config for the ``agent_task`` action type.""" +"""``AgentTaskActionParams`` — params for the ``agent_task`` action type.""" from __future__ import annotations @@ -7,21 +7,21 @@ from typing import Any from pydantic import BaseModel, ConfigDict, Field -class AgentTaskActionConfig(BaseModel): - """Run a LangGraph Deep Agent restricted to a scoped capability list.""" +class AgentTaskActionParams(BaseModel): + """Run an agent task with a scoped tool allowlist.""" model_config = ConfigDict(extra="forbid") - prompt: str = Field(..., min_length=1, description="Task prompt; Jinja-rendered.") + prompt: str = Field(..., min_length=1, description="Task prompt; rendered at execute time.") tools: list[str] = Field( default_factory=list, - description="Capability IDs the agent may call. Empty = no tool access.", + description="Tool identifiers the agent may call. Empty = no tool access.", ) model: str | None = Field( default=None, - description="LiteLLM model id. Defaults to the search space's agent_llm_id.", + description="Model identifier. Defaults to the search space's agent_llm_id.", ) output_schema: dict[str, Any] | None = Field( default=None, - description="JSON Schema the agent must return. Recommended.", + description="JSON Schema (draft 2020-12) the agent must return. Recommended.", ) diff --git a/surfsense_backend/app/automations/schemas/definition/execution.py b/surfsense_backend/app/automations/schemas/definition/execution.py index 2fcbc611e..d5f31364c 100644 --- a/surfsense_backend/app/automations/schemas/definition/execution.py +++ b/surfsense_backend/app/automations/schemas/definition/execution.py @@ -16,9 +16,6 @@ class ExecutionBlock(BaseModel): max_retries: int = Field(default=2, ge=0, description="Per-step retry budget.") retry_backoff: Literal["exponential", "linear", "none"] = "exponential" concurrency: Literal["drop_if_running", "queue", "always"] = "drop_if_running" - budget_cap_usd: float | None = Field( - default=None, gt=0, description="Kill the run when accumulated cost exceeds this." - ) on_failure: list[PlanStep] = Field( default_factory=list, description="Steps run when the main plan fails after retries.", diff --git a/surfsense_backend/app/automations/schemas/definition/inputs.py b/surfsense_backend/app/automations/schemas/definition/inputs.py index 52aed4e90..b0b1a9414 100644 --- a/surfsense_backend/app/automations/schemas/definition/inputs.py +++ b/surfsense_backend/app/automations/schemas/definition/inputs.py @@ -17,5 +17,5 @@ class InputsBlock(BaseModel): schema_: dict[str, Any] = Field( ..., alias="schema", - description="JSON Schema (draft-07) for accepted inputs.", + description="JSON Schema (draft 2020-12) for accepted inputs.", ) diff --git a/surfsense_backend/app/automations/schemas/definition/metadata.py b/surfsense_backend/app/automations/schemas/definition/metadata.py index 61d7af390..9b3722430 100644 --- a/surfsense_backend/app/automations/schemas/definition/metadata.py +++ b/surfsense_backend/app/automations/schemas/definition/metadata.py @@ -9,6 +9,3 @@ class MetadataBlock(BaseModel): model_config = ConfigDict(extra="allow") tags: list[str] = Field(default_factory=list) - created_from_nl: bool = Field( - default=False, description="True when produced by the NL generator." - ) diff --git a/surfsense_backend/app/automations/schemas/definition/plan_step.py b/surfsense_backend/app/automations/schemas/definition/plan_step.py index 6a0bf9a1b..5d16f1f3e 100644 --- a/surfsense_backend/app/automations/schemas/definition/plan_step.py +++ b/surfsense_backend/app/automations/schemas/definition/plan_step.py @@ -14,11 +14,11 @@ class PlanStep(BaseModel): action: str = Field(..., min_length=1, description="Action type; resolved via registry.") when: str | None = Field( default=None, - description="Optional Jinja expression; step is skipped when falsy.", + description="Optional predicate; step is skipped when falsy.", ) - config: dict[str, Any] = Field( + params: dict[str, Any] = Field( default_factory=dict, - description="Action-type-specific config; Jinja-rendered at execute time.", + description="Action-type-specific params; rendered at execute time.", ) output_as: str | None = Field( default=None, diff --git a/surfsense_backend/app/automations/schemas/definition/trigger_spec.py b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py index 0fdf1f35a..a359a2f63 100644 --- a/surfsense_backend/app/automations/schemas/definition/trigger_spec.py +++ b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py @@ -11,7 +11,7 @@ class TriggerSpec(BaseModel): model_config = ConfigDict(extra="forbid") type: str = Field(..., min_length=1, description="Trigger type; resolved via registry.") - config: dict[str, Any] = Field( + params: dict[str, Any] = Field( default_factory=dict, - description="Type-specific config; validated against the trigger's schema.", + description="Type-specific params; validated against the trigger's schema.", ) diff --git a/surfsense_backend/app/automations/schemas/triggers/__init__.py b/surfsense_backend/app/automations/schemas/triggers/__init__.py index 0cd8bc38e..3ddd26f95 100644 --- a/surfsense_backend/app/automations/schemas/triggers/__init__.py +++ b/surfsense_backend/app/automations/schemas/triggers/__init__.py @@ -1,11 +1,11 @@ -"""Per-trigger config schemas, one per trigger type.""" +"""Per-trigger params schemas, one per trigger type.""" from __future__ import annotations -from .manual import ManualTriggerConfig -from .schedule import ScheduleTriggerConfig +from .manual import ManualTriggerParams +from .schedule import ScheduleTriggerParams __all__ = [ - "ManualTriggerConfig", - "ScheduleTriggerConfig", + "ManualTriggerParams", + "ScheduleTriggerParams", ] diff --git a/surfsense_backend/app/automations/schemas/triggers/manual.py b/surfsense_backend/app/automations/schemas/triggers/manual.py index bf14f80b6..577655086 100644 --- a/surfsense_backend/app/automations/schemas/triggers/manual.py +++ b/surfsense_backend/app/automations/schemas/triggers/manual.py @@ -1,9 +1,9 @@ -"""``ManualTriggerConfig`` — config for the ``manual`` trigger (empty in v1).""" +"""``ManualTriggerParams`` — params for the ``manual`` trigger (empty in v1).""" from __future__ import annotations from pydantic import BaseModel, ConfigDict -class ManualTriggerConfig(BaseModel): +class ManualTriggerParams(BaseModel): model_config = ConfigDict(extra="forbid") diff --git a/surfsense_backend/app/automations/schemas/triggers/schedule.py b/surfsense_backend/app/automations/schemas/triggers/schedule.py index 9d8c7d38d..0418bd1d9 100644 --- a/surfsense_backend/app/automations/schemas/triggers/schedule.py +++ b/surfsense_backend/app/automations/schemas/triggers/schedule.py @@ -1,11 +1,11 @@ -"""``ScheduleTriggerConfig`` — config for the ``schedule`` trigger type.""" +"""``ScheduleTriggerParams`` — params for the ``schedule`` trigger type.""" from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field -class ScheduleTriggerConfig(BaseModel): +class ScheduleTriggerParams(BaseModel): model_config = ConfigDict(extra="forbid") cron: str = Field(..., description="Five-field cron expression.", examples=["0 9 * * 1-5"]) From 7ac99b89a0ddf4f8f0d6f0d08b4fac66b7e2c1a2 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 13:29:30 +0200 Subject: [PATCH 097/198] refactor(automation): drop Capability registry --- .../app/automations/registries/__init__.py | 14 +---------- .../automations/registries/actions/types.py | 2 +- .../registries/capabilities/__init__.py | 14 ----------- .../registries/capabilities/store.py | 23 ------------------- .../registries/capabilities/types.py | 18 --------------- .../automations/registries/triggers/types.py | 2 +- 6 files changed, 3 insertions(+), 70 deletions(-) delete mode 100644 surfsense_backend/app/automations/registries/capabilities/__init__.py delete mode 100644 surfsense_backend/app/automations/registries/capabilities/store.py delete mode 100644 surfsense_backend/app/automations/registries/capabilities/types.py diff --git a/surfsense_backend/app/automations/registries/__init__.py b/surfsense_backend/app/automations/registries/__init__.py index a97595ced..f497caf59 100644 --- a/surfsense_backend/app/automations/registries/__init__.py +++ b/surfsense_backend/app/automations/registries/__init__.py @@ -1,4 +1,4 @@ -"""Capability, action, and trigger registries — populated at process startup.""" +"""Action and trigger registries — populated at process startup.""" from __future__ import annotations @@ -9,13 +9,6 @@ from .actions import ( get_action, register_action, ) -from .capabilities import ( - Capability, - CapabilityHandler, - all_capabilities, - get_capability, - register_capability, -) from .triggers import ( TriggerDefinition, all_triggers, @@ -26,16 +19,11 @@ from .triggers import ( __all__ = [ "ActionDefinition", "ActionHandler", - "Capability", - "CapabilityHandler", "TriggerDefinition", "all_actions", - "all_capabilities", "all_triggers", "get_action", - "get_capability", "get_trigger", "register_action", - "register_capability", "register_trigger", ] diff --git a/surfsense_backend/app/automations/registries/actions/types.py b/surfsense_backend/app/automations/registries/actions/types.py index 13c826c66..99f94ae7c 100644 --- a/surfsense_backend/app/automations/registries/actions/types.py +++ b/surfsense_backend/app/automations/registries/actions/types.py @@ -14,5 +14,5 @@ class ActionDefinition: type: str name: str description: str - config_schema: dict[str, Any] + params_schema: dict[str, Any] handler: ActionHandler diff --git a/surfsense_backend/app/automations/registries/capabilities/__init__.py b/surfsense_backend/app/automations/registries/capabilities/__init__.py deleted file mode 100644 index 213303fc0..000000000 --- a/surfsense_backend/app/automations/registries/capabilities/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Capability registry.""" - -from __future__ import annotations - -from .store import all_capabilities, get_capability, register_capability -from .types import Capability, CapabilityHandler - -__all__ = [ - "Capability", - "CapabilityHandler", - "all_capabilities", - "get_capability", - "register_capability", -] diff --git a/surfsense_backend/app/automations/registries/capabilities/store.py b/surfsense_backend/app/automations/registries/capabilities/store.py deleted file mode 100644 index 4d87abe47..000000000 --- a/surfsense_backend/app/automations/registries/capabilities/store.py +++ /dev/null @@ -1,23 +0,0 @@ -"""In-memory capability registry. Populated once at process startup.""" - -from __future__ import annotations - -from .types import Capability - -_REGISTRY: dict[str, Capability] = {} - - -def register_capability(capability: Capability) -> None: - """Register a capability. Raises on duplicate id.""" - if capability.id in _REGISTRY: - raise ValueError(f"Capability already registered: {capability.id!r}") - _REGISTRY[capability.id] = capability - - -def get_capability(capability_id: str) -> Capability | None: - return _REGISTRY.get(capability_id) - - -def all_capabilities() -> dict[str, Capability]: - """Defensive snapshot of the registry.""" - return dict(_REGISTRY) diff --git a/surfsense_backend/app/automations/registries/capabilities/types.py b/surfsense_backend/app/automations/registries/capabilities/types.py deleted file mode 100644 index 2759bc809..000000000 --- a/surfsense_backend/app/automations/registries/capabilities/types.py +++ /dev/null @@ -1,18 +0,0 @@ -"""``Capability`` dataclass and handler signature. Locked at five fields for v1.""" - -from __future__ import annotations - -from collections.abc import Awaitable, Callable -from dataclasses import dataclass -from typing import Any - -CapabilityHandler = Callable[[dict[str, Any]], Awaitable[Any]] - - -@dataclass(frozen=True, slots=True) -class Capability: - id: str - description: str - input_schema: dict[str, Any] - output_schema: dict[str, Any] - handler: CapabilityHandler diff --git a/surfsense_backend/app/automations/registries/triggers/types.py b/surfsense_backend/app/automations/registries/triggers/types.py index 5da081343..783bd7842 100644 --- a/surfsense_backend/app/automations/registries/triggers/types.py +++ b/surfsense_backend/app/automations/registries/triggers/types.py @@ -10,5 +10,5 @@ from typing import Any class TriggerDefinition: type: str description: str - config_schema: dict[str, Any] + params_schema: dict[str, Any] payload_schema: dict[str, Any] From 7f4c1c25abad3531ecb54191aef9b9323feaacf9 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 13:45:32 +0200 Subject: [PATCH 098/198] feat(automation): wire SQLAlchemy relationships on both sides --- .../persistence/models/automation.py | 16 ++++++++++ .../app/automations/persistence/models/run.py | 4 +++ .../automations/persistence/models/trigger.py | 8 +++++ surfsense_backend/app/db.py | 32 +++++++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/surfsense_backend/app/automations/persistence/models/automation.py b/surfsense_backend/app/automations/persistence/models/automation.py index 637fd2282..ee86851c1 100644 --- a/surfsense_backend/app/automations/persistence/models/automation.py +++ b/surfsense_backend/app/automations/persistence/models/automation.py @@ -14,6 +14,7 @@ from sqlalchemy import ( Text, ) from sqlalchemy.dialects.postgresql import JSONB, UUID +from sqlalchemy.orm import relationship from app.db import BaseModel, TimestampMixin @@ -59,3 +60,18 @@ class Automation(BaseModel, TimestampMixin): onupdate=lambda: datetime.now(UTC), index=True, ) + + search_space = relationship("SearchSpace", back_populates="automations") + created_by = relationship("User", back_populates="automations") + triggers = relationship( + "AutomationTrigger", + back_populates="automation", + cascade="all, delete-orphan", + passive_deletes=True, + ) + runs = relationship( + "AutomationRun", + back_populates="automation", + cascade="all, delete-orphan", + passive_deletes=True, + ) diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index 118085b1d..fdc355e8f 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -10,6 +10,7 @@ from sqlalchemy import ( Integer, ) from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.orm import relationship from app.db import BaseModel, TimestampMixin @@ -55,3 +56,6 @@ class AutomationRun(BaseModel, TimestampMixin): started_at = Column(TIMESTAMP(timezone=True), nullable=True) finished_at = Column(TIMESTAMP(timezone=True), nullable=True) + + automation = relationship("Automation", back_populates="runs") + trigger = relationship("AutomationTrigger", back_populates="runs") diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py index 55affeabc..7582234d4 100644 --- a/surfsense_backend/app/automations/persistence/models/trigger.py +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -11,6 +11,7 @@ from sqlalchemy import ( Integer, ) from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.orm import relationship from app.db import BaseModel, TimestampMixin @@ -44,3 +45,10 @@ class AutomationTrigger(BaseModel, TimestampMixin): ) last_fired_at = Column(TIMESTAMP(timezone=True), nullable=True) + + automation = relationship("Automation", back_populates="triggers") + runs = relationship( + "AutomationRun", + back_populates="trigger", + passive_deletes=True, + ) diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py index 9fc27fb1f..71466495b 100644 --- a/surfsense_backend/app/db.py +++ b/surfsense_backend/app/db.py @@ -1533,6 +1533,14 @@ class SearchSpace(BaseModel, TimestampMixin): cascade="all, delete-orphan", ) + automations = relationship( + "Automation", + back_populates="search_space", + order_by="Automation.id", + cascade="all, delete-orphan", + passive_deletes=True, + ) + # RBAC relationships roles = relationship( "SearchSpaceRole", @@ -2125,6 +2133,13 @@ if config.AUTH_TYPE == "GOOGLE": passive_deletes=True, ) + # Automations created by this user + automations = relationship( + "Automation", + back_populates="created_by", + passive_deletes=True, + ) + # Incentive tasks completed by this user incentive_tasks = relationship( "UserIncentiveTask", @@ -2257,6 +2272,13 @@ else: passive_deletes=True, ) + # Automations created by this user + automations = relationship( + "Automation", + back_populates="created_by", + passive_deletes=True, + ) + # Incentive tasks completed by this user incentive_tasks = relationship( "UserIncentiveTask", @@ -2560,6 +2582,16 @@ class RefreshToken(Base, TimestampMixin): return not self.is_expired and not self.is_revoked +# Register model packages that live outside this file so their classes +# are present in Base.metadata before configure_mappers() resolves any +# string-based relationship() references. +from app.automations.persistence import ( # noqa: E402, F401 + Automation, + AutomationRun, + AutomationTrigger, +) + + engine = create_async_engine( DATABASE_URL, pool_size=30, From 56b3e1bfc4961493e4ad774c1ad25997e3171ae7 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 13:48:41 +0200 Subject: [PATCH 099/198] refactor(automation): drop Block suffix from definition components --- .../app/automations/schemas/__init__.py | 12 ++++++------ .../app/automations/schemas/definition/__init__.py | 14 +++++++------- .../app/automations/schemas/definition/envelope.py | 12 ++++++------ .../automations/schemas/definition/execution.py | 4 ++-- .../app/automations/schemas/definition/inputs.py | 4 ++-- .../app/automations/schemas/definition/metadata.py | 4 ++-- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/surfsense_backend/app/automations/schemas/__init__.py b/surfsense_backend/app/automations/schemas/__init__.py index 2bb0060ba..8659ac9c9 100644 --- a/surfsense_backend/app/automations/schemas/__init__.py +++ b/surfsense_backend/app/automations/schemas/__init__.py @@ -5,9 +5,9 @@ from __future__ import annotations from .actions import AgentTaskActionParams from .definition import ( AutomationDefinition, - ExecutionBlock, - InputsBlock, - MetadataBlock, + Execution, + Inputs, + Metadata, PlanStep, TriggerSpec, ) @@ -16,10 +16,10 @@ from .triggers import ManualTriggerParams, ScheduleTriggerParams __all__ = [ "AgentTaskActionParams", "AutomationDefinition", - "ExecutionBlock", - "InputsBlock", + "Execution", + "Inputs", "ManualTriggerParams", - "MetadataBlock", + "Metadata", "PlanStep", "ScheduleTriggerParams", "TriggerSpec", diff --git a/surfsense_backend/app/automations/schemas/definition/__init__.py b/surfsense_backend/app/automations/schemas/definition/__init__.py index 838e72f86..3fb0a739b 100644 --- a/surfsense_backend/app/automations/schemas/definition/__init__.py +++ b/surfsense_backend/app/automations/schemas/definition/__init__.py @@ -1,19 +1,19 @@ -"""Automation definition envelope and its building blocks.""" +"""Automation definition envelope and its components.""" from __future__ import annotations from .envelope import AutomationDefinition -from .execution import ExecutionBlock -from .inputs import InputsBlock -from .metadata import MetadataBlock +from .execution import Execution +from .inputs import Inputs +from .metadata import Metadata from .plan_step import PlanStep from .trigger_spec import TriggerSpec __all__ = [ "AutomationDefinition", - "ExecutionBlock", - "InputsBlock", - "MetadataBlock", + "Execution", + "Inputs", + "Metadata", "PlanStep", "TriggerSpec", ] diff --git a/surfsense_backend/app/automations/schemas/definition/envelope.py b/surfsense_backend/app/automations/schemas/definition/envelope.py index ffc45a0cd..f919b2abb 100644 --- a/surfsense_backend/app/automations/schemas/definition/envelope.py +++ b/surfsense_backend/app/automations/schemas/definition/envelope.py @@ -4,9 +4,9 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field -from .execution import ExecutionBlock -from .inputs import InputsBlock -from .metadata import MetadataBlock +from .execution import Execution +from .inputs import Inputs +from .metadata import Metadata from .plan_step import PlanStep from .trigger_spec import TriggerSpec @@ -19,8 +19,8 @@ class AutomationDefinition(BaseModel): schema_version: str = "1.0" name: str = Field(..., min_length=1, max_length=200) goal: str | None = None - inputs: InputsBlock | None = None + inputs: Inputs | None = None triggers: list[TriggerSpec] = Field(default_factory=list) plan: list[PlanStep] = Field(..., min_length=1) - execution: ExecutionBlock = Field(default_factory=ExecutionBlock) - metadata: MetadataBlock = Field(default_factory=MetadataBlock) + execution: Execution = Field(default_factory=Execution) + metadata: Metadata = Field(default_factory=Metadata) diff --git a/surfsense_backend/app/automations/schemas/definition/execution.py b/surfsense_backend/app/automations/schemas/definition/execution.py index d5f31364c..61861f8d8 100644 --- a/surfsense_backend/app/automations/schemas/definition/execution.py +++ b/surfsense_backend/app/automations/schemas/definition/execution.py @@ -1,4 +1,4 @@ -"""``ExecutionBlock`` — automation-wide execution defaults (overridable per step).""" +"""``Execution`` — automation-wide execution defaults (overridable per step).""" from __future__ import annotations @@ -9,7 +9,7 @@ from pydantic import BaseModel, ConfigDict, Field from .plan_step import PlanStep -class ExecutionBlock(BaseModel): +class Execution(BaseModel): model_config = ConfigDict(extra="forbid") timeout_seconds: int = Field(default=600, gt=0, description="Wall-clock cap for the run.") diff --git a/surfsense_backend/app/automations/schemas/definition/inputs.py b/surfsense_backend/app/automations/schemas/definition/inputs.py index b0b1a9414..619fd16cd 100644 --- a/surfsense_backend/app/automations/schemas/definition/inputs.py +++ b/surfsense_backend/app/automations/schemas/definition/inputs.py @@ -1,4 +1,4 @@ -"""``InputsBlock`` — JSON Schema for inputs an automation accepts at fire time.""" +"""``Inputs`` — JSON Schema for inputs an automation accepts at fire time.""" from __future__ import annotations @@ -7,7 +7,7 @@ from typing import Any from pydantic import BaseModel, ConfigDict, Field -class InputsBlock(BaseModel): +class Inputs(BaseModel): model_config = ConfigDict( extra="forbid", populate_by_name=True, diff --git a/surfsense_backend/app/automations/schemas/definition/metadata.py b/surfsense_backend/app/automations/schemas/definition/metadata.py index 9b3722430..3ac341d2e 100644 --- a/surfsense_backend/app/automations/schemas/definition/metadata.py +++ b/surfsense_backend/app/automations/schemas/definition/metadata.py @@ -1,11 +1,11 @@ -"""``MetadataBlock`` — free-form metadata on a definition. Extra keys allowed.""" +"""``Metadata`` — free-form metadata on a definition. Extra keys allowed.""" from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field -class MetadataBlock(BaseModel): +class Metadata(BaseModel): model_config = ConfigDict(extra="allow") tags: list[str] = Field(default_factory=list) From 99fd1a1338d870316e24d727380a9b1e46169e0a Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 13:58:57 +0200 Subject: [PATCH 100/198] feat(automation): register agent_task action and schedule/manual triggers --- .../registries/actions/__init__.py | 3 +++ .../registries/actions/agent_task.py | 27 +++++++++++++++++++ .../registries/triggers/__init__.py | 3 +++ .../automations/registries/triggers/manual.py | 17 ++++++++++++ .../registries/triggers/schedule.py | 21 +++++++++++++++ 5 files changed, 71 insertions(+) create mode 100644 surfsense_backend/app/automations/registries/actions/agent_task.py create mode 100644 surfsense_backend/app/automations/registries/triggers/manual.py create mode 100644 surfsense_backend/app/automations/registries/triggers/schedule.py diff --git a/surfsense_backend/app/automations/registries/actions/__init__.py b/surfsense_backend/app/automations/registries/actions/__init__.py index 1bb3ae9cc..68e507133 100644 --- a/surfsense_backend/app/automations/registries/actions/__init__.py +++ b/surfsense_backend/app/automations/registries/actions/__init__.py @@ -12,3 +12,6 @@ __all__ = [ "get_action", "register_action", ] + +# Built-in actions self-register at import time. +from . import agent_task # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/registries/actions/agent_task.py b/surfsense_backend/app/automations/registries/actions/agent_task.py new file mode 100644 index 000000000..9acc11c2c --- /dev/null +++ b/surfsense_backend/app/automations/registries/actions/agent_task.py @@ -0,0 +1,27 @@ +"""Built-in ``agent_task`` action. Self-registers at import time.""" + +from __future__ import annotations + +from typing import Any + +from app.automations.schemas.actions import AgentTaskActionParams + +from .store import register_action +from .types import ActionDefinition + + +async def _handle_agent_task(args: dict[str, Any]) -> dict[str, Any]: + """Stub. Validates params; real wiring lands with the executor.""" + AgentTaskActionParams.model_validate(args) + return {"status": "stubbed"} + + +AGENT_TASK_ACTION = ActionDefinition( + type="agent_task", + name="Agent task", + description="Run an agent task with a scoped tool allowlist.", + params_schema=AgentTaskActionParams.model_json_schema(), + handler=_handle_agent_task, +) + +register_action(AGENT_TASK_ACTION) diff --git a/surfsense_backend/app/automations/registries/triggers/__init__.py b/surfsense_backend/app/automations/registries/triggers/__init__.py index 843da5e70..e08dcce76 100644 --- a/surfsense_backend/app/automations/registries/triggers/__init__.py +++ b/surfsense_backend/app/automations/registries/triggers/__init__.py @@ -11,3 +11,6 @@ __all__ = [ "get_trigger", "register_trigger", ] + +# Built-in triggers self-register at import time. +from . import manual, schedule # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/registries/triggers/manual.py b/surfsense_backend/app/automations/registries/triggers/manual.py new file mode 100644 index 000000000..173c38655 --- /dev/null +++ b/surfsense_backend/app/automations/registries/triggers/manual.py @@ -0,0 +1,17 @@ +"""Built-in ``manual`` trigger. Self-registers at import time.""" + +from __future__ import annotations + +from app.automations.schemas.triggers import ManualTriggerParams + +from .store import register_trigger +from .types import TriggerDefinition + +MANUAL_TRIGGER = TriggerDefinition( + type="manual", + description="Fire on a user-initiated 'Run now' invocation.", + params_schema=ManualTriggerParams.model_json_schema(), + payload_schema={"type": "object"}, +) + +register_trigger(MANUAL_TRIGGER) diff --git a/surfsense_backend/app/automations/registries/triggers/schedule.py b/surfsense_backend/app/automations/registries/triggers/schedule.py new file mode 100644 index 000000000..0a6575f39 --- /dev/null +++ b/surfsense_backend/app/automations/registries/triggers/schedule.py @@ -0,0 +1,21 @@ +"""Built-in ``schedule`` trigger. Self-registers at import time.""" + +from __future__ import annotations + +from app.automations.schemas.triggers import ScheduleTriggerParams + +from .store import register_trigger +from .types import TriggerDefinition + +SCHEDULE_TRIGGER = TriggerDefinition( + type="schedule", + description="Fire on a cron schedule in a given timezone.", + params_schema=ScheduleTriggerParams.model_json_schema(), + payload_schema={ + "type": "object", + "additionalProperties": False, + "properties": {}, + }, +) + +register_trigger(SCHEDULE_TRIGGER) From b4e5bf95a46a0f9025403be1c587f2822d18030e Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 14:23:17 +0200 Subject: [PATCH 101/198] feat(automation): add template filter and test allowlist --- .../app/automations/templating/allowlist.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 surfsense_backend/app/automations/templating/allowlist.py diff --git a/surfsense_backend/app/automations/templating/allowlist.py b/surfsense_backend/app/automations/templating/allowlist.py new file mode 100644 index 000000000..ed0103c8f --- /dev/null +++ b/surfsense_backend/app/automations/templating/allowlist.py @@ -0,0 +1,31 @@ +"""Filter and test names admitted into the sandboxed environment.""" + +from __future__ import annotations + +ALLOWED_FILTERS: tuple[str, ...] = ( + "default", + "first", + "join", + "last", + "length", + "lower", + "replace", + "reverse", + "sort", + "tojson", + "trim", + "truncate", + "upper", + "date", + "slugify", +) + +ALLOWED_TESTS: tuple[str, ...] = ( + "defined", + "none", + "number", + "string", + "mapping", + "sequence", + "boolean", +) From 08e94ac5ca4056709220bfb9808b43966608610d Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 14:23:17 +0200 Subject: [PATCH 102/198] feat(automation): add custom template filters --- .../app/automations/templating/filters.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 surfsense_backend/app/automations/templating/filters.py diff --git a/surfsense_backend/app/automations/templating/filters.py b/surfsense_backend/app/automations/templating/filters.py new file mode 100644 index 000000000..65f66eb37 --- /dev/null +++ b/surfsense_backend/app/automations/templating/filters.py @@ -0,0 +1,29 @@ +"""Custom Jinja filters registered into the sandboxed environment.""" + +from __future__ import annotations + +import re +from typing import Any + + +def filter_date(value: Any, fmt: str = "%Y-%m-%d") -> str: + """Format a datetime-like value with ``strftime``. Strings pass through.""" + if value is None: + return "" + if isinstance(value, str): + return value + if hasattr(value, "strftime"): + return value.strftime(fmt) + raise ValueError(f"date filter requires datetime-like, got {type(value).__name__}") + + +_SLUG_NONALNUM = re.compile(r"[^a-z0-9]+") +_SLUG_DASHES = re.compile(r"-+") + + +def filter_slugify(value: Any) -> str: + """Lowercase, replace non-alphanumerics with hyphens, collapse and trim.""" + s = str(value).lower() + s = _SLUG_NONALNUM.sub("-", s) + s = _SLUG_DASHES.sub("-", s) + return s.strip("-") From 8345e79f6d71c1b7355038c1e1302bd51f042fec Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 14:23:17 +0200 Subject: [PATCH 103/198] feat(automation): add sandboxed template environment --- .../app/automations/templating/environment.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 surfsense_backend/app/automations/templating/environment.py diff --git a/surfsense_backend/app/automations/templating/environment.py b/surfsense_backend/app/automations/templating/environment.py new file mode 100644 index 000000000..6ac5f7361 --- /dev/null +++ b/surfsense_backend/app/automations/templating/environment.py @@ -0,0 +1,43 @@ +"""SandboxedEnvironment construction with the audited filter/test allowlist.""" + +from __future__ import annotations + +import json +from datetime import datetime +from typing import Any + +from jinja2 import StrictUndefined +from jinja2.sandbox import SandboxedEnvironment + +from .allowlist import ALLOWED_FILTERS, ALLOWED_TESTS +from .filters import filter_date, filter_slugify + + +def _finalize(value: Any) -> Any: + """Stringify common non-string values at output sites.""" + if value is None: + return "" + if isinstance(value, str): + return value + if isinstance(value, datetime): + return value.isoformat() + if isinstance(value, list | dict): + return json.dumps(value, ensure_ascii=False, default=str) + return value + + +def _build_env() -> SandboxedEnvironment: + env = SandboxedEnvironment( + autoescape=False, + undefined=StrictUndefined, + finalize=_finalize, + ) + env.globals.clear() + env.filters = {k: v for k, v in env.filters.items() if k in ALLOWED_FILTERS} + env.filters["date"] = filter_date + env.filters["slugify"] = filter_slugify + env.tests = {k: v for k, v in env.tests.items() if k in ALLOWED_TESTS} + return env + + +ENV: SandboxedEnvironment = _build_env() From de6da1b775708edd11e370e90d786604162527f4 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 14:23:17 +0200 Subject: [PATCH 104/198] feat(automation): add template render and predicate evaluation --- .../app/automations/templating/render.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 surfsense_backend/app/automations/templating/render.py diff --git a/surfsense_backend/app/automations/templating/render.py b/surfsense_backend/app/automations/templating/render.py new file mode 100644 index 000000000..374095cd2 --- /dev/null +++ b/surfsense_backend/app/automations/templating/render.py @@ -0,0 +1,18 @@ +"""Render templates and evaluate predicates against the sandboxed environment.""" + +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any + +from .environment import ENV + + +def render_template(template: str, context: Mapping[str, Any]) -> str: + """Render ``template`` with ``context``.""" + return ENV.from_string(template).render(**context) + + +def evaluate_predicate(expression: str, context: Mapping[str, Any]) -> bool: + """Evaluate a Jinja expression (not a template body) and coerce to bool.""" + return bool(ENV.compile_expression(expression)(**context)) From cb42b3a84f7b03ab611b233cbfd3c67f6e9cf67c Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 14:23:18 +0200 Subject: [PATCH 105/198] feat(automation): add template run context builder --- .../app/automations/templating/__init__.py | 12 ++++++ .../app/automations/templating/context.py | 41 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 surfsense_backend/app/automations/templating/__init__.py create mode 100644 surfsense_backend/app/automations/templating/context.py diff --git a/surfsense_backend/app/automations/templating/__init__.py b/surfsense_backend/app/automations/templating/__init__.py new file mode 100644 index 000000000..8a00ec5ff --- /dev/null +++ b/surfsense_backend/app/automations/templating/__init__.py @@ -0,0 +1,12 @@ +"""Sandboxed template engine for automation definitions.""" + +from __future__ import annotations + +from .context import build_run_context +from .render import evaluate_predicate, render_template + +__all__ = [ + "build_run_context", + "evaluate_predicate", + "render_template", +] diff --git a/surfsense_backend/app/automations/templating/context.py b/surfsense_backend/app/automations/templating/context.py new file mode 100644 index 000000000..3ca87694c --- /dev/null +++ b/surfsense_backend/app/automations/templating/context.py @@ -0,0 +1,41 @@ +"""Builder for the ``{run, inputs, steps}`` namespace exposed to every template.""" + +from __future__ import annotations + +from collections.abc import Mapping +from datetime import datetime +from typing import Any + + +def build_run_context( + *, + run_id: int, + automation_id: int, + automation_name: str | None, + automation_version: int | None, + search_space_id: int | None, + creator_id: Any, + trigger_id: int | None, + trigger_type: str | None, + started_at: datetime | None, + attempt: int, + resolved_inputs: Mapping[str, Any], + step_outputs: Mapping[str, Any], +) -> dict[str, Any]: + """Build the ``{run, inputs, steps}`` namespace exposed to every template.""" + return { + "run": { + "id": run_id, + "automation_id": automation_id, + "automation_name": automation_name, + "automation_version": automation_version, + "search_space_id": search_space_id, + "creator_id": creator_id, + "trigger_id": trigger_id, + "trigger_type": trigger_type, + "started_at": started_at, + "attempt": attempt, + }, + "inputs": dict(resolved_inputs), + "steps": dict(step_outputs), + } From 8b87d179e92eb52b247e3e910074b15d6f54ade9 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:02:36 +0200 Subject: [PATCH 106/198] feat(automation): add recursive render_value to templating --- .../app/automations/templating/__init__.py | 3 ++- .../app/automations/templating/render.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/surfsense_backend/app/automations/templating/__init__.py b/surfsense_backend/app/automations/templating/__init__.py index 8a00ec5ff..1df1809c7 100644 --- a/surfsense_backend/app/automations/templating/__init__.py +++ b/surfsense_backend/app/automations/templating/__init__.py @@ -3,10 +3,11 @@ from __future__ import annotations from .context import build_run_context -from .render import evaluate_predicate, render_template +from .render import evaluate_predicate, render_template, render_value __all__ = [ "build_run_context", "evaluate_predicate", "render_template", + "render_value", ] diff --git a/surfsense_backend/app/automations/templating/render.py b/surfsense_backend/app/automations/templating/render.py index 374095cd2..42721ddeb 100644 --- a/surfsense_backend/app/automations/templating/render.py +++ b/surfsense_backend/app/automations/templating/render.py @@ -16,3 +16,14 @@ def render_template(template: str, context: Mapping[str, Any]) -> str: def evaluate_predicate(expression: str, context: Mapping[str, Any]) -> bool: """Evaluate a Jinja expression (not a template body) and coerce to bool.""" return bool(ENV.compile_expression(expression)(**context)) + + +def render_value(value: Any, context: Mapping[str, Any]) -> Any: + """Recursively render every string in a JSON-like value structure.""" + if isinstance(value, str): + return render_template(value, context) + if isinstance(value, dict): + return {k: render_value(v, context) for k, v in value.items()} + if isinstance(value, list): + return [render_value(v, context) for v in value] + return value From 924a82c0b1b92e7bca10a2f4e7f5dfd5e39b7fcc Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:02:36 +0200 Subject: [PATCH 107/198] feat(automation): add retry policy helper --- .../app/automations/runtime/retries.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 surfsense_backend/app/automations/runtime/retries.py diff --git a/surfsense_backend/app/automations/runtime/retries.py b/surfsense_backend/app/automations/runtime/retries.py new file mode 100644 index 000000000..d5bfb15ca --- /dev/null +++ b/surfsense_backend/app/automations/runtime/retries.py @@ -0,0 +1,36 @@ +"""Retry policy enforcement for action handlers.""" + +from __future__ import annotations + +import asyncio +from collections.abc import Awaitable, Callable + + +async def with_retries[T]( + coro_factory: Callable[[], Awaitable[T]], + *, + max_retries: int, + backoff: str, + timeout: int | None, +) -> tuple[T, int]: + """Call ``coro_factory`` up to ``1 + max_retries`` times. Return ``(result, attempts)``.""" + total = 1 + max(0, max_retries) + for attempt in range(1, total + 1): + try: + coro = coro_factory() + if timeout is not None and timeout > 0: + return await asyncio.wait_for(coro, timeout=timeout), attempt + return await coro, attempt + except Exception: + if attempt >= total: + raise + await asyncio.sleep(_backoff_seconds(backoff, attempt)) + raise RuntimeError("with_retries exhausted without raising or returning") + + +def _backoff_seconds(strategy: str, attempt: int) -> float: + if strategy == "exponential": + return float(2 ** (attempt - 1)) + if strategy == "linear": + return float(attempt) + return 0.0 From f71a02db2f27fe56467794a0f53a6d331414a273 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:02:36 +0200 Subject: [PATCH 108/198] feat(automation): add automation run repository --- .../app/automations/runtime/repository.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 surfsense_backend/app/automations/runtime/repository.py diff --git a/surfsense_backend/app/automations/runtime/repository.py b/surfsense_backend/app/automations/runtime/repository.py new file mode 100644 index 000000000..a8bdbc55a --- /dev/null +++ b/surfsense_backend/app/automations/runtime/repository.py @@ -0,0 +1,62 @@ +"""Persistence operations on ``AutomationRun``. Pure SQL, no business logic.""" + +from __future__ import annotations + +from datetime import UTC, datetime +from typing import Any + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import selectinload + +from app.automations.persistence.enums.run_status import RunStatus +from app.automations.persistence.models.run import AutomationRun + + +async def load_run(session: AsyncSession, run_id: int) -> AutomationRun | None: + """Load a run with its automation and trigger eagerly loaded.""" + stmt = ( + select(AutomationRun) + .where(AutomationRun.id == run_id) + .options( + selectinload(AutomationRun.automation), + selectinload(AutomationRun.trigger), + ) + ) + result = await session.execute(stmt) + return result.scalar_one_or_none() + + +async def mark_running(session: AsyncSession, run: AutomationRun) -> None: + run.status = RunStatus.RUNNING + run.started_at = datetime.now(UTC) + await session.flush() + + +async def mark_succeeded(session: AsyncSession, run: AutomationRun) -> None: + run.status = RunStatus.SUCCEEDED + run.finished_at = datetime.now(UTC) + await session.flush() + + +async def mark_failed( + session: AsyncSession, + run: AutomationRun, + error: dict[str, Any] | None, +) -> None: + run.status = RunStatus.FAILED + run.finished_at = datetime.now(UTC) + run.error = error + await session.flush() + + +async def append_step_result( + session: AsyncSession, + run: AutomationRun, + step_result: dict[str, Any], +) -> None: + """Append one step result. Reassigns the list so SQLAlchemy detects the change.""" + current = list(run.step_results or []) + current.append(step_result) + run.step_results = current + await session.flush() From 0a329e5a69bd2604471730f9108a4fa9ee8d5a13 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:02:36 +0200 Subject: [PATCH 109/198] feat(automation): add per-step execution --- .../app/automations/runtime/step.py | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 surfsense_backend/app/automations/runtime/step.py diff --git a/surfsense_backend/app/automations/runtime/step.py b/surfsense_backend/app/automations/runtime/step.py new file mode 100644 index 000000000..07b894a91 --- /dev/null +++ b/surfsense_backend/app/automations/runtime/step.py @@ -0,0 +1,92 @@ +"""Execute one plan step: when-predicate, params render, handler dispatch, retries.""" + +from __future__ import annotations + +from collections.abc import Mapping +from datetime import UTC, datetime +from typing import Any + +from app.automations.registries import get_action +from app.automations.schemas.definition.plan_step import PlanStep +from app.automations.templating import evaluate_predicate, render_value + +from .retries import with_retries + + +async def execute_step( + *, + step: PlanStep, + template_context: Mapping[str, Any], + default_max_retries: int, + default_retry_backoff: str, + default_timeout_seconds: int, +) -> dict[str, Any]: + """Run one step and return its structured result entry.""" + started_at = datetime.now(UTC) + + if step.when is not None: + try: + should_run = evaluate_predicate(step.when, template_context) + except Exception as exc: + return _result(step, "failed", started_at, attempts=0, error=_error(exc, "when")) + if not should_run: + return _result(step, "skipped", started_at, attempts=0) + + try: + resolved_params = render_value(step.params, template_context) + except Exception as exc: + return _result(step, "failed", started_at, attempts=0, error=_error(exc, "render")) + + action = get_action(step.action) + if action is None: + return _result( + step, + "failed", + started_at, + attempts=0, + error={"message": f"action not registered: {step.action}", "type": "ActionNotFound"}, + ) + + max_retries = step.max_retries if step.max_retries is not None else default_max_retries + timeout = step.timeout_seconds or default_timeout_seconds + + try: + result, attempts = await with_retries( + lambda: action.handler(resolved_params), + max_retries=max_retries, + backoff=default_retry_backoff, + timeout=timeout, + ) + except Exception as exc: + return _result(step, "failed", started_at, attempts=max_retries + 1, error=_error(exc)) + + return _result(step, "succeeded", started_at, attempts=attempts, result=result) + + +def _result( + step: PlanStep, + status: str, + started_at: datetime, + *, + attempts: int, + result: Any = None, + error: dict[str, Any] | None = None, +) -> dict[str, Any]: + entry: dict[str, Any] = { + "step_id": step.step_id, + "action": step.action, + "status": status, + "started_at": started_at.isoformat(), + "finished_at": datetime.now(UTC).isoformat(), + "attempts": attempts, + } + if result is not None: + entry["result"] = result + if error is not None: + entry["error"] = error + return entry + + +def _error(exc: Exception, phase: str | None = None) -> dict[str, Any]: + msg = f"{phase}: {exc}" if phase else str(exc) + return {"message": msg, "type": type(exc).__name__} From d3cda121917268f198a927ebfcff633c2b4cd8e3 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:02:36 +0200 Subject: [PATCH 110/198] feat(automation): add automation run executor --- .../app/automations/runtime/executor.py | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 surfsense_backend/app/automations/runtime/executor.py diff --git a/surfsense_backend/app/automations/runtime/executor.py b/surfsense_backend/app/automations/runtime/executor.py new file mode 100644 index 000000000..51c4417e3 --- /dev/null +++ b/surfsense_backend/app/automations/runtime/executor.py @@ -0,0 +1,105 @@ +"""Walk an ``AutomationRun``'s snapshot plan to terminal state.""" + +from __future__ import annotations + +from typing import Any + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.persistence.enums.run_status import RunStatus +from app.automations.persistence.models.run import AutomationRun +from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.templating import build_run_context + +from . import repository +from .step import execute_step + + +async def execute_run(session: AsyncSession, run_id: int) -> None: + """Load run ``run_id`` and execute its snapshot plan to a terminal state.""" + run = await repository.load_run(session, run_id) + if run is None: + raise ValueError(f"automation_run {run_id} not found") + + if run.status != RunStatus.PENDING: + return + + try: + definition = AutomationDefinition.model_validate(run.definition_snapshot) + except Exception as exc: + await repository.mark_failed( + session, + run, + {"message": f"definition_snapshot invalid: {exc}", "type": type(exc).__name__}, + ) + await session.commit() + return + + await repository.mark_running(session, run) + await session.commit() + + step_outputs: dict[str, Any] = {} + + for step in definition.plan: + ctx = _build_ctx(run, step_outputs) + result = await execute_step( + step=step, + template_context=ctx, + default_max_retries=definition.execution.max_retries, + default_retry_backoff=definition.execution.retry_backoff, + default_timeout_seconds=definition.execution.timeout_seconds, + ) + await repository.append_step_result(session, run, result) + await session.commit() + + if result["status"] == "failed": + await _run_on_failure(session, run, definition) + await repository.mark_failed(session, run, result.get("error")) + await session.commit() + return + + if result["status"] == "succeeded": + step_outputs[step.output_as or step.step_id] = result.get("result") + + await repository.mark_succeeded(session, run) + await session.commit() + + +async def _run_on_failure( + session: AsyncSession, + run: AutomationRun, + definition: AutomationDefinition, +) -> None: + """Run the on_failure steps. Their failures don't recurse into more on_failure.""" + if not definition.execution.on_failure: + return + ctx = _build_ctx(run, step_outputs={}) + for step in definition.execution.on_failure: + result = await execute_step( + step=step, + template_context=ctx, + default_max_retries=definition.execution.max_retries, + default_retry_backoff=definition.execution.retry_backoff, + default_timeout_seconds=definition.execution.timeout_seconds, + ) + await repository.append_step_result(session, run, result) + await session.commit() + + +def _build_ctx(run: AutomationRun, step_outputs: dict[str, Any]) -> dict[str, Any]: + automation = run.automation + trigger = run.trigger + return build_run_context( + run_id=run.id, + automation_id=run.automation_id, + automation_name=automation.name if automation else None, + automation_version=automation.version if automation else None, + search_space_id=automation.search_space_id if automation else None, + creator_id=automation.created_by_user_id if automation else None, + trigger_id=run.trigger_id, + trigger_type=trigger.type.value if trigger else None, + started_at=run.started_at, + attempt=1, + resolved_inputs=run.resolved_inputs or {}, + step_outputs=step_outputs, + ) From 273b98f350981dcd348527573d0569ab8e192590 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:02:36 +0200 Subject: [PATCH 111/198] feat(automation): expose runtime package surface --- surfsense_backend/app/automations/runtime/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 surfsense_backend/app/automations/runtime/__init__.py diff --git a/surfsense_backend/app/automations/runtime/__init__.py b/surfsense_backend/app/automations/runtime/__init__.py new file mode 100644 index 000000000..0650882b2 --- /dev/null +++ b/surfsense_backend/app/automations/runtime/__init__.py @@ -0,0 +1,7 @@ +"""Automation run executor: plan walker, step dispatch, retries, persistence.""" + +from __future__ import annotations + +from .executor import execute_run + +__all__ = ["execute_run"] From b26bf0bbcf0858839e188cacee4b7ace6a27b50c Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:02:36 +0200 Subject: [PATCH 112/198] feat(automation): register automation run celery task --- .../app/automations/tasks/__init__.py | 3 ++ .../app/automations/tasks/execute_run.py | 33 +++++++++++++++++++ surfsense_backend/app/celery_app.py | 1 + 3 files changed, 37 insertions(+) create mode 100644 surfsense_backend/app/automations/tasks/__init__.py create mode 100644 surfsense_backend/app/automations/tasks/execute_run.py diff --git a/surfsense_backend/app/automations/tasks/__init__.py b/surfsense_backend/app/automations/tasks/__init__.py new file mode 100644 index 000000000..6fe0d62e8 --- /dev/null +++ b/surfsense_backend/app/automations/tasks/__init__.py @@ -0,0 +1,3 @@ +"""Celery task wrappers for the automation runtime.""" + +from __future__ import annotations diff --git a/surfsense_backend/app/automations/tasks/execute_run.py b/surfsense_backend/app/automations/tasks/execute_run.py new file mode 100644 index 000000000..5fc84698b --- /dev/null +++ b/surfsense_backend/app/automations/tasks/execute_run.py @@ -0,0 +1,33 @@ +"""Celery task that runs one automation. Thin wrapper over ``runtime.executor``.""" + +from __future__ import annotations + +import logging + +from app.automations.runtime import execute_run +from app.celery_app import celery_app +from app.tasks.celery_tasks import ( + get_celery_session_maker, + run_async_celery_task, +) + +logger = logging.getLogger(__name__) + +TASK_NAME = "automation_run_execute" + + +@celery_app.task(name=TASK_NAME, bind=True) +def automation_run_execute(self, run_id: int) -> None: # noqa: ARG001 — Celery bind + """Execute one ``AutomationRun``. Idempotent: terminal runs no-op.""" + return run_async_celery_task(lambda: _impl(run_id)) + + +async def _impl(run_id: int) -> None: + session_maker = get_celery_session_maker() + async with session_maker() as session: + try: + await execute_run(session, run_id) + except Exception: + logger.exception("automation_run %d failed unexpectedly", run_id) + await session.rollback() + raise diff --git a/surfsense_backend/app/celery_app.py b/surfsense_backend/app/celery_app.py index 5b45baca1..569178239 100644 --- a/surfsense_backend/app/celery_app.py +++ b/surfsense_backend/app/celery_app.py @@ -188,6 +188,7 @@ celery_app = Celery( "app.tasks.celery_tasks.document_reindex_tasks", "app.tasks.celery_tasks.stale_notification_cleanup_task", "app.tasks.celery_tasks.stripe_reconciliation_task", + "app.automations.tasks.execute_run", ], ) From 1366c8a711b5cfd17caaa3f328d130bc7038ad27 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:30:34 +0200 Subject: [PATCH 113/198] feat(rbac): add automations permission family --- surfsense_backend/app/db.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py index 71466495b..ac880ded5 100644 --- a/surfsense_backend/app/db.py +++ b/surfsense_backend/app/db.py @@ -439,6 +439,13 @@ class Permission(StrEnum): PUBLIC_SHARING_CREATE = "public_sharing:create" PUBLIC_SHARING_DELETE = "public_sharing:delete" + # Automations + AUTOMATIONS_CREATE = "automations:create" + AUTOMATIONS_READ = "automations:read" + AUTOMATIONS_UPDATE = "automations:update" + AUTOMATIONS_DELETE = "automations:delete" + AUTOMATIONS_EXECUTE = "automations:execute" + # Full access wildcard FULL_ACCESS = "*" @@ -494,6 +501,11 @@ DEFAULT_ROLE_PERMISSIONS = { # Public Sharing (can create and view, no delete) Permission.PUBLIC_SHARING_VIEW.value, Permission.PUBLIC_SHARING_CREATE.value, + # Automations (no delete) + Permission.AUTOMATIONS_CREATE.value, + Permission.AUTOMATIONS_READ.value, + Permission.AUTOMATIONS_UPDATE.value, + Permission.AUTOMATIONS_EXECUTE.value, ], "Viewer": [ # Documents (read only) @@ -525,6 +537,8 @@ DEFAULT_ROLE_PERMISSIONS = { Permission.SETTINGS_VIEW.value, # Public Sharing (view only) Permission.PUBLIC_SHARING_VIEW.value, + # Automations (read only) + Permission.AUTOMATIONS_READ.value, ], } From 3bb02d8889559d0cd46f661e7f51f576a39600c9 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:30:41 +0200 Subject: [PATCH 114/198] feat(automations): add manual dispatch service --- .../app/automations/dispatch/__init__.py | 8 ++ .../app/automations/dispatch/manual.py | 107 ++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 surfsense_backend/app/automations/dispatch/__init__.py create mode 100644 surfsense_backend/app/automations/dispatch/manual.py diff --git a/surfsense_backend/app/automations/dispatch/__init__.py b/surfsense_backend/app/automations/dispatch/__init__.py new file mode 100644 index 000000000..4a549a4ce --- /dev/null +++ b/surfsense_backend/app/automations/dispatch/__init__.py @@ -0,0 +1,8 @@ +"""Public dispatch surface for firing automations.""" + +from .manual import DispatchError, dispatch_manual_run + +__all__ = [ + "DispatchError", + "dispatch_manual_run", +] diff --git a/surfsense_backend/app/automations/dispatch/manual.py b/surfsense_backend/app/automations/dispatch/manual.py new file mode 100644 index 000000000..221d6a3e2 --- /dev/null +++ b/surfsense_backend/app/automations/dispatch/manual.py @@ -0,0 +1,107 @@ +"""Manual ``Run now`` dispatch: validate inputs, snapshot the definition, enqueue.""" + +from __future__ import annotations + +from typing import Any + +import jsonschema +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.persistence.enums.automation_status import AutomationStatus +from app.automations.persistence.enums.run_status import RunStatus +from app.automations.persistence.enums.trigger_type import TriggerType +from app.automations.persistence.models.automation import Automation +from app.automations.persistence.models.run import AutomationRun +from app.automations.persistence.models.trigger import AutomationTrigger +from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.tasks.execute_run import automation_run_execute + + +class DispatchError(Exception): + """A manual dispatch could not proceed (missing trigger, invalid inputs, ...).""" + + +async def dispatch_manual_run( + *, + session: AsyncSession, + automation_id: int, + payload: dict[str, Any] | None, +) -> AutomationRun: + """Validate, snapshot, persist, and enqueue an ``AutomationRun``.""" + automation = await _load_automation(session, automation_id) + if automation is None: + raise DispatchError(f"automation {automation_id} not found") + + if automation.status != AutomationStatus.ACTIVE: + raise DispatchError( + f"automation {automation_id} is {automation.status.value}, not active" + ) + + try: + definition = AutomationDefinition.model_validate(automation.definition) + except Exception as exc: + raise DispatchError(f"invalid automation definition: {exc}") from exc + + trigger = await _find_manual_trigger(session, automation_id) + if trigger is None: + raise DispatchError( + f"automation {automation_id} has no enabled manual trigger" + ) + + resolved_inputs = _validate_inputs(definition, payload or {}) + snapshot = definition.model_dump(mode="json", by_alias=True) + + run = AutomationRun( + automation_id=automation_id, + trigger_id=trigger.id, + status=RunStatus.PENDING, + definition_snapshot=snapshot, + trigger_payload=payload, + resolved_inputs=resolved_inputs, + step_results=[], + artifacts=[], + ) + session.add(run) + await session.commit() + await session.refresh(run) + + automation_run_execute.apply_async( + args=[run.id], + time_limit=definition.execution.timeout_seconds, + ) + return run + + +async def _load_automation( + session: AsyncSession, automation_id: int +) -> Automation | None: + stmt = select(Automation).where(Automation.id == automation_id) + return (await session.execute(stmt)).scalar_one_or_none() + + +async def _find_manual_trigger( + session: AsyncSession, automation_id: int +) -> AutomationTrigger | None: + stmt = ( + select(AutomationTrigger) + .where( + AutomationTrigger.automation_id == automation_id, + AutomationTrigger.type == TriggerType.MANUAL, + AutomationTrigger.enabled.is_(True), + ) + .limit(1) + ) + return (await session.execute(stmt)).scalar_one_or_none() + + +def _validate_inputs( + definition: AutomationDefinition, payload: dict[str, Any] +) -> dict[str, Any]: + if definition.inputs is None or not definition.inputs.schema_: + return {} + try: + jsonschema.validate(instance=payload, schema=definition.inputs.schema_) + except jsonschema.ValidationError as exc: + raise DispatchError(f"inputs: {exc.message}") from exc + return payload From cfbe2a7fe025cbf476f3ba1ada33ef3dcdcdbd35 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:30:45 +0200 Subject: [PATCH 115/198] feat(automations): expose POST /automations/{id}/run --- surfsense_backend/app/routes/__init__.py | 2 + .../app/routes/automations_routes.py | 55 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 surfsense_backend/app/routes/automations_routes.py diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index ec4d1650f..1d3ca2141 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -7,6 +7,7 @@ from .agent_revert_route import router as agent_revert_router from .airtable_add_connector_route import ( router as airtable_add_connector_router, ) +from .automations_routes import router as automations_router from .chat_comments_routes import router as chat_comments_router from .circleback_webhook_route import router as circleback_webhook_router from .clickup_add_connector_route import router as clickup_add_connector_router @@ -119,3 +120,4 @@ router.include_router(youtube_router) # YouTube playlist resolution router.include_router(prompts_router) router.include_router(memory_router) # User personal memory (memory.md style) router.include_router(team_memory_router) # Search-space team memory +router.include_router(automations_router) # Automations (manual run-now) diff --git a/surfsense_backend/app/routes/automations_routes.py b/surfsense_backend/app/routes/automations_routes.py new file mode 100644 index 000000000..02c019625 --- /dev/null +++ b/surfsense_backend/app/routes/automations_routes.py @@ -0,0 +1,55 @@ +"""Routes for automations. v1: manual ``Run now``.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body, Depends, HTTPException +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.dispatch import DispatchError, dispatch_manual_run +from app.automations.persistence.models.automation import Automation +from app.db import Permission, User, get_async_session +from app.users import current_active_user +from app.utils.rbac import check_permission + +router = APIRouter() + + +@router.post("/automations/{automation_id}/run") +async def run_automation_now( + automation_id: int, + payload: dict[str, Any] | None = Body(default=None), + session: AsyncSession = Depends(get_async_session), + user: User = Depends(current_active_user), +) -> dict[str, Any]: + """Fire an automation manually. Returns the new run id and status.""" + search_space_id = ( + await session.execute( + select(Automation.search_space_id).where(Automation.id == automation_id) + ) + ).scalar_one_or_none() + if search_space_id is None: + raise HTTPException( + status_code=404, detail=f"automation {automation_id} not found" + ) + + await check_permission( + session, + user, + search_space_id, + Permission.AUTOMATIONS_EXECUTE.value, + "You don't have permission to execute automations in this search space", + ) + + try: + run = await dispatch_manual_run( + session=session, + automation_id=automation_id, + payload=payload, + ) + except DispatchError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc + + return {"run_id": run.id, "status": run.status.value} From f646b5cbab2d69c061b70caca8664e33dcaf379c Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 15:37:25 +0200 Subject: [PATCH 116/198] feat(rbac): backfill automations permissions on existing roles --- ...45_add_automations_permissions_to_roles.py | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 surfsense_backend/alembic/versions/145_add_automations_permissions_to_roles.py diff --git a/surfsense_backend/alembic/versions/145_add_automations_permissions_to_roles.py b/surfsense_backend/alembic/versions/145_add_automations_permissions_to_roles.py new file mode 100644 index 000000000..779656b44 --- /dev/null +++ b/surfsense_backend/alembic/versions/145_add_automations_permissions_to_roles.py @@ -0,0 +1,87 @@ +"""Add automations permissions to existing Editor/Viewer roles + +Revision ID: 145 +Revises: 144 +Create Date: 2026-05-27 + +Owners already have ``*`` and need no backfill. Custom (non-system) roles +are left untouched on purpose: workspace admins manage those explicitly. +""" + +from collections.abc import Sequence + +from sqlalchemy import text + +from alembic import op + +revision: str = "145" +down_revision: str | None = "144" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +_EDITOR_PERMISSIONS = ( + "automations:create", + "automations:read", + "automations:update", + "automations:execute", +) +_VIEWER_PERMISSIONS = ("automations:read",) + + +def upgrade(): + connection = op.get_bind() + + for permission in _EDITOR_PERMISSIONS: + connection.execute( + text( + """ + UPDATE search_space_roles + SET permissions = array_append(permissions, :permission) + WHERE name = 'Editor' + AND NOT (:permission = ANY(permissions)) + """ + ), + {"permission": permission}, + ) + + for permission in _VIEWER_PERMISSIONS: + connection.execute( + text( + """ + UPDATE search_space_roles + SET permissions = array_append(permissions, :permission) + WHERE name = 'Viewer' + AND NOT (:permission = ANY(permissions)) + """ + ), + {"permission": permission}, + ) + + +def downgrade(): + connection = op.get_bind() + + for permission in _EDITOR_PERMISSIONS: + connection.execute( + text( + """ + UPDATE search_space_roles + SET permissions = array_remove(permissions, :permission) + WHERE name = 'Editor' + """ + ), + {"permission": permission}, + ) + + for permission in _VIEWER_PERMISSIONS: + connection.execute( + text( + """ + UPDATE search_space_roles + SET permissions = array_remove(permissions, :permission) + WHERE name = 'Viewer' + """ + ), + {"permission": permission}, + ) From 7ec3468113fe28ffb7c634aee790bfd91c625766 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 16:29:32 +0200 Subject: [PATCH 117/198] refactor(automations): bind action handlers via ActionContext factory --- .../app/automations/registries/__init__.py | 4 +++ .../registries/actions/__init__.py | 4 ++- .../registries/actions/agent_task.py | 17 +++++++---- .../automations/registries/actions/types.py | 20 +++++++++++-- .../app/automations/runtime/executor.py | 29 +++++++++++++++---- .../app/automations/runtime/step.py | 6 +++- 6 files changed, 65 insertions(+), 15 deletions(-) diff --git a/surfsense_backend/app/automations/registries/__init__.py b/surfsense_backend/app/automations/registries/__init__.py index f497caf59..f6af3817b 100644 --- a/surfsense_backend/app/automations/registries/__init__.py +++ b/surfsense_backend/app/automations/registries/__init__.py @@ -3,8 +3,10 @@ from __future__ import annotations from .actions import ( + ActionContext, ActionDefinition, ActionHandler, + ActionHandlerFactory, all_actions, get_action, register_action, @@ -17,8 +19,10 @@ from .triggers import ( ) __all__ = [ + "ActionContext", "ActionDefinition", "ActionHandler", + "ActionHandlerFactory", "TriggerDefinition", "all_actions", "all_triggers", diff --git a/surfsense_backend/app/automations/registries/actions/__init__.py b/surfsense_backend/app/automations/registries/actions/__init__.py index 68e507133..b95c634f2 100644 --- a/surfsense_backend/app/automations/registries/actions/__init__.py +++ b/surfsense_backend/app/automations/registries/actions/__init__.py @@ -3,11 +3,13 @@ from __future__ import annotations from .store import all_actions, get_action, register_action -from .types import ActionDefinition, ActionHandler +from .types import ActionContext, ActionDefinition, ActionHandler, ActionHandlerFactory __all__ = [ + "ActionContext", "ActionDefinition", "ActionHandler", + "ActionHandlerFactory", "all_actions", "get_action", "register_action", diff --git a/surfsense_backend/app/automations/registries/actions/agent_task.py b/surfsense_backend/app/automations/registries/actions/agent_task.py index 9acc11c2c..beba455cc 100644 --- a/surfsense_backend/app/automations/registries/actions/agent_task.py +++ b/surfsense_backend/app/automations/registries/actions/agent_task.py @@ -7,13 +7,18 @@ from typing import Any from app.automations.schemas.actions import AgentTaskActionParams from .store import register_action -from .types import ActionDefinition +from .types import ActionContext, ActionDefinition, ActionHandler -async def _handle_agent_task(args: dict[str, Any]) -> dict[str, Any]: - """Stub. Validates params; real wiring lands with the executor.""" - AgentTaskActionParams.model_validate(args) - return {"status": "stubbed"} +def _build_handler(ctx: ActionContext) -> ActionHandler: + """Bind run/session context to the agent_task handler. Real wiring lands in Phase 4b.""" + del ctx # ignored by the stub; real handler will consume it + + async def handle(params: dict[str, Any]) -> dict[str, Any]: + AgentTaskActionParams.model_validate(params) + return {"status": "stubbed"} + + return handle AGENT_TASK_ACTION = ActionDefinition( @@ -21,7 +26,7 @@ AGENT_TASK_ACTION = ActionDefinition( name="Agent task", description="Run an agent task with a scoped tool allowlist.", params_schema=AgentTaskActionParams.model_json_schema(), - handler=_handle_agent_task, + build_handler=_build_handler, ) register_action(AGENT_TASK_ACTION) diff --git a/surfsense_backend/app/automations/registries/actions/types.py b/surfsense_backend/app/automations/registries/actions/types.py index 99f94ae7c..433c60841 100644 --- a/surfsense_backend/app/automations/registries/actions/types.py +++ b/surfsense_backend/app/automations/registries/actions/types.py @@ -1,12 +1,28 @@ -"""``ActionDefinition`` dataclass and handler signature.""" +"""``ActionDefinition``, ``ActionContext``, and handler/factory signatures.""" from __future__ import annotations from collections.abc import Awaitable, Callable from dataclasses import dataclass from typing import Any +from uuid import UUID + +from sqlalchemy.ext.asyncio import AsyncSession + + +@dataclass(frozen=True, slots=True) +class ActionContext: + """Per-invocation dependencies bound to an action handler at execute time.""" + + session: AsyncSession + run_id: int + step_id: str + search_space_id: int + creator_user_id: UUID | None + ActionHandler = Callable[[dict[str, Any]], Awaitable[Any]] +ActionHandlerFactory = Callable[[ActionContext], ActionHandler] @dataclass(frozen=True, slots=True) @@ -15,4 +31,4 @@ class ActionDefinition: name: str description: str params_schema: dict[str, Any] - handler: ActionHandler + build_handler: ActionHandlerFactory diff --git a/surfsense_backend/app/automations/runtime/executor.py b/surfsense_backend/app/automations/runtime/executor.py index 51c4417e3..e9e55b02d 100644 --- a/surfsense_backend/app/automations/runtime/executor.py +++ b/surfsense_backend/app/automations/runtime/executor.py @@ -8,7 +8,9 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.automations.persistence.enums.run_status import RunStatus from app.automations.persistence.models.run import AutomationRun +from app.automations.registries.actions.types import ActionContext from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.schemas.definition.plan_step import PlanStep from app.automations.templating import build_run_context from . import repository @@ -41,10 +43,12 @@ async def execute_run(session: AsyncSession, run_id: int) -> None: step_outputs: dict[str, Any] = {} for step in definition.plan: - ctx = _build_ctx(run, step_outputs) + template_ctx = _build_template_ctx(run, step_outputs) + action_ctx = _build_action_ctx(session, run, step) result = await execute_step( step=step, - template_context=ctx, + template_context=template_ctx, + action_context=action_ctx, default_max_retries=definition.execution.max_retries, default_retry_backoff=definition.execution.retry_backoff, default_timeout_seconds=definition.execution.timeout_seconds, @@ -73,11 +77,13 @@ async def _run_on_failure( """Run the on_failure steps. Their failures don't recurse into more on_failure.""" if not definition.execution.on_failure: return - ctx = _build_ctx(run, step_outputs={}) + template_ctx = _build_template_ctx(run, step_outputs={}) for step in definition.execution.on_failure: + action_ctx = _build_action_ctx(session, run, step) result = await execute_step( step=step, - template_context=ctx, + template_context=template_ctx, + action_context=action_ctx, default_max_retries=definition.execution.max_retries, default_retry_backoff=definition.execution.retry_backoff, default_timeout_seconds=definition.execution.timeout_seconds, @@ -86,7 +92,7 @@ async def _run_on_failure( await session.commit() -def _build_ctx(run: AutomationRun, step_outputs: dict[str, Any]) -> dict[str, Any]: +def _build_template_ctx(run: AutomationRun, step_outputs: dict[str, Any]) -> dict[str, Any]: automation = run.automation trigger = run.trigger return build_run_context( @@ -103,3 +109,16 @@ def _build_ctx(run: AutomationRun, step_outputs: dict[str, Any]) -> dict[str, An resolved_inputs=run.resolved_inputs or {}, step_outputs=step_outputs, ) + + +def _build_action_ctx( + session: AsyncSession, run: AutomationRun, step: PlanStep +) -> ActionContext: + automation = run.automation + return ActionContext( + session=session, + run_id=run.id, + step_id=step.step_id, + search_space_id=automation.search_space_id, + creator_user_id=automation.created_by_user_id, + ) diff --git a/surfsense_backend/app/automations/runtime/step.py b/surfsense_backend/app/automations/runtime/step.py index 07b894a91..76e3ba171 100644 --- a/surfsense_backend/app/automations/runtime/step.py +++ b/surfsense_backend/app/automations/runtime/step.py @@ -7,6 +7,7 @@ from datetime import UTC, datetime from typing import Any from app.automations.registries import get_action +from app.automations.registries.actions.types import ActionContext from app.automations.schemas.definition.plan_step import PlanStep from app.automations.templating import evaluate_predicate, render_value @@ -17,6 +18,7 @@ async def execute_step( *, step: PlanStep, template_context: Mapping[str, Any], + action_context: ActionContext, default_max_retries: int, default_retry_backoff: str, default_timeout_seconds: int, @@ -47,12 +49,14 @@ async def execute_step( error={"message": f"action not registered: {step.action}", "type": "ActionNotFound"}, ) + handler = action.build_handler(action_context) + max_retries = step.max_retries if step.max_retries is not None else default_max_retries timeout = step.timeout_seconds or default_timeout_seconds try: result, attempts = await with_retries( - lambda: action.handler(resolved_params), + lambda: handler(resolved_params), max_retries=max_retries, backoff=default_retry_backoff, timeout=timeout, From ce45e110096aa560f5e53393a911d07fdffd0d30 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 17:02:44 +0200 Subject: [PATCH 118/198] feat(automations): wire agent_task to multi_agent_chat with auto-approve loop --- .../app/automations/actions/__init__.py | 1 + .../actions/agent_task/__init__.py | 7 ++ .../actions/agent_task/auto_decide.py | 39 ++++++++ .../actions/agent_task/dependencies.py | 58 +++++++++++ .../automations/actions/agent_task/factory.py | 27 ++++++ .../actions/agent_task/finalize.py | 44 +++++++++ .../automations/actions/agent_task/invoke.py | 97 +++++++++++++++++++ .../registries/actions/agent_task.py | 21 +--- .../automations/schemas/actions/agent_task.py | 22 ++--- 9 files changed, 285 insertions(+), 31 deletions(-) create mode 100644 surfsense_backend/app/automations/actions/__init__.py create mode 100644 surfsense_backend/app/automations/actions/agent_task/__init__.py create mode 100644 surfsense_backend/app/automations/actions/agent_task/auto_decide.py create mode 100644 surfsense_backend/app/automations/actions/agent_task/dependencies.py create mode 100644 surfsense_backend/app/automations/actions/agent_task/factory.py create mode 100644 surfsense_backend/app/automations/actions/agent_task/finalize.py create mode 100644 surfsense_backend/app/automations/actions/agent_task/invoke.py diff --git a/surfsense_backend/app/automations/actions/__init__.py b/surfsense_backend/app/automations/actions/__init__.py new file mode 100644 index 000000000..2a518c1db --- /dev/null +++ b/surfsense_backend/app/automations/actions/__init__.py @@ -0,0 +1 @@ +"""Action implementations. One subpackage per built-in action type.""" diff --git a/surfsense_backend/app/automations/actions/agent_task/__init__.py b/surfsense_backend/app/automations/actions/agent_task/__init__.py new file mode 100644 index 000000000..ecf79b448 --- /dev/null +++ b/surfsense_backend/app/automations/actions/agent_task/__init__.py @@ -0,0 +1,7 @@ +"""``agent_task`` action: spin up multi_agent_chat for one rendered query.""" + +from __future__ import annotations + +from .factory import build_handler + +__all__ = ["build_handler"] diff --git a/surfsense_backend/app/automations/actions/agent_task/auto_decide.py b/surfsense_backend/app/automations/actions/agent_task/auto_decide.py new file mode 100644 index 000000000..357eeb565 --- /dev/null +++ b/surfsense_backend/app/automations/actions/agent_task/auto_decide.py @@ -0,0 +1,39 @@ +"""Synthesize HITL decisions for every pending interrupt (approve-all or reject-all).""" + +from __future__ import annotations + +from typing import Any + + +def build_auto_decisions( + state: Any, decision: str +) -> tuple[dict[str, dict[str, Any]], dict[str, dict[str, Any]]]: + """Return ``(lg_resume_map, surfsense_resume_value)`` covering every pending interrupt. + + ``lg_resume_map`` is keyed by ``Interrupt.id`` for ``Command(resume=...)``; + ``surfsense_resume_value`` is keyed by ``tool_call_id`` for the subagent + middleware bridge. Action count is read from ``value.action_requests`` when + present and falls back to ``1`` for wrapped scalar interrupts. + """ + lg_resume_map: dict[str, dict[str, Any]] = {} + routed: dict[str, dict[str, Any]] = {} + + for interrupt_obj in getattr(state, "interrupts", ()) or (): + value = getattr(interrupt_obj, "value", None) + if not isinstance(value, dict): + continue + interrupt_id = getattr(interrupt_obj, "id", None) + if not isinstance(interrupt_id, str): + continue + + action_requests = value.get("action_requests") + count = len(action_requests) if isinstance(action_requests, list) else 1 + decisions = [{"type": decision} for _ in range(count)] + + lg_resume_map[interrupt_id] = {"decisions": decisions} + + tool_call_id = value.get("tool_call_id") + if isinstance(tool_call_id, str): + routed[tool_call_id] = {"decisions": decisions} + + return lg_resume_map, routed diff --git a/surfsense_backend/app/automations/actions/agent_task/dependencies.py b/surfsense_backend/app/automations/actions/agent_task/dependencies.py new file mode 100644 index 000000000..12273aa0f --- /dev/null +++ b/surfsense_backend/app/automations/actions/agent_task/dependencies.py @@ -0,0 +1,58 @@ +"""Build the per-invocation dependencies the multi_agent_chat factory needs.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.tasks.chat.streaming.flows.shared.llm_bundle import load_llm_bundle +from app.tasks.chat.streaming.flows.shared.pre_stream_setup import ( + get_chat_checkpointer, + setup_connector_and_firecrawl, +) + + +class DependencyError(Exception): + """An external dependency (LLM config, checkpointer, ...) refused to load.""" + + +@dataclass(frozen=True, slots=True) +class AgentDependencies: + """Everything ``create_multi_agent_chat_deep_agent`` needs from the environment.""" + + llm: Any + agent_config: Any + connector_service: Any + firecrawl_api_key: str | None + checkpointer: Any + + +async def build_dependencies( + *, + session: AsyncSession, + search_space_id: int, +) -> AgentDependencies: + """Load the LLM bundle, connector service, and checkpointer for one invoke. + + Uses the search space's default LLM config (``config_id=-1``). Per-step + model overrides land in a future iteration alongside the ``model`` param. + """ + llm, agent_config, err = await load_llm_bundle( + session, config_id=-1, search_space_id=search_space_id + ) + if err is not None or llm is None: + raise DependencyError(err or "failed to load default LLM config") + + connector_service, firecrawl_api_key = await setup_connector_and_firecrawl( + session, search_space_id=search_space_id + ) + checkpointer = await get_chat_checkpointer() + return AgentDependencies( + llm=llm, + agent_config=agent_config, + connector_service=connector_service, + firecrawl_api_key=firecrawl_api_key, + checkpointer=checkpointer, + ) diff --git a/surfsense_backend/app/automations/actions/agent_task/factory.py b/surfsense_backend/app/automations/actions/agent_task/factory.py new file mode 100644 index 000000000..a0d867f38 --- /dev/null +++ b/surfsense_backend/app/automations/actions/agent_task/factory.py @@ -0,0 +1,27 @@ +"""Bind ``ActionContext`` to a callable that runs one ``agent_task`` step.""" + +from __future__ import annotations + +from typing import Any + +from app.automations.registries.actions.types import ( + ActionContext, + ActionHandler, +) +from app.automations.schemas.actions import AgentTaskActionParams + +from .invoke import run_agent_task + + +def build_handler(ctx: ActionContext) -> ActionHandler: + """Return a handler closure that validates params and runs the agent task.""" + + async def handle(params: dict[str, Any]) -> dict[str, Any]: + validated = AgentTaskActionParams.model_validate(params) + return await run_agent_task( + ctx=ctx, + query=validated.query, + auto_approve_all=validated.auto_approve_all, + ) + + return handle diff --git a/surfsense_backend/app/automations/actions/agent_task/finalize.py b/surfsense_backend/app/automations/actions/agent_task/finalize.py new file mode 100644 index 000000000..d5f1f95f6 --- /dev/null +++ b/surfsense_backend/app/automations/actions/agent_task/finalize.py @@ -0,0 +1,44 @@ +"""Extract the agent's final assistant text from the terminal invoke result.""" + +from __future__ import annotations + +from typing import Any + +from langchain_core.messages import AIMessage + + +def extract_final_assistant_message(result: Any) -> str | None: + """Return the last ``AIMessage`` text content, or ``None`` if there isn't one. + + Multi-part messages (content lists) are flattened by concatenating ``text`` + parts in order. Non-string content (tool calls, images) is skipped. + """ + if not isinstance(result, dict): + return None + messages = result.get("messages") + if not isinstance(messages, list): + return None + + for msg in reversed(messages): + if not isinstance(msg, AIMessage): + continue + return _content_to_text(msg.content) + return None + + +def _content_to_text(content: Any) -> str | None: + if isinstance(content, str): + text = content.strip() + return text or None + if isinstance(content, list): + parts: list[str] = [] + for part in content: + if isinstance(part, str): + parts.append(part) + elif isinstance(part, dict) and part.get("type") == "text": + text = part.get("text") + if isinstance(text, str): + parts.append(text) + joined = "".join(parts).strip() + return joined or None + return None diff --git a/surfsense_backend/app/automations/actions/agent_task/invoke.py b/surfsense_backend/app/automations/actions/agent_task/invoke.py new file mode 100644 index 000000000..aa849d7e2 --- /dev/null +++ b/surfsense_backend/app/automations/actions/agent_task/invoke.py @@ -0,0 +1,97 @@ +"""Run one ``agent_task`` invocation: ainvoke + auto-decision resume loop.""" + +from __future__ import annotations + +import time +import uuid +from typing import Any + +from langchain_core.messages import HumanMessage +from langgraph.types import Command + +from app.agents.multi_agent_chat import create_multi_agent_chat_deep_agent +from app.automations.registries.actions.types import ActionContext +from app.db import ChatVisibility, async_session_maker + +from .auto_decide import build_auto_decisions +from .dependencies import build_dependencies +from .finalize import extract_final_assistant_message + +# Cap on HITL resume iterations. The agent should not need this many turns in one +# step; treat overshoot as a runaway and fail the step. +_MAX_RESUMES = 50 + + +async def run_agent_task( + *, + ctx: ActionContext, + query: str, + auto_approve_all: bool, +) -> dict[str, Any]: + """Invoke multi_agent_chat for one rendered query and return its outcome. + + Opens its own DB session so the executor's bookkeeping session isn't tied + up for the entire invocation. The LangGraph ``thread_id`` (a fresh UUID) + is returned as ``agent_session_id`` for later inspection. + """ + agent_session_id = str(uuid.uuid4()) + user_id = str(ctx.creator_user_id) if ctx.creator_user_id else None + decision = "approve" if auto_approve_all else "reject" + + async with async_session_maker() as agent_session: + deps = await build_dependencies( + session=agent_session, + search_space_id=ctx.search_space_id, + ) + + agent = await create_multi_agent_chat_deep_agent( + llm=deps.llm, + search_space_id=ctx.search_space_id, + db_session=agent_session, + connector_service=deps.connector_service, + checkpointer=deps.checkpointer, + user_id=user_id, + thread_id=None, + agent_config=deps.agent_config, + firecrawl_api_key=deps.firecrawl_api_key, + thread_visibility=ChatVisibility.PRIVATE, + ) + + request_id = f"automation:{ctx.run_id}:{ctx.step_id}" + turn_id = f"{request_id}:{int(time.time() * 1000)}" + input_state: dict[str, Any] = { + "messages": [HumanMessage(content=query)], + "search_space_id": ctx.search_space_id, + "request_id": request_id, + "turn_id": turn_id, + } + config: dict[str, Any] = { + "configurable": { + "thread_id": agent_session_id, + "request_id": request_id, + "turn_id": turn_id, + }, + "recursion_limit": 10_000, + } + + result = await agent.ainvoke(input_state, config=config) + + resumes = 0 + while True: + state = await agent.aget_state(config) + if not getattr(state, "interrupts", None): + break + if resumes >= _MAX_RESUMES: + raise RuntimeError( + f"agent_task exceeded {_MAX_RESUMES} HITL resume iterations" + ) + lg_resume_map, routed = build_auto_decisions(state, decision) + config["configurable"]["surfsense_resume_value"] = routed + result = await agent.ainvoke(Command(resume=lg_resume_map), config=config) + resumes += 1 + + return { + "agent_session_id": agent_session_id, + "final_message": extract_final_assistant_message(result), + "resumes": resumes, + } diff --git a/surfsense_backend/app/automations/registries/actions/agent_task.py b/surfsense_backend/app/automations/registries/actions/agent_task.py index beba455cc..51ee0eb7f 100644 --- a/surfsense_backend/app/automations/registries/actions/agent_task.py +++ b/surfsense_backend/app/automations/registries/actions/agent_task.py @@ -2,31 +2,18 @@ from __future__ import annotations -from typing import Any - +from app.automations.actions.agent_task import build_handler from app.automations.schemas.actions import AgentTaskActionParams from .store import register_action -from .types import ActionContext, ActionDefinition, ActionHandler - - -def _build_handler(ctx: ActionContext) -> ActionHandler: - """Bind run/session context to the agent_task handler. Real wiring lands in Phase 4b.""" - del ctx # ignored by the stub; real handler will consume it - - async def handle(params: dict[str, Any]) -> dict[str, Any]: - AgentTaskActionParams.model_validate(params) - return {"status": "stubbed"} - - return handle - +from .types import ActionDefinition AGENT_TASK_ACTION = ActionDefinition( type="agent_task", name="Agent task", - description="Run an agent task with a scoped tool allowlist.", + description="Run a multi_agent_chat turn from an automation step.", params_schema=AgentTaskActionParams.model_json_schema(), - build_handler=_build_handler, + build_handler=build_handler, ) register_action(AGENT_TASK_ACTION) diff --git a/surfsense_backend/app/automations/schemas/actions/agent_task.py b/surfsense_backend/app/automations/schemas/actions/agent_task.py index 348db8095..b0e99a78b 100644 --- a/surfsense_backend/app/automations/schemas/actions/agent_task.py +++ b/surfsense_backend/app/automations/schemas/actions/agent_task.py @@ -2,26 +2,20 @@ from __future__ import annotations -from typing import Any - from pydantic import BaseModel, ConfigDict, Field class AgentTaskActionParams(BaseModel): - """Run an agent task with a scoped tool allowlist.""" + """Run a multi_agent_chat turn from an automation step.""" model_config = ConfigDict(extra="forbid") - prompt: str = Field(..., min_length=1, description="Task prompt; rendered at execute time.") - tools: list[str] = Field( - default_factory=list, - description="Tool identifiers the agent may call. Empty = no tool access.", + query: str = Field( + ..., + min_length=1, + description="User query for the agent; rendered at execute time.", ) - model: str | None = Field( - default=None, - description="Model identifier. Defaults to the search space's agent_llm_id.", - ) - output_schema: dict[str, Any] | None = Field( - default=None, - description="JSON Schema (draft 2020-12) the agent must return. Recommended.", + auto_approve_all: bool = Field( + default=False, + description="If true, every HITL approval is auto-approved; otherwise rejected.", ) From 8c32455818094287faf3f3bfa6e602b98616d138 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 17:07:20 +0200 Subject: [PATCH 119/198] refactor(automations): vertical-slice actions and triggers by domain --- .../app/automations/actions/__init__.py | 25 +++++++++++++- .../actions/agent_task/__init__.py | 12 +++++-- .../agent_task/definition.py} | 11 +++---- .../automations/actions/agent_task/factory.py | 8 ++--- .../automations/actions/agent_task/invoke.py | 3 +- .../agent_task/params.py} | 0 .../{registries => }/actions/store.py | 0 .../{registries => }/actions/types.py | 0 .../app/automations/registries/__init__.py | 33 ------------------- .../registries/actions/__init__.py | 19 ----------- .../app/automations/runtime/executor.py | 2 +- .../app/automations/runtime/step.py | 4 +-- .../app/automations/schemas/__init__.py | 13 ++++---- .../automations/schemas/actions/__init__.py | 9 ----- .../automations/schemas/triggers/__init__.py | 11 ------- .../{registries => }/triggers/__init__.py | 6 +++- .../automations/triggers/manual/__init__.py | 10 ++++++ .../manual/definition.py} | 9 +++-- .../manual.py => triggers/manual/params.py} | 0 .../automations/triggers/schedule/__init__.py | 10 ++++++ .../schedule/definition.py} | 9 +++-- .../schedule/params.py} | 0 .../{registries => }/triggers/store.py | 0 .../{registries => }/triggers/types.py | 0 24 files changed, 86 insertions(+), 108 deletions(-) rename surfsense_backend/app/automations/{registries/actions/agent_task.py => actions/agent_task/definition.py} (54%) rename surfsense_backend/app/automations/{schemas/actions/agent_task.py => actions/agent_task/params.py} (100%) rename surfsense_backend/app/automations/{registries => }/actions/store.py (100%) rename surfsense_backend/app/automations/{registries => }/actions/types.py (100%) delete mode 100644 surfsense_backend/app/automations/registries/__init__.py delete mode 100644 surfsense_backend/app/automations/registries/actions/__init__.py delete mode 100644 surfsense_backend/app/automations/schemas/actions/__init__.py delete mode 100644 surfsense_backend/app/automations/schemas/triggers/__init__.py rename surfsense_backend/app/automations/{registries => }/triggers/__init__.py (61%) create mode 100644 surfsense_backend/app/automations/triggers/manual/__init__.py rename surfsense_backend/app/automations/{registries/triggers/manual.py => triggers/manual/definition.py} (58%) rename surfsense_backend/app/automations/{schemas/triggers/manual.py => triggers/manual/params.py} (100%) create mode 100644 surfsense_backend/app/automations/triggers/schedule/__init__.py rename surfsense_backend/app/automations/{registries/triggers/schedule.py => triggers/schedule/definition.py} (64%) rename surfsense_backend/app/automations/{schemas/triggers/schedule.py => triggers/schedule/params.py} (100%) rename surfsense_backend/app/automations/{registries => }/triggers/store.py (100%) rename surfsense_backend/app/automations/{registries => }/triggers/types.py (100%) diff --git a/surfsense_backend/app/automations/actions/__init__.py b/surfsense_backend/app/automations/actions/__init__.py index 2a518c1db..9ef091cb3 100644 --- a/surfsense_backend/app/automations/actions/__init__.py +++ b/surfsense_backend/app/automations/actions/__init__.py @@ -1 +1,24 @@ -"""Action implementations. One subpackage per built-in action type.""" +"""Actions domain: registry surface + built-in action packages. + +Each action lives in its own subpackage (``agent_task/``, ...) and self-registers +at import time via its ``definition`` module. Side-effect imports below ensure +the registry is populated whenever anyone touches the actions package. +""" + +from __future__ import annotations + +from .store import all_actions, get_action, register_action +from .types import ActionContext, ActionDefinition, ActionHandler, ActionHandlerFactory + +__all__ = [ + "ActionContext", + "ActionDefinition", + "ActionHandler", + "ActionHandlerFactory", + "all_actions", + "get_action", + "register_action", +] + +# Built-in actions self-register at import time. +from . import agent_task # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/actions/agent_task/__init__.py b/surfsense_backend/app/automations/actions/agent_task/__init__.py index ecf79b448..308812211 100644 --- a/surfsense_backend/app/automations/actions/agent_task/__init__.py +++ b/surfsense_backend/app/automations/actions/agent_task/__init__.py @@ -1,7 +1,15 @@ -"""``agent_task`` action: spin up multi_agent_chat for one rendered query.""" +"""``agent_task`` action: spin up multi_agent_chat for one rendered query. + +Imports ``definition`` for its side-effect (self-registration on the actions +registry) and re-exports ``build_handler`` for direct consumers. +""" from __future__ import annotations from .factory import build_handler +from .params import AgentTaskActionParams -__all__ = ["build_handler"] +__all__ = ["AgentTaskActionParams", "build_handler"] + +# Side-effect: register on the actions store. +from . import definition # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/registries/actions/agent_task.py b/surfsense_backend/app/automations/actions/agent_task/definition.py similarity index 54% rename from surfsense_backend/app/automations/registries/actions/agent_task.py rename to surfsense_backend/app/automations/actions/agent_task/definition.py index 51ee0eb7f..d7db5cfcd 100644 --- a/surfsense_backend/app/automations/registries/actions/agent_task.py +++ b/surfsense_backend/app/automations/actions/agent_task/definition.py @@ -1,12 +1,11 @@ -"""Built-in ``agent_task`` action. Self-registers at import time.""" +"""``agent_task`` ``ActionDefinition`` registration.""" from __future__ import annotations -from app.automations.actions.agent_task import build_handler -from app.automations.schemas.actions import AgentTaskActionParams - -from .store import register_action -from .types import ActionDefinition +from ..store import register_action +from ..types import ActionDefinition +from .factory import build_handler +from .params import AgentTaskActionParams AGENT_TASK_ACTION = ActionDefinition( type="agent_task", diff --git a/surfsense_backend/app/automations/actions/agent_task/factory.py b/surfsense_backend/app/automations/actions/agent_task/factory.py index a0d867f38..18a408e13 100644 --- a/surfsense_backend/app/automations/actions/agent_task/factory.py +++ b/surfsense_backend/app/automations/actions/agent_task/factory.py @@ -4,13 +4,9 @@ from __future__ import annotations from typing import Any -from app.automations.registries.actions.types import ( - ActionContext, - ActionHandler, -) -from app.automations.schemas.actions import AgentTaskActionParams - +from ..types import ActionContext, ActionHandler from .invoke import run_agent_task +from .params import AgentTaskActionParams def build_handler(ctx: ActionContext) -> ActionHandler: diff --git a/surfsense_backend/app/automations/actions/agent_task/invoke.py b/surfsense_backend/app/automations/actions/agent_task/invoke.py index aa849d7e2..a37e9beed 100644 --- a/surfsense_backend/app/automations/actions/agent_task/invoke.py +++ b/surfsense_backend/app/automations/actions/agent_task/invoke.py @@ -10,9 +10,10 @@ from langchain_core.messages import HumanMessage from langgraph.types import Command from app.agents.multi_agent_chat import create_multi_agent_chat_deep_agent -from app.automations.registries.actions.types import ActionContext from app.db import ChatVisibility, async_session_maker +from ..types import ActionContext + from .auto_decide import build_auto_decisions from .dependencies import build_dependencies from .finalize import extract_final_assistant_message diff --git a/surfsense_backend/app/automations/schemas/actions/agent_task.py b/surfsense_backend/app/automations/actions/agent_task/params.py similarity index 100% rename from surfsense_backend/app/automations/schemas/actions/agent_task.py rename to surfsense_backend/app/automations/actions/agent_task/params.py diff --git a/surfsense_backend/app/automations/registries/actions/store.py b/surfsense_backend/app/automations/actions/store.py similarity index 100% rename from surfsense_backend/app/automations/registries/actions/store.py rename to surfsense_backend/app/automations/actions/store.py diff --git a/surfsense_backend/app/automations/registries/actions/types.py b/surfsense_backend/app/automations/actions/types.py similarity index 100% rename from surfsense_backend/app/automations/registries/actions/types.py rename to surfsense_backend/app/automations/actions/types.py diff --git a/surfsense_backend/app/automations/registries/__init__.py b/surfsense_backend/app/automations/registries/__init__.py deleted file mode 100644 index f6af3817b..000000000 --- a/surfsense_backend/app/automations/registries/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Action and trigger registries — populated at process startup.""" - -from __future__ import annotations - -from .actions import ( - ActionContext, - ActionDefinition, - ActionHandler, - ActionHandlerFactory, - all_actions, - get_action, - register_action, -) -from .triggers import ( - TriggerDefinition, - all_triggers, - get_trigger, - register_trigger, -) - -__all__ = [ - "ActionContext", - "ActionDefinition", - "ActionHandler", - "ActionHandlerFactory", - "TriggerDefinition", - "all_actions", - "all_triggers", - "get_action", - "get_trigger", - "register_action", - "register_trigger", -] diff --git a/surfsense_backend/app/automations/registries/actions/__init__.py b/surfsense_backend/app/automations/registries/actions/__init__.py deleted file mode 100644 index b95c634f2..000000000 --- a/surfsense_backend/app/automations/registries/actions/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Action registry.""" - -from __future__ import annotations - -from .store import all_actions, get_action, register_action -from .types import ActionContext, ActionDefinition, ActionHandler, ActionHandlerFactory - -__all__ = [ - "ActionContext", - "ActionDefinition", - "ActionHandler", - "ActionHandlerFactory", - "all_actions", - "get_action", - "register_action", -] - -# Built-in actions self-register at import time. -from . import agent_task # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/runtime/executor.py b/surfsense_backend/app/automations/runtime/executor.py index e9e55b02d..ced44fb9b 100644 --- a/surfsense_backend/app/automations/runtime/executor.py +++ b/surfsense_backend/app/automations/runtime/executor.py @@ -8,7 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.automations.persistence.enums.run_status import RunStatus from app.automations.persistence.models.run import AutomationRun -from app.automations.registries.actions.types import ActionContext +from app.automations.actions.types import ActionContext from app.automations.schemas.definition.envelope import AutomationDefinition from app.automations.schemas.definition.plan_step import PlanStep from app.automations.templating import build_run_context diff --git a/surfsense_backend/app/automations/runtime/step.py b/surfsense_backend/app/automations/runtime/step.py index 76e3ba171..ac18b5e1f 100644 --- a/surfsense_backend/app/automations/runtime/step.py +++ b/surfsense_backend/app/automations/runtime/step.py @@ -6,8 +6,8 @@ from collections.abc import Mapping from datetime import UTC, datetime from typing import Any -from app.automations.registries import get_action -from app.automations.registries.actions.types import ActionContext +from app.automations.actions import get_action +from app.automations.actions.types import ActionContext from app.automations.schemas.definition.plan_step import PlanStep from app.automations.templating import evaluate_predicate, render_value diff --git a/surfsense_backend/app/automations/schemas/__init__.py b/surfsense_backend/app/automations/schemas/__init__.py index 8659ac9c9..2e2d60f12 100644 --- a/surfsense_backend/app/automations/schemas/__init__.py +++ b/surfsense_backend/app/automations/schemas/__init__.py @@ -1,8 +1,13 @@ -"""Schemas for the automation definition and per-type configs.""" +"""Schemas for the automation definition envelope. + +Per-action and per-trigger params schemas live with the action/trigger +implementations (``app.automations.actions..params`` / +``app.automations.triggers..params``); only the cross-cutting envelope +lives here. +""" from __future__ import annotations -from .actions import AgentTaskActionParams from .definition import ( AutomationDefinition, Execution, @@ -11,16 +16,12 @@ from .definition import ( PlanStep, TriggerSpec, ) -from .triggers import ManualTriggerParams, ScheduleTriggerParams __all__ = [ - "AgentTaskActionParams", "AutomationDefinition", "Execution", "Inputs", - "ManualTriggerParams", "Metadata", "PlanStep", - "ScheduleTriggerParams", "TriggerSpec", ] diff --git a/surfsense_backend/app/automations/schemas/actions/__init__.py b/surfsense_backend/app/automations/schemas/actions/__init__.py deleted file mode 100644 index c51d33b6a..000000000 --- a/surfsense_backend/app/automations/schemas/actions/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Per-action params schemas, one per action type.""" - -from __future__ import annotations - -from .agent_task import AgentTaskActionParams - -__all__ = [ - "AgentTaskActionParams", -] diff --git a/surfsense_backend/app/automations/schemas/triggers/__init__.py b/surfsense_backend/app/automations/schemas/triggers/__init__.py deleted file mode 100644 index 3ddd26f95..000000000 --- a/surfsense_backend/app/automations/schemas/triggers/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Per-trigger params schemas, one per trigger type.""" - -from __future__ import annotations - -from .manual import ManualTriggerParams -from .schedule import ScheduleTriggerParams - -__all__ = [ - "ManualTriggerParams", - "ScheduleTriggerParams", -] diff --git a/surfsense_backend/app/automations/registries/triggers/__init__.py b/surfsense_backend/app/automations/triggers/__init__.py similarity index 61% rename from surfsense_backend/app/automations/registries/triggers/__init__.py rename to surfsense_backend/app/automations/triggers/__init__.py index e08dcce76..258b2fda9 100644 --- a/surfsense_backend/app/automations/registries/triggers/__init__.py +++ b/surfsense_backend/app/automations/triggers/__init__.py @@ -1,4 +1,8 @@ -"""Trigger registry.""" +"""Triggers domain: registry surface + built-in trigger packages. + +Each trigger lives in its own subpackage (``manual/``, ``schedule/``, ...) and +self-registers at import time via its ``definition`` module. +""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/triggers/manual/__init__.py b/surfsense_backend/app/automations/triggers/manual/__init__.py new file mode 100644 index 000000000..bd9b8bf43 --- /dev/null +++ b/surfsense_backend/app/automations/triggers/manual/__init__.py @@ -0,0 +1,10 @@ +"""``manual`` trigger: fired by a user clicking ``Run now``.""" + +from __future__ import annotations + +from .params import ManualTriggerParams + +__all__ = ["ManualTriggerParams"] + +# Side-effect: register on the triggers store. +from . import definition # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/registries/triggers/manual.py b/surfsense_backend/app/automations/triggers/manual/definition.py similarity index 58% rename from surfsense_backend/app/automations/registries/triggers/manual.py rename to surfsense_backend/app/automations/triggers/manual/definition.py index 173c38655..9eb0282af 100644 --- a/surfsense_backend/app/automations/registries/triggers/manual.py +++ b/surfsense_backend/app/automations/triggers/manual/definition.py @@ -1,11 +1,10 @@ -"""Built-in ``manual`` trigger. Self-registers at import time.""" +"""``manual`` ``TriggerDefinition`` registration.""" from __future__ import annotations -from app.automations.schemas.triggers import ManualTriggerParams - -from .store import register_trigger -from .types import TriggerDefinition +from ..store import register_trigger +from ..types import TriggerDefinition +from .params import ManualTriggerParams MANUAL_TRIGGER = TriggerDefinition( type="manual", diff --git a/surfsense_backend/app/automations/schemas/triggers/manual.py b/surfsense_backend/app/automations/triggers/manual/params.py similarity index 100% rename from surfsense_backend/app/automations/schemas/triggers/manual.py rename to surfsense_backend/app/automations/triggers/manual/params.py diff --git a/surfsense_backend/app/automations/triggers/schedule/__init__.py b/surfsense_backend/app/automations/triggers/schedule/__init__.py new file mode 100644 index 000000000..e24750850 --- /dev/null +++ b/surfsense_backend/app/automations/triggers/schedule/__init__.py @@ -0,0 +1,10 @@ +"""``schedule`` trigger: fired on a cron schedule in a given timezone.""" + +from __future__ import annotations + +from .params import ScheduleTriggerParams + +__all__ = ["ScheduleTriggerParams"] + +# Side-effect: register on the triggers store. +from . import definition # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/registries/triggers/schedule.py b/surfsense_backend/app/automations/triggers/schedule/definition.py similarity index 64% rename from surfsense_backend/app/automations/registries/triggers/schedule.py rename to surfsense_backend/app/automations/triggers/schedule/definition.py index 0a6575f39..3f86d767c 100644 --- a/surfsense_backend/app/automations/registries/triggers/schedule.py +++ b/surfsense_backend/app/automations/triggers/schedule/definition.py @@ -1,11 +1,10 @@ -"""Built-in ``schedule`` trigger. Self-registers at import time.""" +"""``schedule`` ``TriggerDefinition`` registration.""" from __future__ import annotations -from app.automations.schemas.triggers import ScheduleTriggerParams - -from .store import register_trigger -from .types import TriggerDefinition +from ..store import register_trigger +from ..types import TriggerDefinition +from .params import ScheduleTriggerParams SCHEDULE_TRIGGER = TriggerDefinition( type="schedule", diff --git a/surfsense_backend/app/automations/schemas/triggers/schedule.py b/surfsense_backend/app/automations/triggers/schedule/params.py similarity index 100% rename from surfsense_backend/app/automations/schemas/triggers/schedule.py rename to surfsense_backend/app/automations/triggers/schedule/params.py diff --git a/surfsense_backend/app/automations/registries/triggers/store.py b/surfsense_backend/app/automations/triggers/store.py similarity index 100% rename from surfsense_backend/app/automations/registries/triggers/store.py rename to surfsense_backend/app/automations/triggers/store.py diff --git a/surfsense_backend/app/automations/registries/triggers/types.py b/surfsense_backend/app/automations/triggers/types.py similarity index 100% rename from surfsense_backend/app/automations/registries/triggers/types.py rename to surfsense_backend/app/automations/triggers/types.py From 861b91004d4e5a0f992c8bbc0b2cb2be50e3ec73 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 17:20:23 +0200 Subject: [PATCH 120/198] refactor(automations): extract dispatch_run; move manual adapter under triggers/manual/dispatch.py --- .../app/automations/dispatch/__init__.py | 12 ++-- .../app/automations/dispatch/errors.py | 7 ++ .../app/automations/dispatch/run.py | 72 +++++++++++++++++++ .../automations/triggers/manual/__init__.py | 3 +- .../manual.py => triggers/manual/dispatch.py} | 56 +++------------ .../app/routes/automations_routes.py | 3 +- 6 files changed, 97 insertions(+), 56 deletions(-) create mode 100644 surfsense_backend/app/automations/dispatch/errors.py create mode 100644 surfsense_backend/app/automations/dispatch/run.py rename surfsense_backend/app/automations/{dispatch/manual.py => triggers/manual/dispatch.py} (51%) diff --git a/surfsense_backend/app/automations/dispatch/__init__.py b/surfsense_backend/app/automations/dispatch/__init__.py index 4a549a4ce..be8a36581 100644 --- a/surfsense_backend/app/automations/dispatch/__init__.py +++ b/surfsense_backend/app/automations/dispatch/__init__.py @@ -1,8 +1,8 @@ -"""Public dispatch surface for firing automations.""" +"""Generic dispatch primitives shared across trigger types.""" -from .manual import DispatchError, dispatch_manual_run +from __future__ import annotations -__all__ = [ - "DispatchError", - "dispatch_manual_run", -] +from .errors import DispatchError +from .run import dispatch_run + +__all__ = ["DispatchError", "dispatch_run"] diff --git a/surfsense_backend/app/automations/dispatch/errors.py b/surfsense_backend/app/automations/dispatch/errors.py new file mode 100644 index 000000000..75640a987 --- /dev/null +++ b/surfsense_backend/app/automations/dispatch/errors.py @@ -0,0 +1,7 @@ +"""Dispatch errors raised when a fire request cannot be turned into a run.""" + +from __future__ import annotations + + +class DispatchError(Exception): + """A dispatch could not proceed (missing trigger, invalid inputs, ...).""" diff --git a/surfsense_backend/app/automations/dispatch/run.py b/surfsense_backend/app/automations/dispatch/run.py new file mode 100644 index 000000000..fd5107a18 --- /dev/null +++ b/surfsense_backend/app/automations/dispatch/run.py @@ -0,0 +1,72 @@ +"""Generic run dispatch: validate, snapshot, persist, enqueue. Shared by every trigger.""" + +from __future__ import annotations + +from typing import Any + +import jsonschema +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.persistence.enums.run_status import RunStatus +from app.automations.persistence.models.automation import Automation +from app.automations.persistence.models.run import AutomationRun +from app.automations.persistence.models.trigger import AutomationTrigger +from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.tasks.execute_run import automation_run_execute + +from .errors import DispatchError + + +async def dispatch_run( + *, + session: AsyncSession, + automation: Automation, + trigger: AutomationTrigger, + payload: dict[str, Any] | None, +) -> AutomationRun: + """Validate, snapshot the definition, persist an ``AutomationRun``, enqueue execution. + + Callers (trigger-specific adapters) are responsible for resolving + ``automation`` and ``trigger`` and for the trigger-side ``ACTIVE`` / + ``enabled`` guards. This function only handles what's identical across + every trigger type. + """ + try: + definition = AutomationDefinition.model_validate(automation.definition) + except Exception as exc: + raise DispatchError(f"invalid automation definition: {exc}") from exc + + resolved_inputs = _validate_inputs(definition, payload or {}) + snapshot = definition.model_dump(mode="json", by_alias=True) + + run = AutomationRun( + automation_id=automation.id, + trigger_id=trigger.id, + status=RunStatus.PENDING, + definition_snapshot=snapshot, + trigger_payload=payload, + resolved_inputs=resolved_inputs, + step_results=[], + artifacts=[], + ) + session.add(run) + await session.commit() + await session.refresh(run) + + automation_run_execute.apply_async( + args=[run.id], + time_limit=definition.execution.timeout_seconds, + ) + return run + + +def _validate_inputs( + definition: AutomationDefinition, payload: dict[str, Any] +) -> dict[str, Any]: + if definition.inputs is None or not definition.inputs.schema_: + return {} + try: + jsonschema.validate(instance=payload, schema=definition.inputs.schema_) + except jsonschema.ValidationError as exc: + raise DispatchError(f"inputs: {exc.message}") from exc + return payload diff --git a/surfsense_backend/app/automations/triggers/manual/__init__.py b/surfsense_backend/app/automations/triggers/manual/__init__.py index bd9b8bf43..65cca9270 100644 --- a/surfsense_backend/app/automations/triggers/manual/__init__.py +++ b/surfsense_backend/app/automations/triggers/manual/__init__.py @@ -2,9 +2,10 @@ from __future__ import annotations +from .dispatch import dispatch_manual_run from .params import ManualTriggerParams -__all__ = ["ManualTriggerParams"] +__all__ = ["ManualTriggerParams", "dispatch_manual_run"] # Side-effect: register on the triggers store. from . import definition # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/dispatch/manual.py b/surfsense_backend/app/automations/triggers/manual/dispatch.py similarity index 51% rename from surfsense_backend/app/automations/dispatch/manual.py rename to surfsense_backend/app/automations/triggers/manual/dispatch.py index 221d6a3e2..750c99937 100644 --- a/surfsense_backend/app/automations/dispatch/manual.py +++ b/surfsense_backend/app/automations/triggers/manual/dispatch.py @@ -1,25 +1,18 @@ -"""Manual ``Run now`` dispatch: validate inputs, snapshot the definition, enqueue.""" +"""Manual ``Run now`` dispatch adapter: load + guard, then call generic dispatch.""" from __future__ import annotations from typing import Any -import jsonschema from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession +from app.automations.dispatch import DispatchError, dispatch_run from app.automations.persistence.enums.automation_status import AutomationStatus -from app.automations.persistence.enums.run_status import RunStatus from app.automations.persistence.enums.trigger_type import TriggerType from app.automations.persistence.models.automation import Automation from app.automations.persistence.models.run import AutomationRun from app.automations.persistence.models.trigger import AutomationTrigger -from app.automations.schemas.definition.envelope import AutomationDefinition -from app.automations.tasks.execute_run import automation_run_execute - - -class DispatchError(Exception): - """A manual dispatch could not proceed (missing trigger, invalid inputs, ...).""" async def dispatch_manual_run( @@ -28,7 +21,7 @@ async def dispatch_manual_run( automation_id: int, payload: dict[str, Any] | None, ) -> AutomationRun: - """Validate, snapshot, persist, and enqueue an ``AutomationRun``.""" + """Find the automation + its enabled manual trigger, then run the generic dispatch.""" automation = await _load_automation(session, automation_id) if automation is None: raise DispatchError(f"automation {automation_id} not found") @@ -38,39 +31,18 @@ async def dispatch_manual_run( f"automation {automation_id} is {automation.status.value}, not active" ) - try: - definition = AutomationDefinition.model_validate(automation.definition) - except Exception as exc: - raise DispatchError(f"invalid automation definition: {exc}") from exc - trigger = await _find_manual_trigger(session, automation_id) if trigger is None: raise DispatchError( f"automation {automation_id} has no enabled manual trigger" ) - resolved_inputs = _validate_inputs(definition, payload or {}) - snapshot = definition.model_dump(mode="json", by_alias=True) - - run = AutomationRun( - automation_id=automation_id, - trigger_id=trigger.id, - status=RunStatus.PENDING, - definition_snapshot=snapshot, - trigger_payload=payload, - resolved_inputs=resolved_inputs, - step_results=[], - artifacts=[], + return await dispatch_run( + session=session, + automation=automation, + trigger=trigger, + payload=payload, ) - session.add(run) - await session.commit() - await session.refresh(run) - - automation_run_execute.apply_async( - args=[run.id], - time_limit=definition.execution.timeout_seconds, - ) - return run async def _load_automation( @@ -93,15 +65,3 @@ async def _find_manual_trigger( .limit(1) ) return (await session.execute(stmt)).scalar_one_or_none() - - -def _validate_inputs( - definition: AutomationDefinition, payload: dict[str, Any] -) -> dict[str, Any]: - if definition.inputs is None or not definition.inputs.schema_: - return {} - try: - jsonschema.validate(instance=payload, schema=definition.inputs.schema_) - except jsonschema.ValidationError as exc: - raise DispatchError(f"inputs: {exc.message}") from exc - return payload diff --git a/surfsense_backend/app/routes/automations_routes.py b/surfsense_backend/app/routes/automations_routes.py index 02c019625..6c169b199 100644 --- a/surfsense_backend/app/routes/automations_routes.py +++ b/surfsense_backend/app/routes/automations_routes.py @@ -8,8 +8,9 @@ from fastapi import APIRouter, Body, Depends, HTTPException from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from app.automations.dispatch import DispatchError, dispatch_manual_run +from app.automations.dispatch import DispatchError from app.automations.persistence.models.automation import Automation +from app.automations.triggers.manual import dispatch_manual_run from app.db import Permission, User, get_async_session from app.users import current_active_user from app.utils.rbac import check_permission From f08b3164417f841644afe355edc794aa1a64f851 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 17:55:58 +0200 Subject: [PATCH 121/198] add next_fire_at to automation_triggers and croniter dep --- .../alembic/versions/144_add_automation_tables.py | 13 +++++++++++++ .../app/automations/persistence/models/trigger.py | 5 +++++ surfsense_backend/pyproject.toml | 1 + surfsense_backend/uv.lock | 14 ++++++++++++++ 4 files changed, 33 insertions(+) diff --git a/surfsense_backend/alembic/versions/144_add_automation_tables.py b/surfsense_backend/alembic/versions/144_add_automation_tables.py index 8b59ee969..6daf4075f 100644 --- a/surfsense_backend/alembic/versions/144_add_automation_tables.py +++ b/surfsense_backend/alembic/versions/144_add_automation_tables.py @@ -89,6 +89,7 @@ def upgrade() -> None: params JSONB NOT NULL, enabled BOOLEAN NOT NULL DEFAULT true, last_fired_at TIMESTAMP WITH TIME ZONE, + next_fire_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); """ @@ -105,6 +106,17 @@ def upgrade() -> None: op.execute( "CREATE INDEX ix_automation_triggers_created_at ON automation_triggers(created_at);" ) + # Partial index for the schedule tick: only enabled schedule triggers + # with a scheduled next fire are ever scanned for due rows. + op.execute( + """ + CREATE INDEX ix_automation_triggers_due + ON automation_triggers (next_fire_at) + WHERE enabled = true + AND type = 'schedule' + AND next_fire_at IS NOT NULL; + """ + ) # automation_runs — the immutable per-fire execution record op.execute( @@ -148,6 +160,7 @@ def downgrade() -> None: op.execute("DROP INDEX IF EXISTS ix_automation_runs_automation_id;") op.execute("DROP TABLE IF EXISTS automation_runs;") + op.execute("DROP INDEX IF EXISTS ix_automation_triggers_due;") op.execute("DROP INDEX IF EXISTS ix_automation_triggers_created_at;") op.execute("DROP INDEX IF EXISTS ix_automation_triggers_enabled;") op.execute("DROP INDEX IF EXISTS ix_automation_triggers_type;") diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py index 7582234d4..b09bc3419 100644 --- a/surfsense_backend/app/automations/persistence/models/trigger.py +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -46,6 +46,11 @@ class AutomationTrigger(BaseModel, TimestampMixin): last_fired_at = Column(TIMESTAMP(timezone=True), nullable=True) + # Precomputed next fire moment in UTC; advanced after each fire by the + # schedule tick. NULL means the trigger has never been scheduled (the + # tick self-heals on first sight). Manual triggers leave this NULL. + next_fire_at = Column(TIMESTAMP(timezone=True), nullable=True) + automation = relationship("Automation", back_populates="triggers") runs = relationship( "AutomationRun", diff --git a/surfsense_backend/pyproject.toml b/surfsense_backend/pyproject.toml index 71c53caae..2ed0acca4 100644 --- a/surfsense_backend/pyproject.toml +++ b/surfsense_backend/pyproject.toml @@ -87,6 +87,7 @@ dependencies = [ "opentelemetry-instrumentation-httpx>=0.61b0", "opentelemetry-instrumentation-celery>=0.61b0", "opentelemetry-instrumentation-logging>=0.61b0", + "croniter>=2.0.0", ] [dependency-groups] diff --git a/surfsense_backend/uv.lock b/surfsense_backend/uv.lock index b902363dc..ba88153c5 100644 --- a/surfsense_backend/uv.lock +++ b/surfsense_backend/uv.lock @@ -1265,6 +1265,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/ca/6a667ccbe649856dcd3458bab80b016681b274399d6211187c6ab969fc50/courlan-1.3.2-py3-none-any.whl", hash = "sha256:d0dab52cf5b5b1000ee2839fbc2837e93b2514d3cb5bb61ae158a55b7a04c6be", size = 33848, upload-time = "2024-10-29T16:40:18.325Z" }, ] +[[package]] +name = "croniter" +version = "6.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/de/5832661ed55107b8a09af3f0a2e71e0957226a59eb1dcf0a445cce6daf20/croniter-6.2.2.tar.gz", hash = "sha256:ba60832a5ec8e12e51b8691c3309a113d1cf6526bdf1a48150ce8ec7a532d0ab", size = 113762, upload-time = "2026-03-15T08:43:48.112Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/39/783980e78cb92c2d7bdb1fc7dbc86e94ccc6d58224d76a7f1f51b6c51e30/croniter-6.2.2-py3-none-any.whl", hash = "sha256:a5d17b1060974d36251ea4faf388233eca8acf0d09cbd92d35f4c4ac8f279960", size = 45422, upload-time = "2026-03-15T08:43:46.626Z" }, +] + [[package]] name = "cryptography" version = "46.0.6" @@ -8132,6 +8144,7 @@ dependencies = [ { name = "celery", extra = ["redis"] }, { name = "chonkie", extra = ["all"] }, { name = "composio" }, + { name = "croniter" }, { name = "datasets" }, { name = "daytona" }, { name = "deepagents" }, @@ -8228,6 +8241,7 @@ requires-dist = [ { name = "celery", extras = ["redis"], specifier = ">=5.5.3" }, { name = "chonkie", extras = ["all"], specifier = ">=1.5.0" }, { name = "composio", specifier = ">=0.10.9" }, + { name = "croniter", specifier = ">=2.0.0" }, { name = "datasets", specifier = ">=2.21.0" }, { name = "daytona", specifier = ">=0.146.0" }, { name = "deepagents", specifier = ">=0.4.12,<0.5" }, From 3b1d7c4389b0224f86dd38c7ea121e94d78f35ee Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 17:56:02 +0200 Subject: [PATCH 122/198] add cron-based schedule trigger --- .../automations/triggers/schedule/__init__.py | 10 +++- .../app/automations/triggers/schedule/cron.py | 37 ++++++++++++++ .../automations/triggers/schedule/dispatch.py | 48 +++++++++++++++++++ .../automations/triggers/schedule/params.py | 12 ++++- 4 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 surfsense_backend/app/automations/triggers/schedule/cron.py create mode 100644 surfsense_backend/app/automations/triggers/schedule/dispatch.py diff --git a/surfsense_backend/app/automations/triggers/schedule/__init__.py b/surfsense_backend/app/automations/triggers/schedule/__init__.py index e24750850..5587692b9 100644 --- a/surfsense_backend/app/automations/triggers/schedule/__init__.py +++ b/surfsense_backend/app/automations/triggers/schedule/__init__.py @@ -2,9 +2,17 @@ from __future__ import annotations +from .cron import InvalidCronError, compute_next_fire_at, validate_cron +from .dispatch import dispatch_schedule_run from .params import ScheduleTriggerParams -__all__ = ["ScheduleTriggerParams"] +__all__ = [ + "InvalidCronError", + "ScheduleTriggerParams", + "compute_next_fire_at", + "dispatch_schedule_run", + "validate_cron", +] # Side-effect: register on the triggers store. from . import definition # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/triggers/schedule/cron.py b/surfsense_backend/app/automations/triggers/schedule/cron.py new file mode 100644 index 000000000..7155bab33 --- /dev/null +++ b/surfsense_backend/app/automations/triggers/schedule/cron.py @@ -0,0 +1,37 @@ +"""Cron math for the ``schedule`` trigger: validate + advance ``next_fire_at``.""" + +from __future__ import annotations + +from datetime import UTC, datetime +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError + +from croniter import CroniterBadCronError, croniter + + +class InvalidCronError(ValueError): + """Raised when a cron expression or timezone fails validation.""" + + +def validate_cron(cron: str, timezone: str) -> None: + """Raise ``InvalidCronError`` if cron or timezone are unusable.""" + try: + ZoneInfo(timezone) + except ZoneInfoNotFoundError as exc: + raise InvalidCronError(f"unknown timezone {timezone!r}") from exc + + try: + croniter(cron) + except (CroniterBadCronError, ValueError) as exc: + raise InvalidCronError(f"invalid cron {cron!r}: {exc}") from exc + + +def compute_next_fire_at(cron: str, timezone: str, *, after: datetime) -> datetime: + """Return the next moment matching ``cron`` in ``timezone`` strictly after ``after``. + + The result is normalized to UTC for storage. ``after`` is converted into the + given timezone before evaluation so DST and IANA rules apply correctly. + """ + tz = ZoneInfo(timezone) + base = after.astimezone(tz) if after.tzinfo else after.replace(tzinfo=UTC).astimezone(tz) + nxt: datetime = croniter(cron, base).get_next(datetime) + return nxt.astimezone(UTC) diff --git a/surfsense_backend/app/automations/triggers/schedule/dispatch.py b/surfsense_backend/app/automations/triggers/schedule/dispatch.py new file mode 100644 index 000000000..fb4fcf686 --- /dev/null +++ b/surfsense_backend/app/automations/triggers/schedule/dispatch.py @@ -0,0 +1,48 @@ +"""Schedule dispatch adapter: load + guard, then call generic dispatch.""" + +from __future__ import annotations + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.dispatch import DispatchError, dispatch_run +from app.automations.persistence.enums.automation_status import AutomationStatus +from app.automations.persistence.models.automation import Automation +from app.automations.persistence.models.run import AutomationRun +from app.automations.persistence.models.trigger import AutomationTrigger + + +async def dispatch_schedule_run( + *, + session: AsyncSession, + trigger: AutomationTrigger, +) -> AutomationRun: + """Fire one scheduled run for ``trigger``. + + The caller (the schedule tick) is responsible for selecting due triggers + and advancing ``next_fire_at`` / ``last_fired_at`` before invoking this. + """ + automation = await _load_automation(session, trigger.automation_id) + if automation is None: + raise DispatchError( + f"automation {trigger.automation_id} not found for trigger {trigger.id}" + ) + + if automation.status != AutomationStatus.ACTIVE: + raise DispatchError( + f"automation {trigger.automation_id} is {automation.status.value}, not active" + ) + + return await dispatch_run( + session=session, + automation=automation, + trigger=trigger, + payload=None, + ) + + +async def _load_automation( + session: AsyncSession, automation_id: int +) -> Automation | None: + stmt = select(Automation).where(Automation.id == automation_id) + return (await session.execute(stmt)).scalar_one_or_none() diff --git a/surfsense_backend/app/automations/triggers/schedule/params.py b/surfsense_backend/app/automations/triggers/schedule/params.py index 0418bd1d9..21da84f68 100644 --- a/surfsense_backend/app/automations/triggers/schedule/params.py +++ b/surfsense_backend/app/automations/triggers/schedule/params.py @@ -2,7 +2,9 @@ from __future__ import annotations -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field, model_validator + +from .cron import InvalidCronError, validate_cron class ScheduleTriggerParams(BaseModel): @@ -10,3 +12,11 @@ class ScheduleTriggerParams(BaseModel): cron: str = Field(..., description="Five-field cron expression.", examples=["0 9 * * 1-5"]) timezone: str = Field(..., description="IANA timezone.", examples=["Africa/Kigali"]) + + @model_validator(mode="after") + def _validate(self) -> ScheduleTriggerParams: + try: + validate_cron(self.cron, self.timezone) + except InvalidCronError as exc: + raise ValueError(str(exc)) from exc + return self From d84240a630f93ba1bd815f54e743c8b6d1acdca1 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 17:56:07 +0200 Subject: [PATCH 123/198] add schedule tick task and beat entry --- .../app/automations/tasks/schedule_tick.py | 159 ++++++++++++++++++ surfsense_backend/app/celery_app.py | 11 ++ 2 files changed, 170 insertions(+) create mode 100644 surfsense_backend/app/automations/tasks/schedule_tick.py diff --git a/surfsense_backend/app/automations/tasks/schedule_tick.py b/surfsense_backend/app/automations/tasks/schedule_tick.py new file mode 100644 index 000000000..cade621c7 --- /dev/null +++ b/surfsense_backend/app/automations/tasks/schedule_tick.py @@ -0,0 +1,159 @@ +"""Celery Beat tick that fires due ``schedule`` triggers. + +Runs every minute. Each tick performs two passes: + +1. **Self-heal**: enabled schedule triggers with NULL ``next_fire_at`` get + it computed from their ``cron`` + ``timezone`` (e.g. fresh inserts or + rows restored from backup). +2. **Claim & fire**: due rows are locked with ``FOR UPDATE SKIP LOCKED``, + their ``next_fire_at`` is advanced and ``last_fired_at`` is set, and + ``dispatch_schedule_run`` is invoked for each. Dispatch errors are + logged; a missed fire stays missed (matches K8s CronJob / Airflow + ``catchup=False`` semantics). +""" + +from __future__ import annotations + +import logging +from datetime import UTC, datetime + +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.persistence.enums.trigger_type import TriggerType +from app.automations.persistence.models.trigger import AutomationTrigger +from app.automations.triggers.schedule import ( + InvalidCronError, + compute_next_fire_at, + dispatch_schedule_run, +) +from app.celery_app import celery_app +from app.tasks.celery_tasks import get_celery_session_maker, run_async_celery_task + +logger = logging.getLogger(__name__) + +TASK_NAME = "automation_schedule_tick" + +# Cap rows touched per tick so a backlog of due triggers can't starve the +# worker; remaining rows fire on the next tick. +_TICK_BATCH = 200 + + +@celery_app.task(name=TASK_NAME) +def automation_schedule_tick() -> None: + """Tick once: self-heal NULL next_fire_at, claim due rows, fire each.""" + return run_async_celery_task(_tick) + + +async def _tick() -> None: + session_maker = get_celery_session_maker() + async with session_maker() as session: + now = datetime.now(UTC) + + await _self_heal_null_next_fire(session, now=now) + + claimed_ids = await _claim_due_triggers(session, now=now) + if not claimed_ids: + return + + for trigger_id in claimed_ids: + await _fire_one(session, trigger_id=trigger_id) + + +async def _self_heal_null_next_fire(session: AsyncSession, *, now: datetime) -> None: + """Backfill ``next_fire_at`` for enabled schedule triggers missing it.""" + stmt = ( + select(AutomationTrigger) + .where( + AutomationTrigger.type == TriggerType.SCHEDULE, + AutomationTrigger.enabled.is_(True), + AutomationTrigger.next_fire_at.is_(None), + ) + .limit(_TICK_BATCH) + ) + triggers = (await session.execute(stmt)).scalars().all() + if not triggers: + return + + for trigger in triggers: + try: + trigger.next_fire_at = compute_next_fire_at( + trigger.params["cron"], + trigger.params["timezone"], + after=now, + ) + except (InvalidCronError, KeyError, TypeError) as exc: + logger.warning( + "automation_trigger %d has invalid schedule params, disabling: %s", + trigger.id, + exc, + ) + trigger.enabled = False + + await session.commit() + + +async def _claim_due_triggers( + session: AsyncSession, *, now: datetime +) -> list[int]: + """Lock and advance due rows; return claimed trigger ids.""" + stmt = ( + select(AutomationTrigger) + .where( + AutomationTrigger.type == TriggerType.SCHEDULE, + AutomationTrigger.enabled.is_(True), + AutomationTrigger.next_fire_at.isnot(None), + AutomationTrigger.next_fire_at <= now, + ) + .order_by(AutomationTrigger.next_fire_at) + .limit(_TICK_BATCH) + .with_for_update(skip_locked=True) + ) + triggers = (await session.execute(stmt)).scalars().all() + if not triggers: + return [] + + claimed: list[int] = [] + for trigger in triggers: + try: + trigger.next_fire_at = compute_next_fire_at( + trigger.params["cron"], + trigger.params["timezone"], + after=now, + ) + except (InvalidCronError, KeyError, TypeError) as exc: + logger.warning( + "automation_trigger %d has invalid schedule params, disabling: %s", + trigger.id, + exc, + ) + trigger.enabled = False + continue + + trigger.last_fired_at = now + claimed.append(trigger.id) + + await session.commit() + return claimed + + +async def _fire_one(session: AsyncSession, *, trigger_id: int) -> None: + """Reload the trigger post-commit and dispatch a run for it.""" + trigger = await session.get(AutomationTrigger, trigger_id) + if trigger is None: + return + + try: + run = await dispatch_schedule_run(session=session, trigger=trigger) + logger.info( + "scheduled fire: trigger=%d automation=%d run=%d", + trigger_id, + trigger.automation_id, + run.id, + ) + except Exception: + logger.exception( + "scheduled fire failed for trigger %d (next attempt at next match)", + trigger_id, + ) + await session.rollback() diff --git a/surfsense_backend/app/celery_app.py b/surfsense_backend/app/celery_app.py index 569178239..9169592fd 100644 --- a/surfsense_backend/app/celery_app.py +++ b/surfsense_backend/app/celery_app.py @@ -189,6 +189,7 @@ celery_app = Celery( "app.tasks.celery_tasks.stale_notification_cleanup_task", "app.tasks.celery_tasks.stripe_reconciliation_task", "app.automations.tasks.execute_run", + "app.automations.tasks.schedule_tick", ], ) @@ -283,4 +284,14 @@ celery_app.conf.beat_schedule = { "expires": 60, }, }, + # Fire due automation schedule triggers. Ticks every minute; per-row cron + # math is precomputed (next_fire_at column) so the tick is an indexed + # lookup, not N cron evaluations. + "automation-schedule-tick": { + "task": "automation_schedule_tick", + "schedule": crontab(minute="*"), + "options": { + "expires": 50, + }, + }, } From dd6bc30f98a5fa82848b86d8a621ad78f5042ba6 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 18:56:16 +0200 Subject: [PATCH 124/198] move automations api into vertical slice with service layer --- .../app/automations/api/__init__.py | 12 ++++ .../app/automations/api/automation.py | 22 +++++++ .../app/automations/services/__init__.py | 7 ++ .../app/automations/services/automation.py | 65 +++++++++++++++++++ surfsense_backend/app/routes/__init__.py | 2 +- .../app/routes/automations_routes.py | 56 ---------------- 6 files changed, 107 insertions(+), 57 deletions(-) create mode 100644 surfsense_backend/app/automations/api/__init__.py create mode 100644 surfsense_backend/app/automations/api/automation.py create mode 100644 surfsense_backend/app/automations/services/__init__.py create mode 100644 surfsense_backend/app/automations/services/automation.py delete mode 100644 surfsense_backend/app/routes/automations_routes.py diff --git a/surfsense_backend/app/automations/api/__init__.py b/surfsense_backend/app/automations/api/__init__.py new file mode 100644 index 000000000..459c6c1b4 --- /dev/null +++ b/surfsense_backend/app/automations/api/__init__.py @@ -0,0 +1,12 @@ +"""HTTP layer for the automations feature.""" + +from __future__ import annotations + +from fastapi import APIRouter + +from .automation import router as automation_router + +router = APIRouter() +router.include_router(automation_router) + +__all__ = ["router"] diff --git a/surfsense_backend/app/automations/api/automation.py b/surfsense_backend/app/automations/api/automation.py new file mode 100644 index 000000000..42163f74d --- /dev/null +++ b/surfsense_backend/app/automations/api/automation.py @@ -0,0 +1,22 @@ +"""Routes for the ``Automation`` resource.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body, Depends + +from app.automations.services import AutomationService, get_automation_service + +router = APIRouter() + + +@router.post("/automations/{automation_id}/run") +async def run_automation_now( + automation_id: int, + payload: dict[str, Any] | None = Body(default=None), + service: AutomationService = Depends(get_automation_service), +) -> dict[str, Any]: + """Fire a manual run.""" + run = await service.run_now(automation_id=automation_id, payload=payload) + return {"run_id": run.id, "status": run.status.value} diff --git a/surfsense_backend/app/automations/services/__init__.py b/surfsense_backend/app/automations/services/__init__.py new file mode 100644 index 000000000..f0a97d216 --- /dev/null +++ b/surfsense_backend/app/automations/services/__init__.py @@ -0,0 +1,7 @@ +"""Service layer for the automations feature.""" + +from __future__ import annotations + +from .automation import AutomationService, get_automation_service + +__all__ = ["AutomationService", "get_automation_service"] diff --git a/surfsense_backend/app/automations/services/automation.py b/surfsense_backend/app/automations/services/automation.py new file mode 100644 index 000000000..2a921e331 --- /dev/null +++ b/surfsense_backend/app/automations/services/automation.py @@ -0,0 +1,65 @@ +"""``AutomationService`` — orchestration for the ``Automation`` resource.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import Depends, HTTPException +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.dispatch import DispatchError +from app.automations.persistence.models.automation import Automation +from app.automations.persistence.models.run import AutomationRun +from app.automations.triggers.manual import dispatch_manual_run +from app.db import Permission, User, get_async_session +from app.users import current_active_user +from app.utils.rbac import check_permission + + +class AutomationService: + """Service for the ``Automation`` resource.""" + + def __init__(self, *, session: AsyncSession, user: User) -> None: + self.session = session + self.user = user + + async def run_now( + self, + *, + automation_id: int, + payload: dict[str, Any] | None, + ) -> AutomationRun: + """Fire a manual run for ``automation_id``.""" + automation = await self._get_automation_or_raise(automation_id) + await check_permission( + self.session, + self.user, + automation.search_space_id, + Permission.AUTOMATIONS_EXECUTE.value, + "You don't have permission to execute automations in this search space", + ) + + try: + return await dispatch_manual_run( + session=self.session, + automation_id=automation_id, + payload=payload, + ) + except DispatchError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc + + async def _get_automation_or_raise(self, automation_id: int) -> Automation: + """Get the automation by id; 404 if missing.""" + automation = await self.session.get(Automation, automation_id) + if automation is None: + raise HTTPException( + status_code=404, detail=f"automation {automation_id} not found" + ) + return automation + + +def get_automation_service( + session: AsyncSession = Depends(get_async_session), + user: User = Depends(current_active_user), +) -> AutomationService: + return AutomationService(session=session, user=user) diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index 1d3ca2141..64c8c6585 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -7,7 +7,7 @@ from .agent_revert_route import router as agent_revert_router from .airtable_add_connector_route import ( router as airtable_add_connector_router, ) -from .automations_routes import router as automations_router +from app.automations.api import router as automations_router from .chat_comments_routes import router as chat_comments_router from .circleback_webhook_route import router as circleback_webhook_router from .clickup_add_connector_route import router as clickup_add_connector_router diff --git a/surfsense_backend/app/routes/automations_routes.py b/surfsense_backend/app/routes/automations_routes.py deleted file mode 100644 index 6c169b199..000000000 --- a/surfsense_backend/app/routes/automations_routes.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Routes for automations. v1: manual ``Run now``.""" - -from __future__ import annotations - -from typing import Any - -from fastapi import APIRouter, Body, Depends, HTTPException -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession - -from app.automations.dispatch import DispatchError -from app.automations.persistence.models.automation import Automation -from app.automations.triggers.manual import dispatch_manual_run -from app.db import Permission, User, get_async_session -from app.users import current_active_user -from app.utils.rbac import check_permission - -router = APIRouter() - - -@router.post("/automations/{automation_id}/run") -async def run_automation_now( - automation_id: int, - payload: dict[str, Any] | None = Body(default=None), - session: AsyncSession = Depends(get_async_session), - user: User = Depends(current_active_user), -) -> dict[str, Any]: - """Fire an automation manually. Returns the new run id and status.""" - search_space_id = ( - await session.execute( - select(Automation.search_space_id).where(Automation.id == automation_id) - ) - ).scalar_one_or_none() - if search_space_id is None: - raise HTTPException( - status_code=404, detail=f"automation {automation_id} not found" - ) - - await check_permission( - session, - user, - search_space_id, - Permission.AUTOMATIONS_EXECUTE.value, - "You don't have permission to execute automations in this search space", - ) - - try: - run = await dispatch_manual_run( - session=session, - automation_id=automation_id, - payload=payload, - ) - except DispatchError as exc: - raise HTTPException(status_code=422, detail=str(exc)) from exc - - return {"run_id": run.id, "status": run.status.value} From 84d99f19a253ecf34c5e53285ce6cb65cd2d98c7 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 19:10:20 +0200 Subject: [PATCH 125/198] automations(api): API request/response schemas --- .../app/automations/api/automation.py | 7 +- .../app/automations/api/schemas/__init__.py | 28 ++++++++ .../app/automations/api/schemas/automation.py | 64 +++++++++++++++++++ .../app/automations/api/schemas/run.py | 50 +++++++++++++++ .../app/automations/api/schemas/trigger.py | 43 +++++++++++++ 5 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 surfsense_backend/app/automations/api/schemas/__init__.py create mode 100644 surfsense_backend/app/automations/api/schemas/automation.py create mode 100644 surfsense_backend/app/automations/api/schemas/run.py create mode 100644 surfsense_backend/app/automations/api/schemas/trigger.py diff --git a/surfsense_backend/app/automations/api/automation.py b/surfsense_backend/app/automations/api/automation.py index 42163f74d..4d0ce7209 100644 --- a/surfsense_backend/app/automations/api/automation.py +++ b/surfsense_backend/app/automations/api/automation.py @@ -6,17 +6,18 @@ from typing import Any from fastapi import APIRouter, Body, Depends +from app.automations.api.schemas import RunDispatched from app.automations.services import AutomationService, get_automation_service router = APIRouter() -@router.post("/automations/{automation_id}/run") +@router.post("/automations/{automation_id}/run", response_model=RunDispatched) async def run_automation_now( automation_id: int, payload: dict[str, Any] | None = Body(default=None), service: AutomationService = Depends(get_automation_service), -) -> dict[str, Any]: +) -> RunDispatched: """Fire a manual run.""" run = await service.run_now(automation_id=automation_id, payload=payload) - return {"run_id": run.id, "status": run.status.value} + return RunDispatched(run_id=run.id, status=run.status) diff --git a/surfsense_backend/app/automations/api/schemas/__init__.py b/surfsense_backend/app/automations/api/schemas/__init__.py new file mode 100644 index 000000000..a8a010a2c --- /dev/null +++ b/surfsense_backend/app/automations/api/schemas/__init__.py @@ -0,0 +1,28 @@ +"""Request/response schemas for the automations HTTP layer.""" + +from __future__ import annotations + +from .automation import ( + AutomationCreate, + AutomationDetail, + AutomationList, + AutomationSummary, + AutomationUpdate, +) +from .run import RunDetail, RunDispatched, RunList, RunSummary +from .trigger import TriggerCreate, TriggerDetail, TriggerUpdate + +__all__ = [ + "AutomationCreate", + "AutomationDetail", + "AutomationList", + "AutomationSummary", + "AutomationUpdate", + "RunDetail", + "RunDispatched", + "RunList", + "RunSummary", + "TriggerCreate", + "TriggerDetail", + "TriggerUpdate", +] diff --git a/surfsense_backend/app/automations/api/schemas/automation.py b/surfsense_backend/app/automations/api/schemas/automation.py new file mode 100644 index 000000000..c1defd417 --- /dev/null +++ b/surfsense_backend/app/automations/api/schemas/automation.py @@ -0,0 +1,64 @@ +"""Request/response schemas for the ``Automation`` resource.""" + +from __future__ import annotations + +from datetime import datetime + +from pydantic import BaseModel, ConfigDict, Field + +from app.automations.persistence.enums.automation_status import AutomationStatus +from app.automations.schemas.definition import AutomationDefinition + +from .trigger import TriggerCreate, TriggerDetail + + +class AutomationCreate(BaseModel): + """Create an automation, optionally with initial triggers (atomic).""" + + model_config = ConfigDict(extra="forbid") + + search_space_id: int + name: str = Field(..., min_length=1, max_length=200) + description: str | None = None + definition: AutomationDefinition + triggers: list[TriggerCreate] = Field(default_factory=list) + + +class AutomationUpdate(BaseModel): + """Partial update of an automation. Triggers are managed separately.""" + + model_config = ConfigDict(extra="forbid") + + name: str | None = Field(default=None, min_length=1, max_length=200) + description: str | None = None + status: AutomationStatus | None = None + definition: AutomationDefinition | None = None + + +class AutomationSummary(BaseModel): + """Lightweight automation view for list endpoints.""" + + model_config = ConfigDict(from_attributes=True) + + id: int + search_space_id: int + name: str + description: str | None = None + status: AutomationStatus + version: int + created_at: datetime + updated_at: datetime + + +class AutomationDetail(AutomationSummary): + """Full automation view including definition and attached triggers.""" + + definition: AutomationDefinition + triggers: list[TriggerDetail] = Field(default_factory=list) + + +class AutomationList(BaseModel): + """Paginated list of automations.""" + + items: list[AutomationSummary] + total: int diff --git a/surfsense_backend/app/automations/api/schemas/run.py b/surfsense_backend/app/automations/api/schemas/run.py new file mode 100644 index 000000000..789b6f674 --- /dev/null +++ b/surfsense_backend/app/automations/api/schemas/run.py @@ -0,0 +1,50 @@ +"""Response schemas for run sub-resources and run dispatch.""" + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +from pydantic import BaseModel, ConfigDict + +from app.automations.persistence.enums.run_status import RunStatus + + +class RunSummary(BaseModel): + """Lightweight run view for list endpoints.""" + + model_config = ConfigDict(from_attributes=True) + + id: int + automation_id: int + trigger_id: int | None = None + status: RunStatus + started_at: datetime | None = None + finished_at: datetime | None = None + created_at: datetime + + +class RunDetail(RunSummary): + """Full run view including snapshot, results and artifacts.""" + + definition_snapshot: dict[str, Any] + trigger_payload: dict[str, Any] | None = None + resolved_inputs: dict[str, Any] + step_results: list[dict[str, Any]] + output: dict[str, Any] | None = None + artifacts: list[dict[str, Any]] + error: dict[str, Any] | None = None + + +class RunList(BaseModel): + """Paginated list of runs.""" + + items: list[RunSummary] + total: int + + +class RunDispatched(BaseModel): + """Response of a successful run dispatch.""" + + run_id: int + status: RunStatus diff --git a/surfsense_backend/app/automations/api/schemas/trigger.py b/surfsense_backend/app/automations/api/schemas/trigger.py new file mode 100644 index 000000000..32afe7c60 --- /dev/null +++ b/surfsense_backend/app/automations/api/schemas/trigger.py @@ -0,0 +1,43 @@ +"""Request/response schemas for trigger sub-resources.""" + +from __future__ import annotations + +from datetime import datetime +from typing import Any + +from pydantic import BaseModel, ConfigDict, Field + +from app.automations.persistence.enums.trigger_type import TriggerType + + +class TriggerCreate(BaseModel): + """Attach a trigger to an automation.""" + + model_config = ConfigDict(extra="forbid") + + type: TriggerType + params: dict[str, Any] = Field(default_factory=dict) + enabled: bool = True + + +class TriggerUpdate(BaseModel): + """Partial update of an existing trigger.""" + + model_config = ConfigDict(extra="forbid") + + enabled: bool | None = None + params: dict[str, Any] | None = None + + +class TriggerDetail(BaseModel): + """Trigger as returned to clients.""" + + model_config = ConfigDict(from_attributes=True) + + id: int + type: TriggerType + params: dict[str, Any] + enabled: bool + last_fired_at: datetime | None = None + next_fire_at: datetime | None = None + created_at: datetime From 27ab367a13492426f315fc7e40c8d987d6f28b74 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 21:21:43 +0200 Subject: [PATCH 126/198] feat(automations): static_inputs on triggers + vertical-slice api/services --- automation-design-plan.md | 489 ++++++++---------- .../versions/144_add_automation_tables.py | 4 +- .../actions/agent_task/definition.py | 2 +- .../app/automations/actions/types.py | 8 +- .../app/automations/api/__init__.py | 4 + .../app/automations/api/automation.py | 83 ++- surfsense_backend/app/automations/api/run.py | 71 +++ .../app/automations/api/trigger.py | 55 ++ .../app/automations/dispatch/run.py | 18 +- .../app/automations/persistence/models/run.py | 5 +- .../automations/persistence/models/trigger.py | 4 + .../app/automations/runtime/executor.py | 2 +- .../{api/schemas => schemas/api}/__init__.py | 0 .../schemas => schemas/api}/automation.py | 0 .../{api/schemas => schemas/api}/run.py | 3 +- .../{api/schemas => schemas/api}/trigger.py | 3 + .../app/automations/services/__init__.py | 13 +- .../app/automations/services/automation.py | 159 +++++- .../app/automations/services/run.py | 93 ++++ .../app/automations/services/trigger.py | 143 +++++ .../app/automations/tasks/schedule_tick.py | 56 +- .../app/automations/templating/context.py | 4 +- .../automations/triggers/manual/definition.py | 3 +- .../automations/triggers/manual/dispatch.py | 11 +- .../triggers/schedule/definition.py | 7 +- .../automations/triggers/schedule/dispatch.py | 21 +- .../app/automations/triggers/types.py | 10 +- 27 files changed, 915 insertions(+), 356 deletions(-) create mode 100644 surfsense_backend/app/automations/api/run.py create mode 100644 surfsense_backend/app/automations/api/trigger.py rename surfsense_backend/app/automations/{api/schemas => schemas/api}/__init__.py (100%) rename surfsense_backend/app/automations/{api/schemas => schemas/api}/automation.py (100%) rename surfsense_backend/app/automations/{api/schemas => schemas/api}/run.py (92%) rename surfsense_backend/app/automations/{api/schemas => schemas/api}/trigger.py (87%) create mode 100644 surfsense_backend/app/automations/services/run.py create mode 100644 surfsense_backend/app/automations/services/trigger.py diff --git a/automation-design-plan.md b/automation-design-plan.md index f57385e31..db5f7a23c 100644 --- a/automation-design-plan.md +++ b/automation-design-plan.md @@ -34,24 +34,27 @@ system will survive feature growth: --- -## 2. The four-layer contract +## 2. The three-layer contract -The system is structured as four layers. Layers 1, 2, and 4 are defined by -SurfSense developers (at registration time). Layer 3 is what users write -(or the NL generator produces). The runtime reads all four to do its job. +The system is structured as three layers. Layers 1 and 3 are defined by +SurfSense developers (at registration time). Layer 2 is what users write +(or the NL generator produces). The runtime reads all three to do its job. | Layer | What it is | Defined by | | ----- | ---------- | ---------- | -| **1. Capability registry** | What this SurfSense instance can do | Developers, at startup | -| **2. Action contract** | Per-action input/output schema | Developers, at startup | -| **3. Automation definition** | One concrete saved automation | Users (or NL generator) | -| **4. Trigger contract** | Per-trigger config and payload schemas | Developers, at startup | +| **1. Action contract** | Per-action params and output schema | Developers, at startup | +| **2. Automation definition** | One concrete saved automation | Users (or NL generator) | +| **3. Trigger contract** | Per-trigger params and payload schemas | Developers, at startup | -Each layer constrains the one above. The runtime reads all four but doesn't -know what's in them ahead of time. That's how a new capability or trigger +Each layer constrains the next. The runtime reads all three but doesn't +know what's in them ahead of time. That's how a new action or trigger type becomes available across the engine without code changes outside its registration. +A unification layer below Layer 1 — one catalog of "things this SurfSense +instance can do," shared by automations, agents, and future surfaces — was +considered and deferred (§3). v1 actions are stand-alone. + ### Schema language Every shape in every layer is described in **JSON Schema (draft 2020-12).** @@ -66,167 +69,126 @@ extensions on top: --- -## 3. Capability registry (Layer 1) +## 3. Capability unification layer — deferred to post-v1 -A `Capability` is one discrete thing the SurfSense backend exposes — -"post a Slack message," "query the Search Space," "generate a podcast." It -is the atomic unit of "things automations can do." +Earlier drafts introduced a `Capability` registry as Layer 1: one catalog +of "things this SurfSense instance can do," shared by the automation +engine (as actions), the agent (as tools), and any future HTTP surface. +The motivation is real — one source of truth beats N parallel registries — +but v1 has a single action (`agent_task`) and a single consumer (the +automation engine). The five-field shape sketched earlier (`id`, +`description`, `input_schema`, `output_schema`, `handler`) cannot safely +host any non-trivial capability: it carries no caller identity, no +search-space scoping, and no authorization gate on tool delegation. +Building the abstraction with one consumer would lock in a shape that +doesn't survive the second consumer. -```python -@dataclass -class Capability: - id: str # "slack.post_message" - description: str # for the NL generator + UI label - input_schema: dict # JSON Schema - output_schema: dict # JSON Schema - handler: AsyncHandler -``` +The unification layer returns when the second consumer lands (Phase 2 +tight actions or Phase 4 MCP), redesigned from the start with: -### v1-minimum: five fields, nothing else +- A `CallContext` carrying caller user id, search space id, and run id, + passed to every handler invocation. +- Explicit scope declarations per capability (e.g. `reads:documents`, + `writes:slack`, `destructive`) for the authorization layer to read. +- A per-user, per-search-space filter consulted at both definition save + time (validating `agent_task.tools`) and run time (scoping the agent's + tool list to what the automation creator can delegate). -The Capability is **deliberately five fields in v1**. Every additional field -that earlier drafts considered (`name`, `required_credentials`, -`side_effects`, `expected_duration_seconds`, `cost_estimate`) has been -removed until a concrete consumer feature demands it. Authoring stays cheap -and the registry stays trivial to introspect: +Until then: -- `name` → folded into `description`. The UI can render a short label from - the first line of `description` or fall back to `id`. No separate field - needed in v1. -- `required_credentials` → returns when external-credential capabilities - ship (Phase 2). v1 capabilities run server-side with app config; nothing - to declare. -- `side_effects` → returns when RBAC inside automations or - `READ_ONLY`-only agent tool gating arrives. v1 capabilities are - hand-picked and all trusted code. -- `expected_duration_seconds` → returns when multi-queue routing ships. - Single Celery queue in v1. -- `cost_estimate` → never returns as a declared field; cost is measured - per run from a ledger, aggregated per Capability, and surfaced as a - historical average. Pre-flight checks are deferred. - -The runtime invariant: a Capability is **a typed, named, callable thing -the system can do.** Every consumer (executor, agent tool layer, future -HTTP API) sees the same five-field shape and uses it the same way. - -### Where capabilities live (v1) - -In v1, the capability registry is a single in-memory dict, populated at -process startup from native registrations in -`automations/registries/capabilities/`. Identical across all workers. -No database persistence, no closures rebuilt per worker. - -### MCP integration — deferred to Phase 4 - -The earlier two-tier registry (native + MCP-derived), the -`mcp_connections` / `mcp_tools` tables, the harvester, and the lazy -per-worker closure cache are **deferred to Phase 4** along with the -rest of the integration-tooling surface. They are removed from v1 -because: - -- v1 has no external connector capabilities (no Slack, Notion, Drive, - etc.). The only capabilities that will ship are server-side helpers - (search-space query / fetch) plus the loose `agent_task` action. -- Without external connectors, the lifecycle mismatch that motivates - the two-tier design (connect Monday, run Friday, workers restarted - in between) doesn't arise. A startup-time dict is sufficient. -- Phase 4 reintroduces this design as-is — the registry interface in - v1 is the same callable surface a Phase-4 MCP harvester will register - into. The deferral is additive, not a different design. - -See archived design at `docs/automation/archived/mcp-registry.md` once -v1 ships; for now the only consumer of the registry is the in-memory -native path. +- v1 actions are stand-alone units (Layer 1 below); the automation engine + reads its own action registry, nothing else. +- `agent_task.params.tools` is a forward-looking allowlist field with no + v1 semantics beyond "list of string identifiers." The handler's tool + resolution is opaque to the automation contract. ### Credentials — deferred to Phase 2 -The earlier per-call credential resolution pattern (`ctx.resolve_mcp_client`, -`ctx.resolve_http_client`, `ctx.resolve_llm`) is **deferred to Phase 2**. -v1 capabilities run server-side using app-level configuration; none of -the seven v1 capabilities needs per-user or per-connection auth. +External-credential handlers (Slack, email, etc.) require per-user or +per-connection auth. v1 actions run server-side with app-level +configuration. When tight actions ship in Phase 2, the credential design +lands as part of the unification redesign: connection IDs in the +definition (never tokens); credentials loaded per-call by the handler +context (never pre-loaded into worker memory); credentials never enter +LLM context. -When Phase 2 ships external-credential capabilities (Slack, email, etc.), -the three guarantees the original design promised are reintroduced -unchanged: +### MCP — deferred to Phase 4 -- Credentials never appear in the automation definition (connection IDs - only). -- Credentials never appear in the LLM's context (the host holds them - and uses them on the LLM's behalf when executing tool calls). -- Credentials are loaded per-call, not pre-loaded into worker memory. - -The Phase-2 design returns as-is; only the v1 surface is simplified. +External tool servers feeding tools into a shared registry land with the +rest of the integration tooling in Phase 4, after the unification layer +is in place. The two-tier registry, `mcp_connections` and `mcp_tools` +tables, and the harvester arrive as a single coherent step then. --- -## 4. Action contract (Layer 2) +## 4. Action contract -An `Action` is what a user references in a plan step. Most actions are -thin wrappers around one capability (e.g., `slack_post` wraps -`slack.post_message`). Some compose: `agent_task` is one action whose -handler invokes the LangGraph runtime, which in turn can call many -capabilities. +An `Action` is what a user references in a plan step. Some actions are +deterministic single-purpose handlers (`slack_post`, `send_email`); one +action (`agent_task`) hosts an LLM and a tool allowlist for cases where +judgment is needed. The contract is the same in both cases — only the +handler differs. ```python -@dataclass +@dataclass(frozen=True, slots=True) class ActionDefinition: - type: str # "agent_task", "slack_post" - name: str # for the UI - description: str # for the NL generator - config_schema: dict # JSON Schema for action.config - output_contract: dict | DynamicOutput # what it produces - uses_capabilities: list[str] # IDs from the registry - produces_artifacts: list[ArtifactSpec] # see §8 - handler: AsyncHandler + type: str # "agent_task", "slack_post" + name: str # short UI label + description: str # for the NL generator and the UI + params_schema: dict # JSON Schema for step.params + handler: ActionHandler ``` +This is the v1 shape: five fields, no handler context, no output +contract, no artifact declaration. The deferrals are intentional: + +- **`output_contract`** — Phase 2. Deterministic handlers will return + a fixed shape; v1's only action (`agent_task`) takes an + `output_schema` inside `params` and validates against that instead. +- **`produces_artifacts`** — Phase 5. Artifact lifecycle (storage, + signed URLs, retention) is its own design step; v1 handlers + persist their own outputs. +- **Handler context** — paired with the unification redesign (§3). + v1 handlers receive `(args)` only; per-user / per-search-space + behavior is not yet a v1 concern. + ### Tight vs loose actions Two patterns coexist by design: -- **Tight actions** (`slack_post`, `linear_create_issue`, `send_email`): - config_schema is fully specified, output_contract is fixed, handler is a - thin wrapper. ~20 LOC each. Used when the user knows exactly what they - want done — no LLM tokens spent on trivial work. +- **Tight actions** (`slack_post`, `linear_create_issue`, + `send_email`) — deterministic single-purpose handlers. ~20 LOC + each. **Phase 2.** +- **Loose actions** (`agent_task`) — params_schema accepts a `prompt`, + a `tools` allowlist, and an optional `output_schema` declaring what + the agent must return; the handler validates the agent's output + against it. **v1.** -- **Loose actions** (`agent_task`): config_schema accepts a `prompt` and a - `tools` allowlist; output_contract is *dynamic* — the user declares the - output shape they want via `output_schema` in the step config; the - handler asks the LLM to return that shape and validates. Used when - judgment is needed. - -The agent's tool list is **the same capabilities** that tight actions call -directly. One registry, two invocation modes. Adding a new MCP server gives -both modes access to its tools automatically. +The agent's `tools` allowlist resolves opaquely in v1; the redesigned +unification layer (§3) will give both invocation modes access to the +same vocabulary, with per-user authorization gating both. ### How names in the definition become function calls -The definition contains strings like `"action": "slack_post"`. The string is -just a name — it does not point to a function. At runtime, the executor -performs a **name-based lookup** against the action registry: +The definition contains strings like `"action": "agent_task"`. The +string is just a name — it does not point to a function. At runtime, +the executor performs a **name-based lookup** against the action +registry: ```python -# step.action is a string from the JSON definition, e.g. "slack_post" -action_def = _ACTION_REGISTRY[step.action] # dict lookup -handler = action_def.handler # Python callable -result = await handler(ctx, resolved_config) # invocation +action_def = action_registry.get(step.action) # dict lookup +handler = action_def.handler # Python callable +result = await handler(resolved_params) # invocation ``` -The registry is a Python dict (or a thin wrapper around one) populated at -process startup. Each entry in `automations/actions/*.py` calls a -`register_action(...)` function at module import time, putting its -`ActionDefinition` (including the handler function reference) into the -registry. +The registry is a Python dict populated at process startup. Each entry +in `automations/registries/actions/*.py` calls `register_action(...)` +at module import time, putting its `ActionDefinition` (including the +handler function reference) into the registry. -The same pattern applies to capabilities. The definition references -capabilities by ID (`"slack.post_message"`); the capability registry maps -the ID to a `Capability` object holding the handler. Definitions never -reference Python code directly — they reference names that the registry -resolves to code. - -This separation is what makes the contract portable. The definition is -pure data. The registry is the engine's runtime vocabulary. They meet at -name-based lookup; nothing else crosses the boundary. +The definition is pure data. The registry is the engine's runtime +vocabulary. They meet at name-based lookup; nothing else crosses the +boundary. ### The full expressive spectrum @@ -238,7 +200,7 @@ fully agentic. Six practical shapes worth recognizing: | **1. Direct call** | `slack_post` with literal channel and template | No LLM. ~200ms. Fractions of a cent. | | **2. Direct call with computed inputs** | `linear_create_issue` using `{{summary.title}}` from a prior step | No LLM for this step. Cheap. | | **3. Single-domain agent task** | `agent_task` with `tools: ["slack.*"]` only | One LLM, bounded toolset. | -| **4. Multi-domain agent task, narrow** | `agent_task` with `tools: ["github.list_pull_requests", "linear.create_issue"]` | One LLM, named capabilities. | +| **4. Multi-domain agent task, narrow** | `agent_task` with `tools: ["github.list_pull_requests", "linear.create_issue"]` | One LLM, named tools. | | **5. Multi-domain agent task, broad** | `agent_task` with `tools: ["slack.*", "github.*", "linear.*"]` | One LLM, large toolset, most agentic. | | **6. Composed plan** | `agent_task` (narrow) for thinking → `slack_post` + `linear_create_issue` for acting | Best cost-to-power ratio. | @@ -258,7 +220,7 @@ user's. --- -## 5. Automation definition (Layer 3) +## 5. Automation definition This is the JSON the user writes (or the NL generator produces). Stored in `automations.definition` as JSONB. @@ -287,7 +249,7 @@ This is the JSON the user writes (or the NL generator produces). Stored in "triggers": [ { "type": "schedule", - "config": { "cron": "0 9 * * 1-5", "timezone": "Africa/Kigali" } + "params": { "cron": "0 9 * * 1-5", "timezone": "Africa/Kigali" } } ], @@ -295,7 +257,7 @@ This is the JSON the user writes (or the NL generator produces). Stored in { "step_id": "research", "action": "agent_task", - "config": { + "params": { "prompt": "Find documents tagged {{inputs.tags}} indexed since {{inputs.since}}. Return JSON with bullets and source_doc_ids.", "tools": ["search_space.query", "search_space.fetch_document"], "model": "anthropic/claude-sonnet-4-7", @@ -313,7 +275,7 @@ This is the JSON the user writes (or the NL generator produces). Stored in { "step_id": "deliver", "action": "slack_post", - "config": { + "params": { "channel_id": "C0123", "message_template": "*Competitor digest*\n\n{% for b in summary.bullets %}• {{b}}\n{% endfor %}" } @@ -325,11 +287,10 @@ This is the JSON the user writes (or the NL generator produces). Stored in "max_retries": 2, "retry_backoff": "exponential", "concurrency": "drop_if_running", - "budget_cap_usd": 1.50, "on_failure": [ /* steps to run if main plan fails after retries */ ] }, - "metadata": { "tags": ["digest"], "created_from_nl": true } + "metadata": { "tags": ["digest"] } } ``` @@ -340,7 +301,7 @@ This is the JSON the user writes (or the NL generator produces). Stored in "step_id": "...", // unique within plan "action": "...", // references an ActionDefinition.type "when": "{{ ... }}", // optional Jinja expr → bool; false = skip - "config": { ... }, // validated against action's config_schema + "params": { ... }, // validated against action's params_schema "output_as": "...", // binds output to this name for later steps "max_retries": 0, // optional, overrides automation default "timeout_seconds": 1200 // optional, overrides automation default @@ -354,7 +315,7 @@ about it, or they compose automations through events (§7.5). --- -## 6. Trigger contract (Layer 4) +## 6. Trigger contract Three trigger types. That's the entire taxonomy. @@ -363,23 +324,12 @@ Three trigger types. That's the entire taxonomy. ```python TriggerDefinition( type="schedule", - config_schema={ - "type": "object", - "required": ["cron", "timezone"], - "properties": { - "cron": { "type": "string" }, - "timezone": { "type": "string", "format": "iana-timezone" } - } - }, - payload_schema={ - "type": "object", - "properties": { - "fired_at": { "type": "string", "format": "date-time" }, - "scheduled_for": { "type": "string", "format": "date-time" }, - "last_fired_at": { "type": "string", "format": "date-time" } - } - } + params_model=ScheduleTriggerParams, # cron + timezone ) +# At fire time the schedule producer emits runtime inputs +# (fired_at, scheduled_for, last_fired_at) which are merged with the +# trigger row's static_inputs (static wins) and validated against +# automation.definition.inputs.schema_. ``` Implementation: extends `app/utils/periodic_scheduler.py`, which already @@ -395,7 +345,7 @@ want an event trigger instead. ```python TriggerDefinition( type="webhook", - config_schema={ + params_schema={ "type": "object", "properties": { "input_mapping": { @@ -422,7 +372,7 @@ Dedups against runs in the last 24 hours. ```python TriggerDefinition( type="event", - config_schema={ + params_schema={ "type": "object", "required": ["event_type"], "properties": { @@ -485,11 +435,13 @@ Common path (after a trigger has fired): 4. **Snapshot the resolved definition** into the run row (immutable history) 5. Enqueue executor task on the single `automations_default` Celery queue -The cost-estimate pre-check (originally step 3) is **deferred**. -v1 capabilities do not declare `cost_estimate`; pre-flight budgeting -returns when a historical-cost ledger exists. The mid-flight budget -cap (§7.2) still kills the run if accumulated cost crosses -`budget_cap_usd`. +The cost-estimate pre-check (originally step 3) is **deferred**. v1 +actions do not declare cost estimates, the run row has no `cost_usd` +column, and no handler reports tokens used — so neither pre-flight +prediction nor mid-flight accumulation can be enforced. `Execution` +therefore does not expose `budget_cap_usd` in v1; it returns as a single +field addition the day the cost ledger ships (per-action cost reporting ++ `automation_runs.cost_usd` column + executor accumulation). Queue routing by `expected_duration_seconds` is **deferred** until load patterns justify a second queue. v1 uses a single queue. @@ -510,15 +462,15 @@ async def execute_run(run_id: int) -> None: if step.when and not evaluate_predicate(step.when, context | step_outputs): record_step_skipped(run, step); continue - resolved_config = render_config(step.config, context | step_outputs) + resolved_params = render_params(step.params, context | step_outputs) action = action_registry.get(step.action) - validate(resolved_config, action.config_schema) + validate(resolved_params, action.params_schema) try: result = await with_retries( action.handler, ctx=build_action_context(run, action), - args=resolved_config, + args=resolved_params, policy=step.retry_policy or run.execution.retry_policy, ) validate(result, step.output_schema) @@ -541,14 +493,20 @@ validated dict come back; it doesn't know that step was "smart." ### 7.3 Action handlers -One handler per `ActionDefinition.type`. Receives `(ctx, args)`, returns -a dict matching `output_contract` (or matching the user-declared -`output_schema` for dynamic-output actions like `agent_task`). +One handler per `ActionDefinition.type`. Receives the validated `args` +dict and returns whatever the step's output validates against (a fixed +shape declared by tight actions, or a dynamic shape declared via +`output_schema` in the step params for `agent_task`). -Handlers handle their own credential resolution via `ctx.resolve_credentials`. -They do not know about retries, timeouts, or budget caps — those are the +Handlers do not know about retries or timeouts — those are the executor's concern. +In v1, handlers take `(args)` only. The `CallContext` parameter sketched +in §7.2's pseudo-code (caller user id, search space id, run id, +credential resolver) arrives with the unification layer redesign (§3); +v1's single action (`agent_task`) reads what it needs from app-level +configuration. + ### 7.4 Template engine #### Why it exists @@ -747,7 +705,7 @@ Three fields, per-automation defaults with optional per-step overrides: - `timeout_seconds`: integer Retries on: -- Capability handler exceptions +- Action handler exceptions - Output schema validation failures (for dynamic-output actions, the validation error is fed back to the LLM in the retry) @@ -755,12 +713,21 @@ Not retries: - `when:` evaluation failures (these are user errors, surface immediately) - Input validation failures (caught at dispatch, never reach the executor) -### Budget enforcement +### Budget enforcement *(deferred — not in v1)* -`budget_cap_usd` is per-run. The dispatcher refuses to enqueue if estimated -cost exceeds it. The executor kills the run if accumulated cost crosses it -mid-flight (the LLM ops handler reports tokens consumed back to the -executor between calls). +Future shape: `budget_cap_usd` on `Execution`, dispatcher refuses to +enqueue if estimated cost exceeds it, executor kills the run if +accumulated cost crosses it mid-flight (the LLM ops handler reports +tokens consumed back to the executor between calls). + +Prerequisites before this can land: +- Each action declares cost reporting (tokens × model price, API call + charges) — `ActionDefinition` has no such field today. +- `automation_runs.cost_usd` column + executor accumulates per step. +- A historical-cost ledger so pre-flight estimation can return useful + numbers (otherwise the dispatcher gate is guessing). + +Until all three exist, v1 has no surface for budget enforcement. ### On-failure handlers @@ -787,14 +754,13 @@ nightly Celery Beat task deletes expired artifacts). ### Duration classes and queue routing — deferred The original design routed runs to multiple Celery queues based on each -capability's declared `expected_duration_seconds`. v1 ships with **one -queue** (`automations_default`) and capabilities do not declare a -duration. Multi-queue routing returns when burst load on a single queue -actually justifies the operational complexity of independent worker -pools. +action's declared `expected_duration_seconds`. v1 ships with **one +queue** (`automations_default`) and actions do not declare a duration. +Multi-queue routing returns when burst load on a single queue actually +justifies the operational complexity of independent worker pools. Adding the second queue is a config change plus reintroducing -`expected_duration_seconds` on the `Capability` dataclass — both +`expected_duration_seconds` on the `ActionDefinition` dataclass — both mechanical, additive, and free of design rewrite. --- @@ -832,14 +798,16 @@ and an immutable run history. ### `automation_triggers` -| field | type | notes | -| --------------- | ----------------------------------------------------------------------------- | ------------------------------------------- | -| `id` | int PK | | -| `automation_id` | FK | | -| `type` | enum: `schedule`, `manual` (Phase 2/3 add `webhook`, `event`) | | -| `config` | jsonb | validated against trigger's `config_schema` | -| `enabled` | bool | | -| `last_fired_at` | timestamp | | +| field | type | notes | +| --------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------- | +| `id` | int PK | | +| `automation_id` | FK | | +| `type` | enum: `schedule`, `manual` (Phase 2/3 add `webhook`, `event`) | | +| `params` | jsonb | trigger-type config, validated against trigger's `params_schema` | +| `static_inputs` | jsonb | per-attachment domain values merged into every run (static wins on collision) | +| `enabled` | bool | | +| `last_fired_at` | timestamp | | +| `next_fire_at` | timestamp / null | precomputed next fire moment for schedule triggers | `secret_hash` (for webhook bearer tokens) is **deferred to Phase 2** with the webhook trigger. @@ -853,8 +821,7 @@ the webhook trigger. | `trigger_id` | FK / null | null = manual via UI | | `status` | enum | `pending`, `running`, `succeeded`, `failed`, `cancelled`, `timed_out` | | `definition_snapshot` | jsonb | the definition as it was when this run fired | -| `trigger_payload` | jsonb | | -| `resolved_inputs` | jsonb | | +| `inputs` | jsonb | merged & validated inputs (trigger.static_inputs ∪ producer runtime data, static wins) | | `step_results` | jsonb | array of per-step results with timing | | `output` | jsonb / null | | | `artifacts` | jsonb | references to created artifacts | @@ -863,7 +830,7 @@ the webhook trigger. | `agent_session_id`| str / null | link to LangGraph trace if agent_task was used | `cost_usd` (per-run accumulated cost) is **deferred** until at least one -v1 capability records token-level cost. When reintroduced it lands as a +action records token-level cost. When reintroduced it lands as a column-only migration. ### Deferred tables @@ -897,8 +864,8 @@ not "trusted authors only." User provides natural-language input. The Generator LLM is given: - The full schema set (input schema for definition, registry of action - types with their config_schemas, registry of trigger types, available - capabilities for this SearchSpace, list of allowed Jinja filters) + types with their params_schemas, registry of trigger types, list of + allowed Jinja filters) - A tool to list available connectors, channels, and other SearchSpace resources, so it doesn't invent names that don't exist - A few-shot set of examples @@ -918,13 +885,13 @@ Output: a structured proposal matching the automation definition schema. Server-side, before the proposal reaches the user: - Validate against JSON Schema (shape correctness) -- Verify every capability referenced exists in the registry (resource existence) +- Verify every action and trigger type referenced exists in the registry - Verify every connector/channel/resource referenced exists in this SearchSpace - Validate every template against the sandbox's allowlist (no underscore attributes, no unregistered filter names, length under cap) Failures here are deterministic errors, not warnings. A proposal that -references a non-existent capability or includes a template using +references a non-existent action or includes a template using `{{x.__class__}}` is rejected before the user sees it; the Generator is re-prompted with the validation error and asked to fix the proposal. @@ -947,7 +914,7 @@ produces two outputs for the user: - Action sequences that touch external systems without obvious benefit to the user - Cost estimates that seem high relative to the goal - - References to capabilities the user hasn't used before + - References to actions the user hasn't used before - Schedules tighter than 15 minutes (likely should be event triggers) The Review LLM is a **UX layer** that makes review actually useful. It is @@ -1009,33 +976,18 @@ always. surfsense_backend/app/ ├── automations/ # NEW: the engine │ ├── __init__.py -│ ├── models.py # SQLAlchemy models for 6 tables -│ ├── schemas.py # Pydantic schemas (definition envelope, etc.) +│ ├── persistence/ # SQLAlchemy models + enums for 3 tables +│ ├── schemas/ # Pydantic schemas (definition envelope, etc.) │ ├── routes.py # FastAPI router (/api/v1/automations) │ ├── service.py # CRUD + business logic -│ ├── dispatcher.py # trigger matching, cost check, run creation +│ ├── dispatcher.py # trigger matching, run creation │ ├── executor.py # the Celery task that runs a plan │ ├── templating.py # Jinja sandbox + filters │ ├── events.py # publish/subscribe for domain_events │ ├── filters.py # JSON filter grammar evaluator -│ ├── actions/ -│ │ ├── registry.py -│ │ ├── agent_task.py -│ │ ├── transform_data.py -│ │ ├── slack_post.py -│ │ ├── send_email.py -│ │ ├── notification.py -│ │ └── (more in Phase 5: podcast_generation, report_generation, ...) -│ ├── triggers/ -│ │ ├── registry.py -│ │ ├── schedule.py # Celery Beat hookup -│ │ ├── webhook.py # /fire endpoint -│ │ └── event.py # subscribes to domain_events -│ ├── capabilities/ -│ │ ├── registry.py -│ │ ├── native.py # native capability registrations -│ │ ├── mcp_harvester.py # registers MCP tools as capabilities (Phase 4) -│ │ └── (LLM ops registered alongside) +│ ├── registries/ # action and trigger registries +│ │ ├── actions/ # ActionDefinition + handler registration +│ │ └── triggers/ # TriggerDefinition │ └── nl/ # Phase 1 — primary user path │ ├── generator.py # Generator LLM │ ├── reviewer.py # Review LLM (summary + flagged items) @@ -1070,23 +1022,22 @@ automations in natural language. **Step 1 (current scope, this batch of commits):** - 3 tables (`automations`, `automation_triggers`, `automation_runs`) + Alembic migration -- Empty Capability, Action, Trigger registries (concrete entries land in - later steps when the consuming feature lands) +- Empty action and trigger registries under + `app/automations/registries/` (concrete entries land in later steps) - Pydantic schemas for the automation definition envelope, the two v1 - trigger configs (`schedule`, `manual`), and the one v1 action config - (`agent_task`) -- Module structure under `app/automations/` (data/, schemas/, + trigger params shapes (`schedule`, `manual`), and the one v1 action + params shape (`agent_task`) +- Module structure under `app/automations/` (persistence/, schemas/, registries/), fully isolated from the existing codebase **Step 2:** -- Register the `agent_task` action and the `schedule` / `manual` - triggers in the registries -- Capability registry populated with native deliverable-producing - capabilities (chosen when this step starts) +- The `agent_task` action handler and the `schedule` / `manual` triggers + registered in `app/automations/registries/`. Tool resolution for + `agent_task.params.tools` is opaque to the contract — the handler + decides what string identifiers it accepts and how they resolve. **Step 3:** -- Executor (single-queue Celery task) with retries, timeouts, budget - caps measured against `cost_usd` ledger on the run +- Executor (single-queue Celery task) with retries and timeouts - Template engine (Jinja sandbox + the v1 filter allowlist + runtime limits) - Manual "Run now" endpoint @@ -1122,19 +1073,23 @@ somewhere humans see, complex pipelines have proper error handling. **After Phase 3**: NL authoring is the polished primary surface; edit flows are conversational rather than form-only. -### Phase 4 — Event triggers +### Phase 4 — Event triggers + integration tooling - `domain_events` table and `events.py` module - Indexing pipeline publishes `connector.*` events (smallest change — just add publish calls to the existing flow) - Automations publish `automation.run.*` events on completion - `event` trigger with filter grammar -- MCP capability harvester (so MCP-backed events and tools both work) +- The unification layer redesign (see §3) — `CallContext`, scope + declarations, per-user authorization gating +- MCP integration on top of the unification layer (external tool servers + harvested into the shared catalog) **After Phase 4**: "do X when Y happens" automations work, including -automation-chaining through events. +automation-chaining through events; external MCP tools and SurfSense +actions share one vocabulary. ### Phase 5 — Wrapping existing features and sharing -- Wrap existing SurfSense capabilities as actions: `podcast_generation`, +- Wrap existing SurfSense features as actions: `podcast_generation`, `report_generation`, `indexing_sweep` - Artifact lifecycle implementation - `expected_duration_seconds` based queue routing (split `automations_long` @@ -1144,7 +1099,7 @@ automation-chaining through events. shift documented in §7.4's pre-Phase-5 gate - Cross-automation composition examples in the docs -**After Phase 5**: every existing SurfSense capability is automatable +**After Phase 5**: every existing SurfSense feature is automatable without any per-feature code, and automations can be shared between SearchSpaces and users. @@ -1156,13 +1111,12 @@ For reference — every decision made through the design process, in one place. ### Foundations -1. ✅ JSON Schema 2020-12 is the single schema language for everything +1. ✅ JSON Schema (draft 2020-12) is the single schema language for everything 2. ✅ Definition is the program; infrastructure is the interpreter 3. ✅ List of steps (not single action) in the plan, with `output_as` chaining -4. ✅ One capability registry serving native + MCP + LLM operations through the same interface -5. ✅ Capability IDs do not leak handler kind (`slack.post_message`, not `mcp.slack.post_message`) -6. ✅ Name-based resolution: definitions reference actions and capabilities by string ID. The registry is the runtime's vocabulary; lookup is a dict access. No code references in definitions. -7. ✅ The expressive spectrum runs from pure direct calls to broad agent_task; the NL generator proposes the cheapest shape that meets intent (Shape 6 from §4 by default) +4. ⏸ Capability unification layer (one catalog shared by automations, agents, and future surfaces) — **deferred to post-v1** (see §3). v1 ships actions only. +5. ✅ Name-based resolution: definitions reference action and trigger types by string ID. The registry is the runtime's vocabulary; lookup is a dict access. No code references in definitions. +6. ✅ The expressive spectrum runs from pure direct calls to broad agent_task; the NL generator proposes the cheapest shape that meets intent (Shape 6 from §4 by default) ### Trigger taxonomy 8. ✅ Three trigger types: `schedule`, `webhook`, `event` @@ -1183,7 +1137,7 @@ place. 19. ✅ No DAGs, no parallelism, no loops — composition via agent_task or events 20. ✅ `on_failure` part of execution policy from v1 21. ✅ Step-level retry and timeout overrides -22. ✅ Budget cap enforced pre-enqueue and mid-flight +22. ⏸ Budget cap enforced pre-enqueue and mid-flight — **deferred** until the cost ledger ships (see §8 Budget enforcement) ### Components 23. ✅ Dispatcher / executor / handlers / registry — distinct, each replaceable @@ -1197,25 +1151,22 @@ place. 29. ✅ Automations publish run events for composability 30. ✅ Publish/subscribe behind interface — no direct table access elsewhere -### Capability storage -31. ✅ Native capabilities registered in-memory at startup from the codebase. Identical across all workers. -32. ⏸ MCP capability metadata persisted in `mcp_connections` and `mcp_tools` tables — **deferred to Phase 4** -33. ⏸ MCP handler closures built lazily per worker from database state — **deferred to Phase 4** -34. ⏸ MCP server tool list re-harvested on a schedule — **deferred to Phase 4** -35. ⏸ MCP tools harvested into the capability registry at connection time — **deferred to Phase 4** -36. ⏸ Side effects inferred from MCP hints + naming + admin overrides — **deferred to Phase 4** -37. ⏸ MCP tools callable directly (no agent required) when caller knows args — **deferred to Phase 4** +### Capability unification — all deferred to post-v1 +31. ⏸ One shared catalog of "things this SurfSense instance can do" — **deferred**, see §3 +32. ⏸ Handler `CallContext` (caller user id, search space id, run id) — **deferred** with unification +33. ⏸ Per-capability scope declarations driving authorization — **deferred** with unification +34. ⏸ MCP integration on top of the unification layer (`mcp_connections`, `mcp_tools`, harvester) — **deferred to Phase 4** ### Credentials — all deferred to Phase 2 -38. ⏸ Credentials never appear in the automation definition — only connection IDs do — **Phase 2** -39. ⏸ Credentials never appear in the LLM's context — the host holds them — **Phase 2** -40. ⏸ Credentials resolved per-call by `ActionContext`, not pre-loaded into worker environment — **Phase 2** -41. ⏸ Tokens encrypted at rest; refresh handled automatically by `ActionContext.resolve_*_client` — **Phase 2** +35. ⏸ Credentials never appear in the automation definition — only connection IDs do — **Phase 2** +36. ⏸ Credentials never appear in the LLM's context — the host holds them — **Phase 2** +37. ⏸ Credentials resolved per-call by the handler context, not pre-loaded into worker environment — **Phase 2** +38. ⏸ Tokens encrypted at rest; refresh handled automatically by the handler context — **Phase 2** -### v1-minimum (new lock) -v1. ✅ `Capability` is exactly five fields: `id`, `description`, `input_schema`, `output_schema`, `handler`. Additional fields are added only when a concrete consumer feature requires them. -v2. ✅ Cost is **measured** from a per-run ledger, not declared. Pre-flight cost checks return when the ledger has enough history. -v3. ✅ Single `automations_default` Celery queue in v1. Multi-queue routing returns when load justifies it. +### v1-minimum +39. ✅ v1 ships actions only — no separate capability layer. `ActionDefinition` is five fields: `type`, `name`, `description`, `params_schema`, `handler`. Additional fields are added only when a concrete consumer feature requires them. +40. ✅ Cost is **measured** from a per-run ledger, not declared. Pre-flight cost checks return when the ledger has enough history. +41. ✅ Single `automations_default` Celery queue in v1. Multi-queue routing returns when load justifies it. ### NL authoring 42. ✅ LLM-authored templates is the primary path from day one — not a Phase 3 addition. Hand-authoring JSON is supported but secondary @@ -1227,7 +1178,7 @@ v3. ✅ Single `automations_default` Celery queue in v1. Multi-queue routing ret 48. ✅ NL drafts are transient storage, not a core table ### Data model -49. ✅ Six tables total — four for engine state, two for MCP persistence +49. ✅ v1 ships three tables (`automations`, `automation_triggers`, `automation_runs`). `domain_events` lands in Phase 3; `mcp_connections` and `mcp_tools` in Phase 4. 50. ✅ Run rows snapshot the definition (immutable history) 51. ✅ All entities scoped by `search_space_id` for RBAC 52. ✅ Editing an automation bumps `version`; existing runs unaffected @@ -1283,7 +1234,7 @@ Schemas spelled out concretely. Those follow mechanically from this plan. agent (cron and skills subsystems); n8n documentation on node types and workflow data model; the SurfSense repository and DeepWiki architecture notes (FastAPI + Celery Beat + Electric SQL + LangGraph Deep Agents + -Search Space RBAC); Model Context Protocol specification for capability -harvesting; AWS EventBridge for filter grammar; workflow-pattern +Search Space RBAC); Model Context Protocol specification for external +tool harvesting; AWS EventBridge for filter grammar; workflow-pattern literature (van der Aalst et al.) for the trigger / action / concurrency vocabulary.* diff --git a/surfsense_backend/alembic/versions/144_add_automation_tables.py b/surfsense_backend/alembic/versions/144_add_automation_tables.py index 6daf4075f..8d836095d 100644 --- a/surfsense_backend/alembic/versions/144_add_automation_tables.py +++ b/surfsense_backend/alembic/versions/144_add_automation_tables.py @@ -87,6 +87,7 @@ def upgrade() -> None: REFERENCES automations(id) ON DELETE CASCADE, type automation_trigger_type NOT NULL, params JSONB NOT NULL, + static_inputs JSONB NOT NULL DEFAULT '{}'::jsonb, enabled BOOLEAN NOT NULL DEFAULT true, last_fired_at TIMESTAMP WITH TIME ZONE, next_fire_at TIMESTAMP WITH TIME ZONE, @@ -129,8 +130,7 @@ def upgrade() -> None: REFERENCES automation_triggers(id) ON DELETE SET NULL, status automation_run_status NOT NULL DEFAULT 'pending', definition_snapshot JSONB NOT NULL, - trigger_payload JSONB, - resolved_inputs JSONB NOT NULL DEFAULT '{}'::jsonb, + inputs JSONB NOT NULL DEFAULT '{}'::jsonb, step_results JSONB NOT NULL DEFAULT '[]'::jsonb, output JSONB, artifacts JSONB NOT NULL DEFAULT '[]'::jsonb, diff --git a/surfsense_backend/app/automations/actions/agent_task/definition.py b/surfsense_backend/app/automations/actions/agent_task/definition.py index d7db5cfcd..7d14dc49e 100644 --- a/surfsense_backend/app/automations/actions/agent_task/definition.py +++ b/surfsense_backend/app/automations/actions/agent_task/definition.py @@ -11,7 +11,7 @@ AGENT_TASK_ACTION = ActionDefinition( type="agent_task", name="Agent task", description="Run a multi_agent_chat turn from an automation step.", - params_schema=AgentTaskActionParams.model_json_schema(), + params_model=AgentTaskActionParams, build_handler=build_handler, ) diff --git a/surfsense_backend/app/automations/actions/types.py b/surfsense_backend/app/automations/actions/types.py index 433c60841..2c4ffad8d 100644 --- a/surfsense_backend/app/automations/actions/types.py +++ b/surfsense_backend/app/automations/actions/types.py @@ -7,6 +7,7 @@ from dataclasses import dataclass from typing import Any from uuid import UUID +from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession @@ -30,5 +31,10 @@ class ActionDefinition: type: str name: str description: str - params_schema: dict[str, Any] + params_model: type[BaseModel] build_handler: ActionHandlerFactory + + @property + def params_schema(self) -> dict[str, Any]: + """JSON Schema (draft 2020-12) derived from ``params_model``.""" + return self.params_model.model_json_schema() diff --git a/surfsense_backend/app/automations/api/__init__.py b/surfsense_backend/app/automations/api/__init__.py index 459c6c1b4..a18e91a95 100644 --- a/surfsense_backend/app/automations/api/__init__.py +++ b/surfsense_backend/app/automations/api/__init__.py @@ -5,8 +5,12 @@ from __future__ import annotations from fastapi import APIRouter from .automation import router as automation_router +from .run import router as run_router +from .trigger import router as trigger_router router = APIRouter() router.include_router(automation_router) +router.include_router(trigger_router) +router.include_router(run_router) __all__ = ["router"] diff --git a/surfsense_backend/app/automations/api/automation.py b/surfsense_backend/app/automations/api/automation.py index 4d0ce7209..b67f0af09 100644 --- a/surfsense_backend/app/automations/api/automation.py +++ b/surfsense_backend/app/automations/api/automation.py @@ -1,23 +1,80 @@ -"""Routes for the ``Automation`` resource.""" +"""HTTP routes for the ``Automation`` resource.""" from __future__ import annotations -from typing import Any +from fastapi import APIRouter, Depends, Query, status -from fastapi import APIRouter, Body, Depends - -from app.automations.api.schemas import RunDispatched +from app.automations.schemas.api import ( + AutomationCreate, + AutomationDetail, + AutomationList, + AutomationSummary, + AutomationUpdate, +) from app.automations.services import AutomationService, get_automation_service router = APIRouter() -@router.post("/automations/{automation_id}/run", response_model=RunDispatched) -async def run_automation_now( - automation_id: int, - payload: dict[str, Any] | None = Body(default=None), +@router.post( + "/automations", + response_model=AutomationDetail, + status_code=status.HTTP_201_CREATED, +) +async def create_automation( + payload: AutomationCreate, service: AutomationService = Depends(get_automation_service), -) -> RunDispatched: - """Fire a manual run.""" - run = await service.run_now(automation_id=automation_id, payload=payload) - return RunDispatched(run_id=run.id, status=run.status) +) -> AutomationDetail: + """Create an automation, optionally with initial triggers (atomic).""" + automation = await service.create(payload) + return AutomationDetail.model_validate(automation) + + +@router.get("/automations", response_model=AutomationList) +async def list_automations( + search_space_id: int = Query(...), + limit: int = Query(default=50, ge=1, le=200), + offset: int = Query(default=0, ge=0), + service: AutomationService = Depends(get_automation_service), +) -> AutomationList: + """List automations in a search space.""" + items, total = await service.list( + search_space_id=search_space_id, limit=limit, offset=offset + ) + return AutomationList( + items=[AutomationSummary.model_validate(a) for a in items], + total=total, + ) + + +@router.get("/automations/{automation_id}", response_model=AutomationDetail) +async def get_automation( + automation_id: int, + service: AutomationService = Depends(get_automation_service), +) -> AutomationDetail: + """Get one automation with its definition and triggers.""" + automation = await service.get(automation_id) + return AutomationDetail.model_validate(automation) + + +@router.patch("/automations/{automation_id}", response_model=AutomationDetail) +async def update_automation( + automation_id: int, + patch: AutomationUpdate, + service: AutomationService = Depends(get_automation_service), +) -> AutomationDetail: + """Partially update an automation. Triggers are managed separately.""" + automation = await service.update(automation_id, patch) + return AutomationDetail.model_validate(automation) + + +@router.delete( + "/automations/{automation_id}", + status_code=status.HTTP_204_NO_CONTENT, +) +async def delete_automation( + automation_id: int, + service: AutomationService = Depends(get_automation_service), +) -> None: + """Delete an automation; triggers and runs are removed by FK cascade.""" + await service.delete(automation_id) diff --git a/surfsense_backend/app/automations/api/run.py b/surfsense_backend/app/automations/api/run.py new file mode 100644 index 000000000..d0d4bbfb7 --- /dev/null +++ b/surfsense_backend/app/automations/api/run.py @@ -0,0 +1,71 @@ +"""HTTP routes for automation runs (dispatch + history).""" + +from __future__ import annotations + +from typing import Any + +from fastapi import APIRouter, Body, Depends, Query, status + +from app.automations.schemas.api import ( + RunDetail, + RunDispatched, + RunList, + RunSummary, +) +from app.automations.services import RunService, get_run_service + +router = APIRouter() + + +@router.post( + "/automations/{automation_id}/run", + response_model=RunDispatched, + status_code=status.HTTP_202_ACCEPTED, +) +async def run_automation_now( + automation_id: int, + inputs: dict[str, Any] | None = Body(default=None), + service: RunService = Depends(get_run_service), +) -> RunDispatched: + """Fire a manual run. + + ``inputs`` is the runtime payload supplied by the caller; it is merged with + the manual trigger's ``static_inputs`` (static wins) and validated against + the automation's input schema. + """ + run = await service.dispatch_manual(automation_id=automation_id, runtime_inputs=inputs) + return RunDispatched(run_id=run.id, status=run.status) + + +@router.get( + "/automations/{automation_id}/runs", + response_model=RunList, +) +async def list_runs( + automation_id: int, + limit: int = Query(default=50, ge=1, le=200), + offset: int = Query(default=0, ge=0), + service: RunService = Depends(get_run_service), +) -> RunList: + """List run history for an automation, newest first.""" + items, total = await service.list( + automation_id=automation_id, limit=limit, offset=offset + ) + return RunList( + items=[RunSummary.model_validate(r) for r in items], + total=total, + ) + + +@router.get( + "/automations/{automation_id}/runs/{run_id}", + response_model=RunDetail, +) +async def get_run( + automation_id: int, + run_id: int, + service: RunService = Depends(get_run_service), +) -> RunDetail: + """Get the full record of a single run, including step results and artifacts.""" + run = await service.get(automation_id=automation_id, run_id=run_id) + return RunDetail.model_validate(run) diff --git a/surfsense_backend/app/automations/api/trigger.py b/surfsense_backend/app/automations/api/trigger.py new file mode 100644 index 000000000..40e47a86b --- /dev/null +++ b/surfsense_backend/app/automations/api/trigger.py @@ -0,0 +1,55 @@ +"""HTTP routes for triggers attached to an automation.""" + +from __future__ import annotations + +from fastapi import APIRouter, Depends, status + +from app.automations.schemas.api import TriggerCreate, TriggerDetail, TriggerUpdate +from app.automations.services import TriggerService, get_trigger_service + +router = APIRouter() + + +@router.post( + "/automations/{automation_id}/triggers", + response_model=TriggerDetail, + status_code=status.HTTP_201_CREATED, +) +async def add_trigger( + automation_id: int, + payload: TriggerCreate, + service: TriggerService = Depends(get_trigger_service), +) -> TriggerDetail: + """Attach a new trigger to an automation.""" + trigger = await service.add(automation_id=automation_id, payload=payload) + return TriggerDetail.model_validate(trigger) + + +@router.patch( + "/automations/{automation_id}/triggers/{trigger_id}", + response_model=TriggerDetail, +) +async def update_trigger( + automation_id: int, + trigger_id: int, + patch: TriggerUpdate, + service: TriggerService = Depends(get_trigger_service), +) -> TriggerDetail: + """Toggle ``enabled`` or replace ``params``. Trigger type is immutable.""" + trigger = await service.update( + automation_id=automation_id, trigger_id=trigger_id, patch=patch + ) + return TriggerDetail.model_validate(trigger) + + +@router.delete( + "/automations/{automation_id}/triggers/{trigger_id}", + status_code=status.HTTP_204_NO_CONTENT, +) +async def remove_trigger( + automation_id: int, + trigger_id: int, + service: TriggerService = Depends(get_trigger_service), +) -> None: + """Detach a trigger from an automation.""" + await service.remove(automation_id=automation_id, trigger_id=trigger_id) diff --git a/surfsense_backend/app/automations/dispatch/run.py b/surfsense_backend/app/automations/dispatch/run.py index fd5107a18..e317a13b9 100644 --- a/surfsense_backend/app/automations/dispatch/run.py +++ b/surfsense_backend/app/automations/dispatch/run.py @@ -22,10 +22,14 @@ async def dispatch_run( session: AsyncSession, automation: Automation, trigger: AutomationTrigger, - payload: dict[str, Any] | None, + runtime_inputs: dict[str, Any] | None = None, ) -> AutomationRun: """Validate, snapshot the definition, persist an ``AutomationRun``, enqueue execution. + Final inputs = ``trigger.static_inputs`` merged with ``runtime_inputs``, + static winning on key collision. The merged dict is validated against + ``automation.definition.inputs.schema_`` and stored on the run. + Callers (trigger-specific adapters) are responsible for resolving ``automation`` and ``trigger`` and for the trigger-side ``ACTIVE`` / ``enabled`` guards. This function only handles what's identical across @@ -36,7 +40,8 @@ async def dispatch_run( except Exception as exc: raise DispatchError(f"invalid automation definition: {exc}") from exc - resolved_inputs = _validate_inputs(definition, payload or {}) + merged_inputs = {**(runtime_inputs or {}), **(trigger.static_inputs or {})} + validated_inputs = _validate_inputs(definition, merged_inputs) snapshot = definition.model_dump(mode="json", by_alias=True) run = AutomationRun( @@ -44,8 +49,7 @@ async def dispatch_run( trigger_id=trigger.id, status=RunStatus.PENDING, definition_snapshot=snapshot, - trigger_payload=payload, - resolved_inputs=resolved_inputs, + inputs=validated_inputs, step_results=[], artifacts=[], ) @@ -61,12 +65,12 @@ async def dispatch_run( def _validate_inputs( - definition: AutomationDefinition, payload: dict[str, Any] + definition: AutomationDefinition, inputs: dict[str, Any] ) -> dict[str, Any]: if definition.inputs is None or not definition.inputs.schema_: return {} try: - jsonschema.validate(instance=payload, schema=definition.inputs.schema_) + jsonschema.validate(instance=inputs, schema=definition.inputs.schema_) except jsonschema.ValidationError as exc: raise DispatchError(f"inputs: {exc.message}") from exc - return payload + return inputs diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index fdc355e8f..81b33c37c 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -45,8 +45,9 @@ class AutomationRun(BaseModel, TimestampMixin): # locked at fire time so historical runs always show the exact code path definition_snapshot = Column(JSONB, nullable=False) - trigger_payload = Column(JSONB, nullable=True) - resolved_inputs = Column(JSONB, nullable=False, server_default="{}") + # merged & validated inputs the run was dispatched with + # (trigger.static_inputs ∪ producer runtime data, static wins on collision) + inputs = Column(JSONB, nullable=False, server_default="{}") # one entry per executed step; agent_task entries carry their own # `agent_session_id` inside their entry step_results = Column(JSONB, nullable=False, server_default="[]") diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py index b09bc3419..72d1d8d07 100644 --- a/surfsense_backend/app/automations/persistence/models/trigger.py +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -36,6 +36,10 @@ class AutomationTrigger(BaseModel, TimestampMixin): params = Column(JSONB, nullable=False) + # Per-attachment domain values merged into every dispatched run's inputs. + # Static wins over runtime data on key collision. + static_inputs = Column(JSONB, nullable=False, server_default="{}") + enabled = Column( Boolean, nullable=False, diff --git a/surfsense_backend/app/automations/runtime/executor.py b/surfsense_backend/app/automations/runtime/executor.py index ced44fb9b..b8a377e5b 100644 --- a/surfsense_backend/app/automations/runtime/executor.py +++ b/surfsense_backend/app/automations/runtime/executor.py @@ -106,7 +106,7 @@ def _build_template_ctx(run: AutomationRun, step_outputs: dict[str, Any]) -> dic trigger_type=trigger.type.value if trigger else None, started_at=run.started_at, attempt=1, - resolved_inputs=run.resolved_inputs or {}, + inputs=run.inputs or {}, step_outputs=step_outputs, ) diff --git a/surfsense_backend/app/automations/api/schemas/__init__.py b/surfsense_backend/app/automations/schemas/api/__init__.py similarity index 100% rename from surfsense_backend/app/automations/api/schemas/__init__.py rename to surfsense_backend/app/automations/schemas/api/__init__.py diff --git a/surfsense_backend/app/automations/api/schemas/automation.py b/surfsense_backend/app/automations/schemas/api/automation.py similarity index 100% rename from surfsense_backend/app/automations/api/schemas/automation.py rename to surfsense_backend/app/automations/schemas/api/automation.py diff --git a/surfsense_backend/app/automations/api/schemas/run.py b/surfsense_backend/app/automations/schemas/api/run.py similarity index 92% rename from surfsense_backend/app/automations/api/schemas/run.py rename to surfsense_backend/app/automations/schemas/api/run.py index 789b6f674..42ea7ac14 100644 --- a/surfsense_backend/app/automations/api/schemas/run.py +++ b/surfsense_backend/app/automations/schemas/api/run.py @@ -28,8 +28,7 @@ class RunDetail(RunSummary): """Full run view including snapshot, results and artifacts.""" definition_snapshot: dict[str, Any] - trigger_payload: dict[str, Any] | None = None - resolved_inputs: dict[str, Any] + inputs: dict[str, Any] step_results: list[dict[str, Any]] output: dict[str, Any] | None = None artifacts: list[dict[str, Any]] diff --git a/surfsense_backend/app/automations/api/schemas/trigger.py b/surfsense_backend/app/automations/schemas/api/trigger.py similarity index 87% rename from surfsense_backend/app/automations/api/schemas/trigger.py rename to surfsense_backend/app/automations/schemas/api/trigger.py index 32afe7c60..35176fb9f 100644 --- a/surfsense_backend/app/automations/api/schemas/trigger.py +++ b/surfsense_backend/app/automations/schemas/api/trigger.py @@ -17,6 +17,7 @@ class TriggerCreate(BaseModel): type: TriggerType params: dict[str, Any] = Field(default_factory=dict) + static_inputs: dict[str, Any] = Field(default_factory=dict) enabled: bool = True @@ -27,6 +28,7 @@ class TriggerUpdate(BaseModel): enabled: bool | None = None params: dict[str, Any] | None = None + static_inputs: dict[str, Any] | None = None class TriggerDetail(BaseModel): @@ -37,6 +39,7 @@ class TriggerDetail(BaseModel): id: int type: TriggerType params: dict[str, Any] + static_inputs: dict[str, Any] enabled: bool last_fired_at: datetime | None = None next_fire_at: datetime | None = None diff --git a/surfsense_backend/app/automations/services/__init__.py b/surfsense_backend/app/automations/services/__init__.py index f0a97d216..597aca98a 100644 --- a/surfsense_backend/app/automations/services/__init__.py +++ b/surfsense_backend/app/automations/services/__init__.py @@ -1,7 +1,16 @@ -"""Service layer for the automations feature.""" +"""Services for the automations HTTP layer (one service per resource).""" from __future__ import annotations from .automation import AutomationService, get_automation_service +from .run import RunService, get_run_service +from .trigger import TriggerService, get_trigger_service -__all__ = ["AutomationService", "get_automation_service"] +__all__ = [ + "AutomationService", + "RunService", + "TriggerService", + "get_automation_service", + "get_run_service", + "get_trigger_service", +] diff --git a/surfsense_backend/app/automations/services/automation.py b/surfsense_backend/app/automations/services/automation.py index 2a921e331..9140da3b5 100644 --- a/surfsense_backend/app/automations/services/automation.py +++ b/surfsense_backend/app/automations/services/automation.py @@ -2,54 +2,111 @@ from __future__ import annotations -from typing import Any +from datetime import UTC, datetime from fastapi import Depends, HTTPException +from pydantic import ValidationError +from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy.orm import selectinload -from app.automations.dispatch import DispatchError +from app.automations.schemas.api import ( + AutomationCreate, + AutomationUpdate, + TriggerCreate, +) +from app.automations.persistence.enums.trigger_type import TriggerType from app.automations.persistence.models.automation import Automation -from app.automations.persistence.models.run import AutomationRun -from app.automations.triggers.manual import dispatch_manual_run +from app.automations.persistence.models.trigger import AutomationTrigger +from app.automations.triggers import get_trigger +from app.automations.triggers.schedule import compute_next_fire_at from app.db import Permission, User, get_async_session from app.users import current_active_user from app.utils.rbac import check_permission class AutomationService: - """Service for the ``Automation`` resource.""" + """Lifecycle of the ``Automation`` resource.""" def __init__(self, *, session: AsyncSession, user: User) -> None: self.session = session self.user = user - async def run_now( + async def create(self, payload: AutomationCreate) -> Automation: + """Create an automation and its initial triggers in one transaction.""" + await self._authorize(payload.search_space_id, Permission.AUTOMATIONS_CREATE.value) + + automation = Automation( + search_space_id=payload.search_space_id, + created_by_user_id=self.user.id, + name=payload.name, + description=payload.description, + definition=payload.definition.model_dump(mode="json", by_alias=True), + version=1, + ) + for spec in payload.triggers: + automation.triggers.append(_build_trigger(spec)) + + self.session.add(automation) + await self.session.commit() + return await self._get_with_triggers_or_raise(automation.id) + + async def list( self, *, - automation_id: int, - payload: dict[str, Any] | None, - ) -> AutomationRun: - """Fire a manual run for ``automation_id``.""" - automation = await self._get_automation_or_raise(automation_id) - await check_permission( - self.session, - self.user, - automation.search_space_id, - Permission.AUTOMATIONS_EXECUTE.value, - "You don't have permission to execute automations in this search space", + search_space_id: int, + limit: int, + offset: int, + ) -> tuple[list[Automation], int]: + """Return a page of automations and the total count.""" + await self._authorize(search_space_id, Permission.AUTOMATIONS_READ.value) + + base = select(Automation).where(Automation.search_space_id == search_space_id) + total = await self.session.scalar( + select(func.count()).select_from(base.subquery()) ) - try: - return await dispatch_manual_run( - session=self.session, - automation_id=automation_id, - payload=payload, + rows = ( + await self.session.execute( + base.order_by(Automation.created_at.desc()).limit(limit).offset(offset) ) - except DispatchError as exc: - raise HTTPException(status_code=422, detail=str(exc)) from exc + ).scalars().all() + return list(rows), int(total or 0) - async def _get_automation_or_raise(self, automation_id: int) -> Automation: - """Get the automation by id; 404 if missing.""" + async def get(self, automation_id: int) -> Automation: + """Get an automation with its triggers loaded.""" + automation = await self._get_with_triggers_or_raise(automation_id) + await self._authorize(automation.search_space_id, Permission.AUTOMATIONS_READ.value) + return automation + + async def update(self, automation_id: int, patch: AutomationUpdate) -> Automation: + """Patch fields. Bumps ``version`` when ``definition`` changes.""" + automation = await self._get_with_triggers_or_raise(automation_id) + await self._authorize(automation.search_space_id, Permission.AUTOMATIONS_UPDATE.value) + + data = patch.model_dump(exclude_unset=True) + + if "name" in data: + automation.name = data["name"] + if "description" in data: + automation.description = data["description"] + if "status" in data: + automation.status = data["status"] + if "definition" in data: + automation.definition = patch.definition.model_dump(mode="json", by_alias=True) + automation.version += 1 + + await self.session.commit() + return await self._get_with_triggers_or_raise(automation_id) + + async def delete(self, automation_id: int) -> None: + """Delete an automation; FK cascades remove triggers and runs.""" + automation = await self._get_or_raise(automation_id) + await self._authorize(automation.search_space_id, Permission.AUTOMATIONS_DELETE.value) + await self.session.delete(automation) + await self.session.commit() + + async def _get_or_raise(self, automation_id: int) -> Automation: automation = await self.session.get(Automation, automation_id) if automation is None: raise HTTPException( @@ -57,6 +114,56 @@ class AutomationService: ) return automation + async def _get_with_triggers_or_raise(self, automation_id: int) -> Automation: + stmt = ( + select(Automation) + .where(Automation.id == automation_id) + .options(selectinload(Automation.triggers)) + ) + automation = (await self.session.execute(stmt)).scalar_one_or_none() + if automation is None: + raise HTTPException( + status_code=404, detail=f"automation {automation_id} not found" + ) + return automation + + async def _authorize(self, search_space_id: int, permission: str) -> None: + await check_permission( + self.session, + self.user, + search_space_id, + permission, + f"You don't have permission to {permission.split(':')[1]} automations in this search space", + ) + + +def _build_trigger(spec: TriggerCreate) -> AutomationTrigger: + """Validate trigger params via its registered Pydantic model and build the ORM row.""" + definition = get_trigger(spec.type.value) + if definition is None: + raise HTTPException(status_code=422, detail=f"unknown trigger type {spec.type.value!r}") + + try: + validated = definition.params_model.model_validate(spec.params) + except ValidationError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc + + params = validated.model_dump(mode="json") + + next_fire_at = None + if spec.type == TriggerType.SCHEDULE and spec.enabled: + next_fire_at = compute_next_fire_at( + params["cron"], params["timezone"], after=datetime.now(UTC) + ) + + return AutomationTrigger( + type=spec.type, + params=params, + static_inputs=spec.static_inputs, + enabled=spec.enabled, + next_fire_at=next_fire_at, + ) + def get_automation_service( session: AsyncSession = Depends(get_async_session), diff --git a/surfsense_backend/app/automations/services/run.py b/surfsense_backend/app/automations/services/run.py new file mode 100644 index 000000000..92d79e9bc --- /dev/null +++ b/surfsense_backend/app/automations/services/run.py @@ -0,0 +1,93 @@ +"""``RunService`` — dispatch and history of automation runs.""" + +from __future__ import annotations + +from typing import Any + +from fastapi import Depends, HTTPException +from sqlalchemy import func, select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.dispatch import DispatchError +from app.automations.persistence.models.automation import Automation +from app.automations.persistence.models.run import AutomationRun +from app.automations.triggers.manual import dispatch_manual_run +from app.db import Permission, User, get_async_session +from app.users import current_active_user +from app.utils.rbac import check_permission + + +class RunService: + """Lifecycle of the ``AutomationRun`` resource.""" + + def __init__(self, *, session: AsyncSession, user: User) -> None: + self.session = session + self.user = user + + async def dispatch_manual( + self, + *, + automation_id: int, + runtime_inputs: dict[str, Any] | None, + ) -> AutomationRun: + """Fire a manual run via the registered manual trigger.""" + await self._authorize(automation_id, Permission.AUTOMATIONS_EXECUTE.value) + try: + return await dispatch_manual_run( + session=self.session, + automation_id=automation_id, + runtime_inputs=runtime_inputs, + ) + except DispatchError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc + + async def list( + self, + *, + automation_id: int, + limit: int, + offset: int, + ) -> tuple[list[AutomationRun], int]: + """Return a page of runs for an automation, newest first.""" + await self._authorize(automation_id, Permission.AUTOMATIONS_READ.value) + + base = select(AutomationRun).where(AutomationRun.automation_id == automation_id) + total = await self.session.scalar( + select(func.count()).select_from(base.subquery()) + ) + + rows = ( + await self.session.execute( + base.order_by(AutomationRun.created_at.desc()).limit(limit).offset(offset) + ) + ).scalars().all() + return list(rows), int(total or 0) + + async def get(self, *, automation_id: int, run_id: int) -> AutomationRun: + await self._authorize(automation_id, Permission.AUTOMATIONS_READ.value) + run = await self.session.get(AutomationRun, run_id) + if run is None or run.automation_id != automation_id: + raise HTTPException(status_code=404, detail=f"run {run_id} not found") + return run + + async def _authorize(self, automation_id: int, permission: str) -> Automation: + automation = await self.session.get(Automation, automation_id) + if automation is None: + raise HTTPException( + status_code=404, detail=f"automation {automation_id} not found" + ) + await check_permission( + self.session, + self.user, + automation.search_space_id, + permission, + f"You don't have permission to {permission.split(':')[1]} automations in this search space", + ) + return automation + + +def get_run_service( + session: AsyncSession = Depends(get_async_session), + user: User = Depends(current_active_user), +) -> RunService: + return RunService(session=session, user=user) diff --git a/surfsense_backend/app/automations/services/trigger.py b/surfsense_backend/app/automations/services/trigger.py new file mode 100644 index 000000000..33e9c1386 --- /dev/null +++ b/surfsense_backend/app/automations/services/trigger.py @@ -0,0 +1,143 @@ +"""``TriggerService`` — lifecycle of triggers attached to an automation.""" + +from __future__ import annotations + +from datetime import UTC, datetime + +from fastapi import Depends, HTTPException +from pydantic import ValidationError +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.schemas.api import TriggerCreate, TriggerUpdate +from app.automations.persistence.enums.trigger_type import TriggerType +from app.automations.persistence.models.automation import Automation +from app.automations.persistence.models.trigger import AutomationTrigger +from app.automations.triggers import get_trigger +from app.automations.triggers.schedule import compute_next_fire_at +from app.db import Permission, User, get_async_session +from app.users import current_active_user +from app.utils.rbac import check_permission + + +class TriggerService: + """Lifecycle of the ``AutomationTrigger`` sub-resource.""" + + def __init__(self, *, session: AsyncSession, user: User) -> None: + self.session = session + self.user = user + + async def add( + self, *, automation_id: int, payload: TriggerCreate + ) -> AutomationTrigger: + automation = await self._authorize_automation( + automation_id, Permission.AUTOMATIONS_UPDATE.value + ) + + validated_params = _validate_params(payload.type, payload.params) + trigger = AutomationTrigger( + automation_id=automation.id, + type=payload.type, + params=validated_params, + static_inputs=payload.static_inputs, + enabled=payload.enabled, + next_fire_at=_initial_next_fire(payload.type, validated_params, payload.enabled), + ) + self.session.add(trigger) + await self.session.commit() + await self.session.refresh(trigger) + return trigger + + async def update( + self, + *, + automation_id: int, + trigger_id: int, + patch: TriggerUpdate, + ) -> AutomationTrigger: + await self._authorize_automation(automation_id, Permission.AUTOMATIONS_UPDATE.value) + trigger = await self._get_trigger_or_raise(automation_id, trigger_id) + + data = patch.model_dump(exclude_unset=True) + + if "params" in data: + trigger.params = _validate_params(trigger.type, data["params"]) + + if "static_inputs" in data: + trigger.static_inputs = data["static_inputs"] + + if "enabled" in data: + trigger.enabled = data["enabled"] + + # Recompute next_fire_at when schedule timing changed or the trigger was + # toggled back on. Manual triggers always have NULL next_fire_at. + if trigger.type == TriggerType.SCHEDULE: + trigger.next_fire_at = _initial_next_fire( + trigger.type, trigger.params, trigger.enabled + ) + + await self.session.commit() + await self.session.refresh(trigger) + return trigger + + async def remove(self, *, automation_id: int, trigger_id: int) -> None: + await self._authorize_automation(automation_id, Permission.AUTOMATIONS_UPDATE.value) + trigger = await self._get_trigger_or_raise(automation_id, trigger_id) + await self.session.delete(trigger) + await self.session.commit() + + async def _authorize_automation( + self, automation_id: int, permission: str + ) -> Automation: + automation = await self.session.get(Automation, automation_id) + if automation is None: + raise HTTPException( + status_code=404, detail=f"automation {automation_id} not found" + ) + await check_permission( + self.session, + self.user, + automation.search_space_id, + permission, + f"You don't have permission to {permission.split(':')[1]} automations in this search space", + ) + return automation + + async def _get_trigger_or_raise( + self, automation_id: int, trigger_id: int + ) -> AutomationTrigger: + trigger = await self.session.get(AutomationTrigger, trigger_id) + if trigger is None or trigger.automation_id != automation_id: + raise HTTPException( + status_code=404, detail=f"trigger {trigger_id} not found" + ) + return trigger + + +def _validate_params(trigger_type: TriggerType, raw: dict) -> dict: + definition = get_trigger(trigger_type.value) + if definition is None: + raise HTTPException( + status_code=422, detail=f"unknown trigger type {trigger_type.value!r}" + ) + try: + validated = definition.params_model.model_validate(raw) + except ValidationError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc + return validated.model_dump(mode="json") + + +def _initial_next_fire( + trigger_type: TriggerType, params: dict, enabled: bool +) -> datetime | None: + if trigger_type != TriggerType.SCHEDULE or not enabled: + return None + return compute_next_fire_at( + params["cron"], params["timezone"], after=datetime.now(UTC) + ) + + +def get_trigger_service( + session: AsyncSession = Depends(get_async_session), + user: User = Depends(current_active_user), +) -> TriggerService: + return TriggerService(session=session, user=user) diff --git a/surfsense_backend/app/automations/tasks/schedule_tick.py b/surfsense_backend/app/automations/tasks/schedule_tick.py index cade621c7..385bd7242 100644 --- a/surfsense_backend/app/automations/tasks/schedule_tick.py +++ b/surfsense_backend/app/automations/tasks/schedule_tick.py @@ -15,6 +15,7 @@ Runs every minute. Each tick performs two passes: from __future__ import annotations import logging +from dataclasses import dataclass from datetime import UTC, datetime from sqlalchemy import select @@ -39,6 +40,15 @@ TASK_NAME = "automation_schedule_tick" _TICK_BATCH = 200 +@dataclass(frozen=True, slots=True) +class _Claim: + """Per-trigger fire context captured before row state is mutated.""" + + trigger_id: int + scheduled_for: datetime + previous_last_fired_at: datetime | None + + @celery_app.task(name=TASK_NAME) def automation_schedule_tick() -> None: """Tick once: self-heal NULL next_fire_at, claim due rows, fire each.""" @@ -52,12 +62,12 @@ async def _tick() -> None: await _self_heal_null_next_fire(session, now=now) - claimed_ids = await _claim_due_triggers(session, now=now) - if not claimed_ids: + claims = await _claim_due_triggers(session, now=now) + if not claims: return - for trigger_id in claimed_ids: - await _fire_one(session, trigger_id=trigger_id) + for claim in claims: + await _fire_one(session, claim=claim, fired_at=now) async def _self_heal_null_next_fire(session: AsyncSession, *, now: datetime) -> None: @@ -95,8 +105,8 @@ async def _self_heal_null_next_fire(session: AsyncSession, *, now: datetime) -> async def _claim_due_triggers( session: AsyncSession, *, now: datetime -) -> list[int]: - """Lock and advance due rows; return claimed trigger ids.""" +) -> list[_Claim]: + """Lock and advance due rows; return per-trigger fire context.""" stmt = ( select(AutomationTrigger) .where( @@ -113,8 +123,12 @@ async def _claim_due_triggers( if not triggers: return [] - claimed: list[int] = [] + claims: list[_Claim] = [] for trigger in triggers: + # Snapshot fire-context BEFORE we advance the row. + scheduled_for = trigger.next_fire_at + previous_last_fired_at = trigger.last_fired_at + try: trigger.next_fire_at = compute_next_fire_at( trigger.params["cron"], @@ -131,29 +145,43 @@ async def _claim_due_triggers( continue trigger.last_fired_at = now - claimed.append(trigger.id) + claims.append( + _Claim( + trigger_id=trigger.id, + scheduled_for=scheduled_for, + previous_last_fired_at=previous_last_fired_at, + ) + ) await session.commit() - return claimed + return claims -async def _fire_one(session: AsyncSession, *, trigger_id: int) -> None: +async def _fire_one( + session: AsyncSession, *, claim: _Claim, fired_at: datetime +) -> None: """Reload the trigger post-commit and dispatch a run for it.""" - trigger = await session.get(AutomationTrigger, trigger_id) + trigger = await session.get(AutomationTrigger, claim.trigger_id) if trigger is None: return try: - run = await dispatch_schedule_run(session=session, trigger=trigger) + run = await dispatch_schedule_run( + session=session, + trigger=trigger, + fired_at=fired_at, + scheduled_for=claim.scheduled_for, + previous_last_fired_at=claim.previous_last_fired_at, + ) logger.info( "scheduled fire: trigger=%d automation=%d run=%d", - trigger_id, + claim.trigger_id, trigger.automation_id, run.id, ) except Exception: logger.exception( "scheduled fire failed for trigger %d (next attempt at next match)", - trigger_id, + claim.trigger_id, ) await session.rollback() diff --git a/surfsense_backend/app/automations/templating/context.py b/surfsense_backend/app/automations/templating/context.py index 3ca87694c..96fdb02e9 100644 --- a/surfsense_backend/app/automations/templating/context.py +++ b/surfsense_backend/app/automations/templating/context.py @@ -19,7 +19,7 @@ def build_run_context( trigger_type: str | None, started_at: datetime | None, attempt: int, - resolved_inputs: Mapping[str, Any], + inputs: Mapping[str, Any], step_outputs: Mapping[str, Any], ) -> dict[str, Any]: """Build the ``{run, inputs, steps}`` namespace exposed to every template.""" @@ -36,6 +36,6 @@ def build_run_context( "started_at": started_at, "attempt": attempt, }, - "inputs": dict(resolved_inputs), + "inputs": dict(inputs), "steps": dict(step_outputs), } diff --git a/surfsense_backend/app/automations/triggers/manual/definition.py b/surfsense_backend/app/automations/triggers/manual/definition.py index 9eb0282af..5a3529116 100644 --- a/surfsense_backend/app/automations/triggers/manual/definition.py +++ b/surfsense_backend/app/automations/triggers/manual/definition.py @@ -9,8 +9,7 @@ from .params import ManualTriggerParams MANUAL_TRIGGER = TriggerDefinition( type="manual", description="Fire on a user-initiated 'Run now' invocation.", - params_schema=ManualTriggerParams.model_json_schema(), - payload_schema={"type": "object"}, + params_model=ManualTriggerParams, ) register_trigger(MANUAL_TRIGGER) diff --git a/surfsense_backend/app/automations/triggers/manual/dispatch.py b/surfsense_backend/app/automations/triggers/manual/dispatch.py index 750c99937..6c92317d0 100644 --- a/surfsense_backend/app/automations/triggers/manual/dispatch.py +++ b/surfsense_backend/app/automations/triggers/manual/dispatch.py @@ -19,9 +19,14 @@ async def dispatch_manual_run( *, session: AsyncSession, automation_id: int, - payload: dict[str, Any] | None, + runtime_inputs: dict[str, Any] | None, ) -> AutomationRun: - """Find the automation + its enabled manual trigger, then run the generic dispatch.""" + """Find the automation + its enabled manual trigger, then run the generic dispatch. + + ``runtime_inputs`` is the caller-supplied payload (e.g. an HTTP body for a + "Run now" API call); it is merged with the trigger's ``static_inputs`` by + the generic dispatcher, with static winning on key collision. + """ automation = await _load_automation(session, automation_id) if automation is None: raise DispatchError(f"automation {automation_id} not found") @@ -41,7 +46,7 @@ async def dispatch_manual_run( session=session, automation=automation, trigger=trigger, - payload=payload, + runtime_inputs=runtime_inputs, ) diff --git a/surfsense_backend/app/automations/triggers/schedule/definition.py b/surfsense_backend/app/automations/triggers/schedule/definition.py index 3f86d767c..605765307 100644 --- a/surfsense_backend/app/automations/triggers/schedule/definition.py +++ b/surfsense_backend/app/automations/triggers/schedule/definition.py @@ -9,12 +9,7 @@ from .params import ScheduleTriggerParams SCHEDULE_TRIGGER = TriggerDefinition( type="schedule", description="Fire on a cron schedule in a given timezone.", - params_schema=ScheduleTriggerParams.model_json_schema(), - payload_schema={ - "type": "object", - "additionalProperties": False, - "properties": {}, - }, + params_model=ScheduleTriggerParams, ) register_trigger(SCHEDULE_TRIGGER) diff --git a/surfsense_backend/app/automations/triggers/schedule/dispatch.py b/surfsense_backend/app/automations/triggers/schedule/dispatch.py index fb4fcf686..6d3d5fcb9 100644 --- a/surfsense_backend/app/automations/triggers/schedule/dispatch.py +++ b/surfsense_backend/app/automations/triggers/schedule/dispatch.py @@ -2,6 +2,8 @@ from __future__ import annotations +from datetime import datetime + from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession @@ -16,9 +18,18 @@ async def dispatch_schedule_run( *, session: AsyncSession, trigger: AutomationTrigger, + fired_at: datetime, + scheduled_for: datetime, + previous_last_fired_at: datetime | None, ) -> AutomationRun: """Fire one scheduled run for ``trigger``. + Emits calendar context as runtime inputs: + + - ``fired_at`` — actual fire time + - ``scheduled_for`` — cron-derived target time for this fire + - ``last_fired_at`` — fire time of the previous run, or null on first fire + The caller (the schedule tick) is responsible for selecting due triggers and advancing ``next_fire_at`` / ``last_fired_at`` before invoking this. """ @@ -33,11 +44,19 @@ async def dispatch_schedule_run( f"automation {trigger.automation_id} is {automation.status.value}, not active" ) + runtime_inputs = { + "fired_at": fired_at.isoformat(), + "scheduled_for": scheduled_for.isoformat(), + "last_fired_at": ( + previous_last_fired_at.isoformat() if previous_last_fired_at else None + ), + } + return await dispatch_run( session=session, automation=automation, trigger=trigger, - payload=None, + runtime_inputs=runtime_inputs, ) diff --git a/surfsense_backend/app/automations/triggers/types.py b/surfsense_backend/app/automations/triggers/types.py index 783bd7842..aa2808e4d 100644 --- a/surfsense_backend/app/automations/triggers/types.py +++ b/surfsense_backend/app/automations/triggers/types.py @@ -5,10 +5,16 @@ from __future__ import annotations from dataclasses import dataclass from typing import Any +from pydantic import BaseModel + @dataclass(frozen=True, slots=True) class TriggerDefinition: type: str description: str - params_schema: dict[str, Any] - payload_schema: dict[str, Any] + params_model: type[BaseModel] + + @property + def params_schema(self) -> dict[str, Any]: + """JSON Schema (draft 2020-12) derived from ``params_model``.""" + return self.params_model.model_json_schema() From 8fb65d7188c05d8193a211af4cfbf96d6b0327c4 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 21:53:07 +0200 Subject: [PATCH 127/198] fix(automations): use enum values not names for postgres enum columns --- .../app/automations/persistence/models/automation.py | 6 +++++- surfsense_backend/app/automations/persistence/models/run.py | 6 +++++- .../app/automations/persistence/models/trigger.py | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/surfsense_backend/app/automations/persistence/models/automation.py b/surfsense_backend/app/automations/persistence/models/automation.py index ee86851c1..cb0b2ed31 100644 --- a/surfsense_backend/app/automations/persistence/models/automation.py +++ b/surfsense_backend/app/automations/persistence/models/automation.py @@ -42,7 +42,11 @@ class Automation(BaseModel, TimestampMixin): description = Column(Text, nullable=True) status = Column( - SQLAlchemyEnum(AutomationStatus, name="automation_status"), + SQLAlchemyEnum( + AutomationStatus, + name="automation_status", + values_callable=lambda x: [e.value for e in x], + ), nullable=False, default=AutomationStatus.ACTIVE, server_default=AutomationStatus.ACTIVE.value, diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index 81b33c37c..262e4c2bf 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -35,7 +35,11 @@ class AutomationRun(BaseModel, TimestampMixin): ) status = Column( - SQLAlchemyEnum(RunStatus, name="automation_run_status"), + SQLAlchemyEnum( + RunStatus, + name="automation_run_status", + values_callable=lambda x: [e.value for e in x], + ), nullable=False, default=RunStatus.PENDING, server_default=RunStatus.PENDING.value, diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py index 72d1d8d07..f73a8f350 100644 --- a/surfsense_backend/app/automations/persistence/models/trigger.py +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -29,7 +29,11 @@ class AutomationTrigger(BaseModel, TimestampMixin): ) type = Column( - SQLAlchemyEnum(TriggerType, name="automation_trigger_type"), + SQLAlchemyEnum( + TriggerType, + name="automation_trigger_type", + values_callable=lambda x: [e.value for e in x], + ), nullable=False, index=True, ) From c0232fdcfe346f4521c0b5aa578b3ac178bcdc6f Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 27 May 2026 22:29:51 +0200 Subject: [PATCH 128/198] refactor(automations): park manual trigger pending Run-now redesign Manual-as-a-standalone-trigger conflates "user clicks Run now" with the trigger model and forces ad-hoc input plumbing on the caller. Remove the unreachable surface so the tree reflects reality (schedule is the only v1 trigger). - Unregister `manual`: drop import from triggers/__init__.py - Delete `app/automations/triggers/manual/` - Drop `RunService.dispatch_manual` (RunService is now read-only) - Drop `POST /automations/{id}/run` and `RunDispatched` schema - Keep `TriggerType.MANUAL` Python + PG enum value (reserved, documented) to avoid an Alembic round-trip when Run-now is redesigned --- surfsense_backend/app/automations/api/run.py | 33 +-------- .../persistence/enums/trigger_type.py | 7 +- .../automations/persistence/models/trigger.py | 2 +- .../app/automations/schemas/api/__init__.py | 3 +- .../app/automations/schemas/api/run.py | 9 +-- .../app/automations/services/run.py | 25 +------ .../app/automations/services/trigger.py | 2 +- .../app/automations/triggers/__init__.py | 4 +- .../automations/triggers/manual/__init__.py | 11 --- .../automations/triggers/manual/definition.py | 15 ---- .../automations/triggers/manual/dispatch.py | 72 ------------------- .../app/automations/triggers/manual/params.py | 9 --- surfsense_backend/app/routes/__init__.py | 2 +- 13 files changed, 18 insertions(+), 176 deletions(-) delete mode 100644 surfsense_backend/app/automations/triggers/manual/__init__.py delete mode 100644 surfsense_backend/app/automations/triggers/manual/definition.py delete mode 100644 surfsense_backend/app/automations/triggers/manual/dispatch.py delete mode 100644 surfsense_backend/app/automations/triggers/manual/params.py diff --git a/surfsense_backend/app/automations/api/run.py b/surfsense_backend/app/automations/api/run.py index d0d4bbfb7..b662a5943 100644 --- a/surfsense_backend/app/automations/api/run.py +++ b/surfsense_backend/app/automations/api/run.py @@ -1,42 +1,15 @@ -"""HTTP routes for automation runs (dispatch + history).""" +"""HTTP routes for automation run history.""" from __future__ import annotations -from typing import Any +from fastapi import APIRouter, Depends, Query -from fastapi import APIRouter, Body, Depends, Query, status - -from app.automations.schemas.api import ( - RunDetail, - RunDispatched, - RunList, - RunSummary, -) +from app.automations.schemas.api import RunDetail, RunList, RunSummary from app.automations.services import RunService, get_run_service router = APIRouter() -@router.post( - "/automations/{automation_id}/run", - response_model=RunDispatched, - status_code=status.HTTP_202_ACCEPTED, -) -async def run_automation_now( - automation_id: int, - inputs: dict[str, Any] | None = Body(default=None), - service: RunService = Depends(get_run_service), -) -> RunDispatched: - """Fire a manual run. - - ``inputs`` is the runtime payload supplied by the caller; it is merged with - the manual trigger's ``static_inputs`` (static wins) and validated against - the automation's input schema. - """ - run = await service.dispatch_manual(automation_id=automation_id, runtime_inputs=inputs) - return RunDispatched(run_id=run.id, status=run.status) - - @router.get( "/automations/{automation_id}/runs", response_model=RunList, diff --git a/surfsense_backend/app/automations/persistence/enums/trigger_type.py b/surfsense_backend/app/automations/persistence/enums/trigger_type.py index 8318bfdee..a583b1bd6 100644 --- a/surfsense_backend/app/automations/persistence/enums/trigger_type.py +++ b/surfsense_backend/app/automations/persistence/enums/trigger_type.py @@ -1,4 +1,9 @@ -"""Trigger-kind discriminator. v1: schedule | manual; webhook/event in Phase 2/3.""" +"""Trigger-kind discriminator. + +v1 only registers ``schedule``. ``manual`` is reserved in the enum (mirrors the +postgres enum) but is intentionally unregistered pending a redesign of the +"Run now" UX. +""" from __future__ import annotations diff --git a/surfsense_backend/app/automations/persistence/models/trigger.py b/surfsense_backend/app/automations/persistence/models/trigger.py index f73a8f350..de1078acf 100644 --- a/surfsense_backend/app/automations/persistence/models/trigger.py +++ b/surfsense_backend/app/automations/persistence/models/trigger.py @@ -56,7 +56,7 @@ class AutomationTrigger(BaseModel, TimestampMixin): # Precomputed next fire moment in UTC; advanced after each fire by the # schedule tick. NULL means the trigger has never been scheduled (the - # tick self-heals on first sight). Manual triggers leave this NULL. + # tick self-heals on first sight). next_fire_at = Column(TIMESTAMP(timezone=True), nullable=True) automation = relationship("Automation", back_populates="triggers") diff --git a/surfsense_backend/app/automations/schemas/api/__init__.py b/surfsense_backend/app/automations/schemas/api/__init__.py index a8a010a2c..f49e5c589 100644 --- a/surfsense_backend/app/automations/schemas/api/__init__.py +++ b/surfsense_backend/app/automations/schemas/api/__init__.py @@ -9,7 +9,7 @@ from .automation import ( AutomationSummary, AutomationUpdate, ) -from .run import RunDetail, RunDispatched, RunList, RunSummary +from .run import RunDetail, RunList, RunSummary from .trigger import TriggerCreate, TriggerDetail, TriggerUpdate __all__ = [ @@ -19,7 +19,6 @@ __all__ = [ "AutomationSummary", "AutomationUpdate", "RunDetail", - "RunDispatched", "RunList", "RunSummary", "TriggerCreate", diff --git a/surfsense_backend/app/automations/schemas/api/run.py b/surfsense_backend/app/automations/schemas/api/run.py index 42ea7ac14..3f6eaab82 100644 --- a/surfsense_backend/app/automations/schemas/api/run.py +++ b/surfsense_backend/app/automations/schemas/api/run.py @@ -1,4 +1,4 @@ -"""Response schemas for run sub-resources and run dispatch.""" +"""Response schemas for run sub-resources.""" from __future__ import annotations @@ -40,10 +40,3 @@ class RunList(BaseModel): items: list[RunSummary] total: int - - -class RunDispatched(BaseModel): - """Response of a successful run dispatch.""" - - run_id: int - status: RunStatus diff --git a/surfsense_backend/app/automations/services/run.py b/surfsense_backend/app/automations/services/run.py index 92d79e9bc..ac9970241 100644 --- a/surfsense_backend/app/automations/services/run.py +++ b/surfsense_backend/app/automations/services/run.py @@ -1,46 +1,25 @@ -"""``RunService`` — dispatch and history of automation runs.""" +"""``RunService`` — read-only access to automation run history.""" from __future__ import annotations -from typing import Any - from fastapi import Depends, HTTPException from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession -from app.automations.dispatch import DispatchError from app.automations.persistence.models.automation import Automation from app.automations.persistence.models.run import AutomationRun -from app.automations.triggers.manual import dispatch_manual_run from app.db import Permission, User, get_async_session from app.users import current_active_user from app.utils.rbac import check_permission class RunService: - """Lifecycle of the ``AutomationRun`` resource.""" + """Read-only access to ``AutomationRun`` history.""" def __init__(self, *, session: AsyncSession, user: User) -> None: self.session = session self.user = user - async def dispatch_manual( - self, - *, - automation_id: int, - runtime_inputs: dict[str, Any] | None, - ) -> AutomationRun: - """Fire a manual run via the registered manual trigger.""" - await self._authorize(automation_id, Permission.AUTOMATIONS_EXECUTE.value) - try: - return await dispatch_manual_run( - session=self.session, - automation_id=automation_id, - runtime_inputs=runtime_inputs, - ) - except DispatchError as exc: - raise HTTPException(status_code=422, detail=str(exc)) from exc - async def list( self, *, diff --git a/surfsense_backend/app/automations/services/trigger.py b/surfsense_backend/app/automations/services/trigger.py index 33e9c1386..c76cc0740 100644 --- a/surfsense_backend/app/automations/services/trigger.py +++ b/surfsense_backend/app/automations/services/trigger.py @@ -69,7 +69,7 @@ class TriggerService: trigger.enabled = data["enabled"] # Recompute next_fire_at when schedule timing changed or the trigger was - # toggled back on. Manual triggers always have NULL next_fire_at. + # toggled back on. if trigger.type == TriggerType.SCHEDULE: trigger.next_fire_at = _initial_next_fire( trigger.type, trigger.params, trigger.enabled diff --git a/surfsense_backend/app/automations/triggers/__init__.py b/surfsense_backend/app/automations/triggers/__init__.py index 258b2fda9..d7abb6b5d 100644 --- a/surfsense_backend/app/automations/triggers/__init__.py +++ b/surfsense_backend/app/automations/triggers/__init__.py @@ -1,6 +1,6 @@ """Triggers domain: registry surface + built-in trigger packages. -Each trigger lives in its own subpackage (``manual/``, ``schedule/``, ...) and +Each trigger lives in its own subpackage (``schedule/``, ...) and self-registers at import time via its ``definition`` module. """ @@ -17,4 +17,4 @@ __all__ = [ ] # Built-in triggers self-register at import time. -from . import manual, schedule # noqa: E402, F401 +from . import schedule # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/triggers/manual/__init__.py b/surfsense_backend/app/automations/triggers/manual/__init__.py deleted file mode 100644 index 65cca9270..000000000 --- a/surfsense_backend/app/automations/triggers/manual/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -"""``manual`` trigger: fired by a user clicking ``Run now``.""" - -from __future__ import annotations - -from .dispatch import dispatch_manual_run -from .params import ManualTriggerParams - -__all__ = ["ManualTriggerParams", "dispatch_manual_run"] - -# Side-effect: register on the triggers store. -from . import definition # noqa: E402, F401 diff --git a/surfsense_backend/app/automations/triggers/manual/definition.py b/surfsense_backend/app/automations/triggers/manual/definition.py deleted file mode 100644 index 5a3529116..000000000 --- a/surfsense_backend/app/automations/triggers/manual/definition.py +++ /dev/null @@ -1,15 +0,0 @@ -"""``manual`` ``TriggerDefinition`` registration.""" - -from __future__ import annotations - -from ..store import register_trigger -from ..types import TriggerDefinition -from .params import ManualTriggerParams - -MANUAL_TRIGGER = TriggerDefinition( - type="manual", - description="Fire on a user-initiated 'Run now' invocation.", - params_model=ManualTriggerParams, -) - -register_trigger(MANUAL_TRIGGER) diff --git a/surfsense_backend/app/automations/triggers/manual/dispatch.py b/surfsense_backend/app/automations/triggers/manual/dispatch.py deleted file mode 100644 index 6c92317d0..000000000 --- a/surfsense_backend/app/automations/triggers/manual/dispatch.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Manual ``Run now`` dispatch adapter: load + guard, then call generic dispatch.""" - -from __future__ import annotations - -from typing import Any - -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession - -from app.automations.dispatch import DispatchError, dispatch_run -from app.automations.persistence.enums.automation_status import AutomationStatus -from app.automations.persistence.enums.trigger_type import TriggerType -from app.automations.persistence.models.automation import Automation -from app.automations.persistence.models.run import AutomationRun -from app.automations.persistence.models.trigger import AutomationTrigger - - -async def dispatch_manual_run( - *, - session: AsyncSession, - automation_id: int, - runtime_inputs: dict[str, Any] | None, -) -> AutomationRun: - """Find the automation + its enabled manual trigger, then run the generic dispatch. - - ``runtime_inputs`` is the caller-supplied payload (e.g. an HTTP body for a - "Run now" API call); it is merged with the trigger's ``static_inputs`` by - the generic dispatcher, with static winning on key collision. - """ - automation = await _load_automation(session, automation_id) - if automation is None: - raise DispatchError(f"automation {automation_id} not found") - - if automation.status != AutomationStatus.ACTIVE: - raise DispatchError( - f"automation {automation_id} is {automation.status.value}, not active" - ) - - trigger = await _find_manual_trigger(session, automation_id) - if trigger is None: - raise DispatchError( - f"automation {automation_id} has no enabled manual trigger" - ) - - return await dispatch_run( - session=session, - automation=automation, - trigger=trigger, - runtime_inputs=runtime_inputs, - ) - - -async def _load_automation( - session: AsyncSession, automation_id: int -) -> Automation | None: - stmt = select(Automation).where(Automation.id == automation_id) - return (await session.execute(stmt)).scalar_one_or_none() - - -async def _find_manual_trigger( - session: AsyncSession, automation_id: int -) -> AutomationTrigger | None: - stmt = ( - select(AutomationTrigger) - .where( - AutomationTrigger.automation_id == automation_id, - AutomationTrigger.type == TriggerType.MANUAL, - AutomationTrigger.enabled.is_(True), - ) - .limit(1) - ) - return (await session.execute(stmt)).scalar_one_or_none() diff --git a/surfsense_backend/app/automations/triggers/manual/params.py b/surfsense_backend/app/automations/triggers/manual/params.py deleted file mode 100644 index 577655086..000000000 --- a/surfsense_backend/app/automations/triggers/manual/params.py +++ /dev/null @@ -1,9 +0,0 @@ -"""``ManualTriggerParams`` — params for the ``manual`` trigger (empty in v1).""" - -from __future__ import annotations - -from pydantic import BaseModel, ConfigDict - - -class ManualTriggerParams(BaseModel): - model_config = ConfigDict(extra="forbid") diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index 64c8c6585..ef1c9312a 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -120,4 +120,4 @@ router.include_router(youtube_router) # YouTube playlist resolution router.include_router(prompts_router) router.include_router(memory_router) # User personal memory (memory.md style) router.include_router(team_memory_router) # Search-space team memory -router.include_router(automations_router) # Automations (manual run-now) +router.include_router(automations_router) # Automations CRUD + run history From 9d6e9b7e2d4edf97cd68e3f7fbbd535ba398c378 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Wed, 27 May 2026 14:58:10 -0700 Subject: [PATCH 129/198] feat: enhance task management and timeout configurations in multi-agent chat - Added new environment variables for controlling task execution limits, including `SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS`, `SURFSENSE_TASK_BATCH_CONCURRENCY`, and `SURFSENSE_TASK_BATCH_MAX_SIZE`. - Updated documentation to reflect new batch processing capabilities for `task` calls, allowing for concurrent execution of multiple subagent tasks. - Improved error handling and receipt generation for deliverables, ensuring consistent feedback on task status. - Refactored middleware to incorporate search space ID for better task management. --- surfsense_backend/.env.example | 47 ++ .../system_prompt/prompts/routing.md | 70 ++ .../prompts/tools/task/description.md | 59 +- .../constants.py | 71 ++ .../middleware.py | 21 +- .../spawn_paused.py | 84 +++ .../task_tool.py | 674 +++++++++++++++++- .../shared/kb_context_projection.py | 4 +- .../shared/permissions/ask/request.py | 3 +- .../multi_agent_chat/middleware/stack.py | 1 + .../builtins/deliverables/system_prompt.md | 12 +- .../deliverables/tools/generate_image.py | 78 +- .../builtins/deliverables/tools/podcast.py | 111 ++- .../builtins/deliverables/tools/report.py | 100 ++- .../builtins/deliverables/tools/resume.py | 202 ++++-- .../deliverables/tools/video_presentation.py | 114 ++- .../knowledge_base/system_prompt_cloud.md | 9 +- .../knowledge_base/system_prompt_desktop.md | 9 +- .../builtins/memory/system_prompt.md | 8 +- .../builtins/research/system_prompt.md | 6 +- .../connectors/airtable/system_prompt.md | 8 +- .../connectors/calendar/system_prompt.md | 9 +- .../connectors/clickup/system_prompt.md | 8 +- .../connectors/confluence/system_prompt.md | 7 +- .../connectors/discord/system_prompt.md | 7 +- .../connectors/dropbox/system_prompt.md | 7 +- .../connectors/gmail/system_prompt.md | 9 +- .../connectors/gmail/tools/send_email.py | 148 ++-- .../connectors/google_drive/system_prompt.md | 7 +- .../connectors/jira/system_prompt.md | 8 +- .../connectors/linear/system_prompt.md | 8 +- .../connectors/luma/system_prompt.md | 7 +- .../connectors/notion/system_prompt.md | 7 +- .../connectors/notion/tools/delete_page.py | 140 +++- .../connectors/onedrive/system_prompt.md | 7 +- .../connectors/slack/system_prompt.md | 8 +- .../connectors/teams/system_prompt.md | 7 +- .../subagents/shared/md_file_reader.py | 13 + .../subagents/shared/snippets/__init__.py | 6 + .../shared/snippets/output_contract_base.md | 6 + .../shared/snippets/verifiable_handle.md | 10 + .../multi_agent_chat/subagents/shared/spec.py | 32 +- .../subagents/shared/subagent_builder.py | 56 +- .../app/agents/new_chat/filesystem_state.py | 31 + .../agents/new_chat/middleware/compaction.py | 3 +- .../agents/new_chat/middleware/doom_loop.py | 3 +- .../new_chat/middleware/kb_persistence.py | 76 ++ .../agents/new_chat/middleware/permission.py | 3 +- .../app/agents/new_chat/state_reducers.py | 34 + .../app/agents/new_chat/tools/podcast.py | 57 +- .../new_chat/tools/video_presentation.py | 58 +- .../app/agents/shared/__init__.py | 9 + .../app/agents/shared/deliverable_wait.py | 123 ++++ .../app/agents/shared/receipt.py | 161 +++++ .../app/agents/shared/receipt_command.py | 71 ++ .../app/etl_pipeline/etl_pipeline_service.py | 4 +- .../app/services/composio_service.py | 9 +- .../app/services/gmail/kb_sync_service.py | 4 +- .../google_calendar/kb_sync_service.py | 8 +- .../app/services/jira/kb_sync_service.py | 8 +- surfsense_backend/app/services/llm_service.py | 6 +- .../app/services/onedrive/kb_sync_service.py | 4 +- .../app/tasks/chat/stream_new_chat.py | 4 +- .../tasks/chat/streaming/handlers/tool_end.py | 37 +- .../generate_video_presentation/emission.py | 16 +- .../app/utils/document_converters.py | 4 +- 66 files changed, 2561 insertions(+), 380 deletions(-) create mode 100644 surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/spawn_paused.py create mode 100644 surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/__init__.py create mode 100644 surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/output_contract_base.md create mode 100644 surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/verifiable_handle.md create mode 100644 surfsense_backend/app/agents/shared/__init__.py create mode 100644 surfsense_backend/app/agents/shared/deliverable_wait.py create mode 100644 surfsense_backend/app/agents/shared/receipt.py create mode 100644 surfsense_backend/app/agents/shared/receipt_command.py diff --git a/surfsense_backend/.env.example b/surfsense_backend/.env.example index b05369412..70cf687d8 100644 --- a/surfsense_backend/.env.example +++ b/surfsense_backend/.env.example @@ -357,3 +357,50 @@ LANGSMITH_PROJECT=surfsense # updates and deletes — the TTL only bounds staleness for bulk-import # paths that bypass the ORM. Set to 0 to disable the cache. # SURFSENSE_CONNECTOR_DISCOVERY_TTL_SECONDS=30 + +# ----------------------------------------------------------------------------- +# `task` boundary controls (Hermes-inspired improvements) +# ----------------------------------------------------------------------------- +# Wall-clock budget for a single ``task(subagent, ...)`` invocation in +# seconds. Subagents that run hot (slow image vendors, sluggish embedders, +# wedged MCP servers) would otherwise pin the orchestrator until the next +# checkpoint heartbeat fires. On timeout the runtime cancels the underlying +# coroutine and synthesizes a ToolMessage telling the orchestrator to treat +# the result as ``status=error``. Set to 0 to disable the cap entirely. +# Default: 300.0 +# SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS=300 + +# Batch-mode (``task(tasks=[...])``) concurrency cap and max batch size. +# Concurrency is enforced via an ``asyncio.Semaphore`` so a runaway fanout +# cannot starve unrelated subagents (each child still owns an LLM call and +# its own DB session). Max-size is a hard safety net for prompt-injection / +# runaway loops; the orchestrator rarely needs more than a handful of +# concurrent specialists. Set concurrency to 1 to effectively serialise +# batches without changing the schema. +# SURFSENSE_TASK_BATCH_CONCURRENCY=3 +# SURFSENSE_TASK_BATCH_MAX_SIZE=8 + +# Soft per-turn cap on cumulative ``task(...)`` invocations across all +# subagents. Once the sum of ``state['billable_calls']`` crosses this +# number, the runtime appends a one-shot warning ToolMessage telling the +# orchestrator to wrap up rather than launching more specialists. Tunable +# so heavy-research turns (15+ legitimate specialist calls) don't trip the +# alarm in production. Set to 0 to disable the warning entirely. +# SURFSENSE_SUBAGENT_BILLABLE_THRESHOLD=15 + +# Per-workspace spawn-paused kill switch — set via Redis at runtime, not +# this env var. The env var below only disables the check itself (useful +# for local dev without Redis). To pause a workspace in production: +# redis-cli SET surfsense:spawn_paused: 1 EX 600 +# redis-cli DEL surfsense:spawn_paused: +# The check is fail-open: a Redis blip never blocks ``task(...)``. +# SURFSENSE_TASK_SPAWN_PAUSED_DISABLED=false + +# Note on Celery-backed deliverables (generate_podcast, +# generate_video_presentation): these tools poll the artefact row until +# it reaches a terminal status — they do NOT use an internal wall-clock +# budget. The effective ceiling is SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS +# (above, default 300s) in multi-agent mode and the chat's HTTP / process +# lifetime in single-agent mode. If your podcasts or videos routinely +# exceed 5 minutes, raise SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS (or +# set it to 0 to disable that ceiling entirely). diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md index 4e27381d3..1038dde3d 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md @@ -33,6 +33,15 @@ Rules for `task`: - Neither's prompt references the other's output, and - They target different specialists, OR the same specialist with non-overlapping scopes (e.g. reading two unrelated paths). +- **Batch shape for many-shot fanout.** When a single user request expands + to **3 or more independent specialist calls** (e.g. "create five issues + from this list"), prefer the batch shape: + `task(tasks=[{description, subagent_type}, ...])`. The runtime fans them + out concurrently under a small semaphore and aggregates one ToolMessage + per child prefixed with `[task ]`. Batched children **do not + support human-in-the-loop interrupts** — if one needs approval it surfaces + an error and you re-dispatch it as a single (non-batched) `task(...)` call. + For 1–2 independent calls, just emit two separate `task(...)` calls. - **Serialise dependent work across turns.** If one specialist's output must inform another's input (e.g. "find the roadmap in my KB, then email it to Maya"), invoke them on consecutive turns — first finishes, @@ -93,4 +102,65 @@ user: "Find my Q2 roadmap doc in the KB and email a summary to Maya." task(gmail, "Send an email to Maya with subject 'Q2 roadmap summary' and the following body: .") + + +user: "Create issues in Linear for each of these five bugs: " +→ Many-shot independent fanout — use the batch shape: + task(tasks=[ + {subagent_type: "linear", description: "Create a Linear issue titled + '' with body ''. Return the issue URL."}, + {subagent_type: "linear", description: "Create a Linear issue titled + '' with body ''. Return the issue URL."}, + {subagent_type: "linear", description: "Create a Linear issue titled + '' with body ''. Return the issue URL."}, + {subagent_type: "linear", description: "Create a Linear issue titled + '' with body ''. Return the issue URL."}, + {subagent_type: "linear", description: "Create a Linear issue titled + '' with body ''. Return the issue URL."}, + ]) + Read back the `[task 0]`…`[task 4]` blocks in the combined ToolMessage and + verify each via its Receipt's `verifiable_url` per the `` + teaching before confirming to the user. + + + +user: "Make a 30-second podcast of this conversation." +→ Celery-backed deliverable. The `deliverables` subagent dispatches the + Celery job and then **waits for it to finish** before returning. The + call may take 10-60 seconds (or longer for video presentations) — + that is intentional, not a hang. You always get back one of two + Receipt shapes: + task(deliverables, "Generate a podcast titled '' from the + following content. Use a 30-second style brief. Return the podcast + id and title.\n\n<source content>") + Outcomes: + - **`status="success"`**: the audio is saved. Tell the user the + podcast is **ready** and quote the `external_id` / `preview` so + they can find it in the podcast panel. + - **`status="failed"`**: surface the Receipt's `error` field + verbatim. Do NOT silently re-dispatch — the backend already tried + and reported a real error. + Same two-way pattern applies to video presentations (which take + longer to render, but still return a terminal status). If a + `task(deliverables, ...)` invocation itself times out at the subagent + layer (separate from the Receipt), that's an operator-side problem + with the subagent invoke timeout, not a deliverable failure — pass + the message through and stop. +</example> + +<example> +user: "Post the launch announcement to #general and let me know when it's up." +→ Mutating subagent + user wants external confirmation. Apply the + `<verification>` teaching: the slack subagent's reply is a self-report; + check its `evidence.receipts` for a Receipt with `status="success"` and + a `verifiable_url`, then fetch that URL to confirm before reporting back. + This turn: + task(slack, "Post '<launch announcement text>' to #general. + Return the message permalink.") + Next turn (with the receipt's `verifiable_url` in hand): + scrape_webpage(url=<verifiable_url from slack receipt>) + → confirm the post is live, then tell the user it's up with the URL. + If the slack reply has NO Receipt with `status="success"`, treat it as a + silent failure: surface the error verbatim, do not retry. +</example> </routing> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/task/description.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/task/description.md index 2f47d4df1..d6a81d8d3 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/task/description.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/task/description.md @@ -4,12 +4,69 @@ `<specialists>` for the live roster. - Each subagent runs in isolation with its own tool stack and context, and returns a single synthesized result. - - Args: + - Args (single mode): - `subagent_type` — name of the specialist to invoke (must match an entry in `<specialists>`). - `description` — the FULL task prompt. The specialist cannot see this thread, so include all context and constraints, plus what you need back. The specialist will respond in its own format — don't dictate one. + - Args (batch mode): + - `tasks` — array of `{description, subagent_type}` objects to fan out + concurrently. Mutually exclusive with single-mode args. Use when a + single request expands to **3 or more independent specialist calls** + (e.g. "create five issues from this list"). Children run under a + small concurrency cap and the runtime returns one ToolMessage block + per child, prefixed with `[task <index>]`. **Batched children do not + support human-in-the-loop interrupts** — if any child needs approval + it surfaces an error and you must re-dispatch that single task as a + non-batched `task(...)` call. - Routing rules (when to call, how often, how to scope) live in `<routing>`. + <verification> + A subagent's natural-language reply is a **self-report**, not proof. The + specialist might claim a Slack message was posted, a Jira issue was + created, or a report was generated even when the underlying tool call + failed silently or was rate-limited. Treat success language ("Done", + "Posted to #general", "Created ENG-42") as a hypothesis, not a fact. + + Two ground-truth signals are always available to verify a mutating + subagent's claim: + + 1. **`state['receipts']`** — every mutating tool emits a structured + `Receipt` (route, type, operation, status, external_id, + verifiable_url, preview) into this append-only list. The supervisor + never sees the raw list directly, but each subagent's + `<output_contract>` carries the matching Receipt(s) under + `evidence.receipts`. If a subagent reports success with NO matching + Receipt at `status="success"` (or `"pending"` for async deliverables + like podcasts/videos), the operation did not happen — treat as + failure and surface that to the user verbatim, do not retry blindly. + + 2. **`scrape_webpage`** — when a Receipt carries a `verifiable_url` + (Notion page URL, Slack permalink, Jira issue URL, Linear identifier + URL, etc.), you can fetch that URL and confirm the operation + externally. Use this for high-stakes mutations the user explicitly + called out (e.g. "send the launch email to the whole team") or when + the subagent's self-report contradicts what the user expected. + + **Receipt status semantics — read carefully:** + + - `status="success"`: the mutation already committed in the backend. + If a `verifiable_url` is present and the request was high-stakes, + you may `scrape_webpage` it to externally confirm. Otherwise trust + the Receipt and tell the user it is done. Celery-backed deliverables + (podcasts, video presentations) also land here — the subagent + already waited for the worker to finish, so a `success` Receipt + means the artefact really is saved. + - `status="failed"`: a Receipt with this status carries the backend's + error in its `error` field. Surface that text verbatim to the user; + re-routing or retrying is only appropriate when the user explicitly + asks for it. + - `status="pending"`: rare today — current mutating tools wait for + their backend before returning. If you ever do see a pending + Receipt, tell the user the work has been **kicked off** (quote the + `external_id` / `preview` so they can find it later), do not + `scrape_webpage` it, and do not re-dispatch the same + `task(...)` call hoping it will be done "this time". + </verification> diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/constants.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/constants.py index 6c4519f3a..e11f3c3ec 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/constants.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/constants.py @@ -2,6 +2,8 @@ from __future__ import annotations +import os + # Mirror of deepagents.middleware.subagents._EXCLUDED_STATE_KEYS. EXCLUDED_STATE_KEYS = frozenset( { @@ -16,3 +18,72 @@ EXCLUDED_STATE_KEYS = frozenset( # Match the parent graph's budget; the LangGraph default of 25 trips on # multi-step subagent runs. DEFAULT_SUBAGENT_RECURSION_LIMIT = 10_000 + + +def _read_timeout_env(name: str, default: float) -> float: + """Parse ``name`` from the environment; fall back to ``default`` on bad values. + + Kept as a free function so the module-level constants stay constants + after import; tests can monkeypatch this and re-evaluate via + ``importlib.reload`` if they need a different value mid-process. + """ + raw = os.environ.get(name) + if not raw: + return default + try: + value = float(raw) + except (TypeError, ValueError): + return default + return value if value > 0 else default + + +# Wall-clock budget for a single ``task(subagent, ...)`` invocation. +# Subagents that run hot (image generation with slow vendors, KB writes +# behind a sluggish embedder) can otherwise wedge the orchestrator until +# the next checkpoint heartbeat. ``0`` disables the timeout entirely. +DEFAULT_SUBAGENT_INVOKE_TIMEOUT_SECONDS: float = _read_timeout_env( + "SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS", + default=300.0, +) + + +def _read_int_env(name: str, default: int) -> int: + raw = os.environ.get(name) + if not raw: + return default + try: + value = int(raw) + except (TypeError, ValueError): + return default + return value if value > 0 else default + + +# Maximum number of children that ``task(..., tasks=[...])`` runs in +# parallel via ``asyncio.gather`` + ``Semaphore``. Bounded so a runaway +# fanout cannot starve unrelated subagents (each child still owns an +# LLM call + DB session). Set ``SURFSENSE_TASK_BATCH_CONCURRENCY=1`` to +# effectively serialise batches without changing the schema. +DEFAULT_SUBAGENT_BATCH_CONCURRENCY: int = _read_int_env( + "SURFSENSE_TASK_BATCH_CONCURRENCY", + default=3, +) + +# Max number of children in a single batched ``task`` call. Hard upper +# bound is a safety net for prompt-injection / runaway loops; the orchestrator +# rarely needs more than a handful of concurrent specialists. +MAX_SUBAGENT_BATCH_SIZE: int = _read_int_env( + "SURFSENSE_TASK_BATCH_MAX_SIZE", + default=8, +) + + +# Soft threshold for per-turn cumulative ``task(...)`` invocations across +# **all** subagents. Once the sum of ``state['billable_calls']`` values +# crosses this number, the runtime appends a one-shot warning ToolMessage +# instructing the orchestrator to wrap up the turn. Tunable so heavy-research +# turns (which legitimately need 15+ specialist calls) don't trip the alarm +# in production. Set to ``0`` to disable the warning entirely. +DEFAULT_SUBAGENT_BILLABLE_THRESHOLD: int = _read_int_env( + "SURFSENSE_SUBAGENT_BILLABLE_THRESHOLD", + default=15, +) diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/middleware.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/middleware.py index 0119752c1..6cc71f252 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/middleware.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/middleware.py @@ -16,6 +16,9 @@ from langchain.agents import create_agent from langchain.chat_models import init_chat_model from langgraph.types import Checkpointer +from app.agents.multi_agent_chat.subagents.shared.spec import ( + SURF_CONTEXT_HINT_PROVIDER_KEY, +) from app.utils.perf import get_perf_logger from .task_tool import build_task_tool_with_parent_config @@ -34,6 +37,7 @@ class SurfSenseCheckpointedSubAgentMiddleware(SubAgentMiddleware): subagents: list[SubAgent | CompiledSubAgent], system_prompt: str | None = TASK_SYSTEM_PROMPT, task_description: str | None = None, + search_space_id: int | None = None, ) -> None: self._surf_checkpointer = checkpointer super(SubAgentMiddleware, self).__init__() @@ -43,8 +47,17 @@ class SurfSenseCheckpointedSubAgentMiddleware(SubAgentMiddleware): ) self._backend = backend self._subagents = subagents + # Search-space id is captured at build time (the orchestrator runs in + # exactly one search space for its lifetime). The spawn-paused kill + # switch keys on it so an operator can quarantine one workspace + # without affecting the rest of the deployment. + self._search_space_id = search_space_id subagent_specs = self._surf_compile_subagent_graphs() - task_tool = build_task_tool_with_parent_config(subagent_specs, task_description) + task_tool = build_task_tool_with_parent_config( + subagent_specs, + task_description, + search_space_id=search_space_id, + ) if system_prompt and subagent_specs: agents_desc = "\n".join( f"- {s['name']}: {s['description']}" for s in subagent_specs @@ -64,6 +77,10 @@ class SurfSenseCheckpointedSubAgentMiddleware(SubAgentMiddleware): for spec in self._subagents: spec_start = time.perf_counter() + # Provider may be ``None`` (no hint), in which case task_tool + # skips the prepend step. We forward the key unconditionally so + # the registry shape is uniform. + hint_provider = cast(dict, spec).get(SURF_CONTEXT_HINT_PROVIDER_KEY) if "runnable" in spec: compiled = cast(CompiledSubAgent, spec) specs.append( @@ -71,6 +88,7 @@ class SurfSenseCheckpointedSubAgentMiddleware(SubAgentMiddleware): "name": compiled["name"], "description": compiled["description"], "runnable": compiled["runnable"], + SURF_CONTEXT_HINT_PROVIDER_KEY: hint_provider, } ) timings.append( @@ -108,6 +126,7 @@ class SurfSenseCheckpointedSubAgentMiddleware(SubAgentMiddleware): "name": spec["name"], "description": spec["description"], "runnable": runnable, + SURF_CONTEXT_HINT_PROVIDER_KEY: hint_provider, } ) timings.append( diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/spawn_paused.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/spawn_paused.py new file mode 100644 index 000000000..2c9e114e0 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/spawn_paused.py @@ -0,0 +1,84 @@ +"""Per-search-space spawn-paused kill switch for the ``task`` boundary. + +When operators see a runaway loop, a vendor outage, or a billing event +that requires immediate cessation of subagent traffic for a specific +workspace, they flip a Redis flag and the ``task`` tool short-circuits +without touching downstream services. The flag is **per-search-space** +so one tenant's incident never silences the rest of the deployment. + +Flag key: ``surfsense:spawn_paused:{search_space_id}`` +Flag value: any string-truthy value (we read presence, not contents). +TTL: set by whoever toggles the flag — this module never expires + keys on its own, since "the flag is on" is itself the signal + that a human (or alert) needs to investigate. + +The check is best-effort: Redis errors are logged but do not block the +``task`` invocation. Failing closed (block-on-redis-error) would let a +single Redis blip take the whole orchestrator offline; failing open +preserves availability and the alarm bells (rate-limits, cost spikes) +will surface the underlying outage. +""" + +from __future__ import annotations + +import contextlib +import logging +import os + +from app.config import config + +logger = logging.getLogger(__name__) + + +# Operators can disable the check entirely (e.g. local dev without Redis) +# by setting ``SURFSENSE_TASK_SPAWN_PAUSED_DISABLED=1``. Default is +# enabled so production never relies on flipping an opt-out flag. +_DISABLED = os.environ.get( + "SURFSENSE_TASK_SPAWN_PAUSED_DISABLED", "" +).strip().lower() in { + "1", + "true", + "yes", + "on", +} + + +def _flag_key(search_space_id: int) -> str: + return f"surfsense:spawn_paused:{search_space_id}" + + +async def is_spawn_paused(search_space_id: int | None) -> bool: + """Return ``True`` iff the workspace's spawn-paused flag is set in Redis. + + A ``None`` search-space (e.g. dev paths that did not plumb the id + through yet) bypasses the check. So does a Redis outage — see module + docstring for the fail-open rationale. + """ + if _DISABLED or search_space_id is None: + return False + try: + # Local import keeps the cold-path import cheap and lets routes + # that never call ``task`` skip the redis dependency entirely. + import redis.asyncio as aioredis # type: ignore[import-not-found] + + client = aioredis.from_url(config.REDIS_APP_URL, decode_responses=True) + try: + raw = await client.get(_flag_key(search_space_id)) + finally: + # ``aclose()`` is the async-safe variant on redis-py >=5; fall back + # to ``close()`` for older clients pinned in tests. + close = getattr(client, "aclose", None) or getattr(client, "close", None) + if callable(close): + with contextlib.suppress(Exception): + await close() # type: ignore[misc] + return bool(raw) + except Exception: + logger.warning( + "spawn_paused check failed for search_space_id=%s; failing open.", + search_space_id, + exc_info=True, + ) + return False + + +__all__ = ["is_spawn_paused"] diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py index c3babab83..91a0be506 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py @@ -8,9 +8,12 @@ re-raises any new pending interrupt back to the parent. from __future__ import annotations +import asyncio +import json import logging import time -from typing import Annotated, Any, NoReturn +from collections.abc import Awaitable +from typing import Annotated, Any, NoReturn, TypeVar from deepagents.middleware.subagents import TASK_TOOL_DESCRIPTION from langchain.tools import BaseTool, ToolRuntime @@ -20,6 +23,10 @@ from langchain_core.tools import StructuredTool from langgraph.errors import GraphInterrupt from langgraph.types import Command, Interrupt +from app.agents.multi_agent_chat.subagents.shared.spec import ( + SURF_CONTEXT_HINT_PROVIDER_KEY, + ContextHintProvider, +) from app.observability import metrics as ot_metrics, otel as ot from app.utils.perf import get_perf_logger @@ -29,7 +36,13 @@ from .config import ( has_surfsense_resume, subagent_invoke_config, ) -from .constants import EXCLUDED_STATE_KEYS +from .constants import ( + DEFAULT_SUBAGENT_BATCH_CONCURRENCY, + DEFAULT_SUBAGENT_BILLABLE_THRESHOLD, + DEFAULT_SUBAGENT_INVOKE_TIMEOUT_SECONDS, + EXCLUDED_STATE_KEYS, + MAX_SUBAGENT_BATCH_SIZE, +) from .propagation import wrap_with_tool_call_id from .resume import ( build_resume_command, @@ -37,11 +50,70 @@ from .resume import ( get_first_pending_subagent_interrupt, hitlrequest_action_count, ) +from .spawn_paused import is_spawn_paused logger = logging.getLogger(__name__) _perf_log = get_perf_logger() +class SubagentInvokeTimeoutError(Exception): + """Raised when ``subagent.ainvoke`` exceeds the configured wall-clock budget. + + Carries the subagent name and the elapsed seconds so the caller can + synthesize a ToolMessage that the orchestrator can act on (re-route, + surface to the user, or retry with a smaller scope). + """ + + def __init__(self, subagent_type: str, elapsed_seconds: float) -> None: + super().__init__( + f"subagent {subagent_type!r} exceeded " + f"{DEFAULT_SUBAGENT_INVOKE_TIMEOUT_SECONDS:.0f}s budget " + f"(elapsed={elapsed_seconds:.1f}s)" + ) + self.subagent_type = subagent_type + self.elapsed_seconds = elapsed_seconds + + +_T = TypeVar("_T") + + +async def _ainvoke_with_timeout[T]( + coro: Awaitable[_T], *, subagent_type: str, started_at: float +) -> _T: + """Apply :data:`DEFAULT_SUBAGENT_INVOKE_TIMEOUT_SECONDS` to ``coro``. + + A non-positive timeout disables the cap (configurable via the + ``SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS`` env var). On expiry the + underlying task is cancelled and :class:`SubagentInvokeTimeoutError` is + raised — the caller wraps it into a synthetic ToolMessage so the + orchestrator can decide what to do. + """ + timeout = DEFAULT_SUBAGENT_INVOKE_TIMEOUT_SECONDS + if timeout <= 0: + return await coro + try: + return await asyncio.wait_for(coro, timeout=timeout) + except TimeoutError as exc: + elapsed = time.perf_counter() - started_at + raise SubagentInvokeTimeoutError(subagent_type, elapsed) from exc + + +def _synthesize_timeout_command( + exc: SubagentInvokeTimeoutError, *, tool_call_id: str +) -> Command: + """Turn a :class:`SubagentInvokeTimeoutError` into a ToolMessage the parent can read.""" + content = ( + f"Subagent {exc.subagent_type!r} timed out after " + f"{exc.elapsed_seconds:.1f}s (budget=" + f"{DEFAULT_SUBAGENT_INVOKE_TIMEOUT_SECONDS:.0f}s). " + "The work was cancelled. Treat as status=error; re-route with a " + "narrower scope or different specialist." + ) + return Command( + update={"messages": [ToolMessage(content=content, tool_call_id=tool_call_id)]} + ) + + def _reraise_stamped_subagent_interrupt( gi: GraphInterrupt, tool_call_id: str ) -> NoReturn: @@ -70,11 +142,24 @@ def _reraise_stamped_subagent_interrupt( def build_task_tool_with_parent_config( subagents: list[dict[str, Any]], task_description: str | None = None, + *, + search_space_id: int | None = None, ) -> BaseTool: """Upstream ``_build_task_tool`` + parent ``runtime.config`` propagation + resume bridging.""" subagent_graphs: dict[str, Runnable] = { spec["name"]: spec["runnable"] for spec in subagents } + # Per-subagent context-hint providers (see ``SurfSenseSubagentSpec``). + # The mapping is sparse: only routes that opted in via ``pack_subagent`` + # appear here, and the value is invoked once per ``task(...)`` call to + # generate a short string prepended to the subagent's first + # ``HumanMessage``. Failures are logged and swallowed — a broken hint + # provider must never prevent the underlying task from running. + subagent_hint_providers: dict[str, ContextHintProvider] = { + spec["name"]: provider + for spec in subagents + if (provider := spec.get(SURF_CONTEXT_HINT_PROVIDER_KEY)) is not None + } subagent_description_str = "\n".join( f"- {s['name']}: {s['description']}" for s in subagents ) @@ -88,6 +173,120 @@ def build_task_tool_with_parent_config( else: description = task_description + def _billable_call_update( + subagent_type: str, runtime: ToolRuntime + ) -> dict[str, Any]: + """Build the per-call ``billable_calls`` delta + an optional warning. + + The orchestrator's ``billable_calls`` map is summed by + :func:`_int_counter_merge_reducer`, so we always emit + ``{subagent_type: 1}`` and let the reducer accumulate. If the + cumulative count *after* this call would cross the configured + threshold, we also slip a soft ``messages`` entry into the update + so the orchestrator can read it on its next step and self-limit. + Returning a plain ``dict`` (vs. an extra :class:`Command`) keeps + the helper composable with the existing single/batch return paths. + """ + delta: dict[str, Any] = {"billable_calls": {subagent_type: 1}} + threshold = DEFAULT_SUBAGENT_BILLABLE_THRESHOLD + if threshold <= 0: + return delta + prior = runtime.state.get("billable_calls") or {} + # ``prior`` may be a plain dict or a reducer-managed mapping; only + # int values are counted so a malformed checkpoint can't crash us. + prior_total = sum(v for v in prior.values() if isinstance(v, int)) + new_total = prior_total + 1 + if prior_total < threshold <= new_total: + warn = ( + f"[budget warning] This turn has dispatched {new_total} " + f"subagent calls (soft cap = {threshold}). Wrap up the " + "user's request with what you have rather than launching " + "more specialists; surface a partial answer if needed." + ) + delta["_billable_warn_text"] = warn + return delta + + def _attach_billable( + cmd: Command, subagent_type: str, runtime: ToolRuntime + ) -> Command: + """Merge the per-call billable counter (and warning) into ``cmd``.""" + delta = _billable_call_update(subagent_type, runtime) + warn_text = delta.pop("_billable_warn_text", None) + # ``cmd.update`` may be a dict or LangGraph ``UpdateDict``; defensively + # copy so we don't mutate state shared across other tool returns. + update = dict(getattr(cmd, "update", {}) or {}) + for key, value in delta.items(): + update[key] = value + if warn_text: + existing_msgs = list(update.get("messages") or []) + existing_msgs.append( + ToolMessage(content=warn_text, tool_call_id=runtime.tool_call_id) + ) + update["messages"] = existing_msgs + return Command(update=update) + + def _safe_message_text(msg: Any) -> str: + """Pull text out of a BaseMessage without trusting the ``.text`` property. + + ``BaseMessage.text`` walks ``content_blocks`` and crashes with + ``TypeError: 'NoneType' object is not iterable`` when ``content`` is + ``None`` (common for tool-call AIMessages whose payload is purely + structured). ``getattr(msg, "text", None)`` does not catch this + because Python evaluates the property body before falling back to + the default. Read ``content`` directly and coerce defensively. + """ + try: + content = getattr(msg, "content", None) + except Exception: + content = None + if content is None: + return "" + if isinstance(content, str): + return content + if isinstance(content, list): + parts: list[str] = [] + for block in content: + if isinstance(block, str): + parts.append(block) + elif isinstance(block, dict): + block_text = block.get("text") or block.get("content") + if isinstance(block_text, str): + parts.append(block_text) + return " ".join(parts) + return str(content) + + def _build_tool_trace(messages: list[Any]) -> list[dict[str, Any]]: + """Compress the subagent's message stream into a compact tool trace. + + Each entry is ``{"tool": <name>, "status": "ok"|"error", "preview": + <≤120 chars>}`` so the orchestrator can show "this is what your + specialist actually did" without dumping the full message stream + back through the prompt. The list is attached to the returned + ToolMessage's ``additional_kwargs`` (under ``"surf_tool_trace"``); + the LLM never sees it, but UI / observability code can pluck it + out of the checkpoint. + """ + trace: list[dict[str, Any]] = [] + for msg in messages: + tool_name = getattr(msg, "name", None) + tool_call_id_attr = getattr(msg, "tool_call_id", None) + if not tool_name and not tool_call_id_attr: + # Only ToolMessages have either field; skip AIMessage / + # HumanMessage / SystemMessage frames. + continue + status = getattr(msg, "status", None) or "ok" + preview = _safe_message_text(msg).strip().replace("\n", " ") + if len(preview) > 120: + preview = preview[:117] + "..." + trace.append( + { + "tool": tool_name or "<unknown>", + "status": status, + "preview": preview, + } + ) + return trace + def _return_command_with_state_update(result: dict, tool_call_id: str) -> Command: if "messages" not in result: msg = ( @@ -106,15 +305,51 @@ def build_task_tool_with_parent_config( "output to forward back to the user." ) raise ValueError(msg) - last_text = getattr(messages[-1], "text", None) or "" - message_text = last_text.rstrip() + message_text = _safe_message_text(messages[-1]).rstrip() + # Tool-trace is purely observability — wrap defensively so a single + # malformed frame never bubbles up and kills the whole user turn. + try: + tool_trace = _build_tool_trace(messages) + except Exception: + logger.exception( + "Failed to build tool_trace for subagent return; " + "continuing without trace." + ) + tool_trace = [] + tool_msg = ToolMessage(message_text, tool_call_id=tool_call_id) + if tool_trace: + # ``additional_kwargs`` is a free-form dict on BaseMessage; using + # a ``surf_`` prefix avoids collision with provider-specific keys + # (e.g. Anthropic's ``cache_control``). The LLM doesn't see it; + # consumers (UI, observability) read it off the checkpoint. + tool_msg.additional_kwargs["surf_tool_trace"] = tool_trace return Command( update={ **state_update, - "messages": [ToolMessage(message_text, tool_call_id=tool_call_id)], + "messages": [tool_msg], } ) + def _resolve_context_hint( + subagent_type: str, description: str, runtime: ToolRuntime + ) -> str | None: + """Run the per-subagent hint provider; swallow & log any exception.""" + provider = subagent_hint_providers.get(subagent_type) + if provider is None: + return None + try: + hint = provider(runtime.state, description) + except Exception: + logger.exception( + "Context-hint provider for subagent %r raised; skipping hint.", + subagent_type, + ) + return None + if not hint or not isinstance(hint, str): + return None + cleaned = hint.strip() + return cleaned or None + def _validate_and_prepare_state( subagent_type: str, description: str, runtime: ToolRuntime ) -> tuple[Runnable, dict]: @@ -122,20 +357,308 @@ def build_task_tool_with_parent_config( subagent_state = { k: v for k, v in runtime.state.items() if k not in EXCLUDED_STATE_KEYS } - subagent_state["messages"] = [HumanMessage(content=description)] + hint = _resolve_context_hint(subagent_type, description, runtime) + if hint: + # Prepend as a tagged block so the subagent prompt can pattern-match + # on the section (and a future change can lift it into its own + # ``SystemMessage`` if needed). + payload = f"<context_hint>\n{hint}\n</context_hint>\n\n{description}" + else: + payload = description + subagent_state["messages"] = [HumanMessage(content=payload)] return subagent, subagent_state + def _merge_batch_results( + results: list[tuple[int, str, dict | str, dict | None]], + runtime: ToolRuntime, + ) -> Command: + """Combine per-child results into one Command with a combined ToolMessage. + + ``results`` is a list of ``(task_index, subagent_type, + payload_or_error_text, child_state_update)`` tuples — preserving the + input order so the orchestrator can map each block back to the task + it dispatched. State updates are merged by reducer for keys outside + :data:`EXCLUDED_STATE_KEYS`; everything else (``messages``, ``todos``, + etc.) is replaced by the synthesized aggregate ToolMessage. Every + child also contributes a ``billable_calls`` increment so cost + accounting matches single-mode dispatch. + """ + results.sort(key=lambda r: r[0]) + merged_state: dict[str, Any] = {} + billable_delta: dict[str, int] = {} + message_blocks: list[str] = [] + batch_trace: list[dict[str, Any]] = [] + for task_index, subagent_type, payload, state_update in results: + billable_delta[subagent_type] = billable_delta.get(subagent_type, 0) + 1 + if isinstance(payload, str): + # Pre-flight error or per-task exception text. + message_blocks.append(f"[task {task_index}] {payload}") + batch_trace.append( + { + "task_index": task_index, + "subagent_type": subagent_type, + "status": "error", + "tool_trace": [], + } + ) + continue + messages = payload.get("messages") or [] + last_text = _safe_message_text(messages[-1]).rstrip() if messages else "" + message_blocks.append( + f"[task {task_index}] {last_text or '<empty>'}" + ) + try: + child_trace = _build_tool_trace(messages) + except Exception: + logger.exception( + "Failed to build tool_trace for batch task_index=%d; continuing.", + task_index, + ) + child_trace = [] + batch_trace.append( + { + "task_index": task_index, + "subagent_type": subagent_type, + "status": "ok", + "tool_trace": child_trace, + } + ) + if state_update: + # Naive merge: later tasks win on scalar collisions; reducer-backed + # fields (``receipts``, ``files`` etc.) accumulate at apply time. + merged_state.update(state_update) + aggregate = "\n\n".join(message_blocks) + aggregate_msg = ToolMessage( + content=aggregate, tool_call_id=runtime.tool_call_id + ) + if batch_trace: + aggregate_msg.additional_kwargs["surf_tool_trace"] = batch_trace + update: dict[str, Any] = { + **merged_state, + "billable_calls": billable_delta, + "messages": [aggregate_msg], + } + # Soft-cap warning: check the cumulative count after attribution. + threshold = DEFAULT_SUBAGENT_BILLABLE_THRESHOLD + if threshold > 0: + prior = runtime.state.get("billable_calls") or {} + prior_total = sum(v for v in prior.values() if isinstance(v, int)) + new_total = prior_total + sum(billable_delta.values()) + if prior_total < threshold <= new_total: + update["messages"].append( + ToolMessage( + content=( + f"[budget warning] This turn has dispatched " + f"{new_total} subagent calls (soft cap = " + f"{threshold}). Wrap up the user's request with " + "what you have rather than launching more " + "specialists; surface a partial answer if needed." + ), + tool_call_id=runtime.tool_call_id, + ) + ) + return Command(update=update) + + async def _ainvoke_one_batch_child( + *, + task_index: int, + subagent_type: str, + description: str, + runtime: ToolRuntime, + semaphore: asyncio.Semaphore, + ) -> tuple[int, str, dict | str, dict | None]: + """Run one child of a batched ``task`` call under the concurrency cap. + + Errors are returned as plain text in slot 2 so a single child's + failure does not abort the whole batch. ``GraphInterrupt`` from a + batched child is currently treated as a hard failure for that child + only — batched HITL is intentionally out of scope for the v1 + rollout (see plan tier 2 item 4 risks). + """ + async with semaphore: + if subagent_type not in subagent_graphs: + allowed_types = ", ".join([f"`{k}`" for k in subagent_graphs]) + return ( + task_index, + subagent_type, + ( + f"Subagent {subagent_type!r} does not exist; " + f"allowed: {allowed_types}" + ), + None, + ) + subagent, subagent_state = _validate_and_prepare_state( + subagent_type, description, runtime + ) + sub_config = subagent_invoke_config(runtime) + started_at = time.perf_counter() + try: + result = await _ainvoke_with_timeout( + subagent.ainvoke(subagent_state, config=sub_config), + subagent_type=subagent_type, + started_at=started_at, + ) + except SubagentInvokeTimeoutError as exc: + logger.warning( + "Batch child %d (%s) timed out after %.1fs", + task_index, + subagent_type, + exc.elapsed_seconds, + ) + return (task_index, subagent_type, str(exc), None) + except GraphInterrupt: + # Batched HITL is unsupported in v1 — surface as a failure + # for this child so the rest of the batch still completes. + logger.warning( + "Batch child %d (%s) raised GraphInterrupt; batched HITL " + "is not supported. Re-dispatch this task as a single " + "(non-batched) `task(...)` call to get the HITL prompt.", + task_index, + subagent_type, + ) + return ( + task_index, + subagent_type, + ( + f"Subagent {subagent_type!r} needs human approval. " + "Re-dispatch this task as a single (non-batched) " + "`task(...)` call so the approval card can be shown." + ), + None, + ) + except Exception as exc: + logger.exception( + "Batch child %d (%s) raised: %s", + task_index, + subagent_type, + exc, + ) + return ( + task_index, + subagent_type, + f"Subagent {subagent_type!r} error: {exc}", + None, + ) + child_state_update = { + k: v for k, v in result.items() if k not in EXCLUDED_STATE_KEYS + } + return (task_index, subagent_type, result, child_state_update) + + def _coerce_batch_arg(tasks: Any) -> list[dict] | str: + """Rescue common LLM-side malformations of the ``tasks`` argument. + + Some providers serialise an array argument as a JSON-encoded string, + and small models occasionally hand back a single ``{description, + subagent_type}`` dict instead of a one-element array. Both are + recovered here with a WARN log so the issue is visible in metrics + but the user's turn still completes; truly broken shapes return a + plain string that the caller surfaces as the tool error. + """ + if isinstance(tasks, list): + return tasks + if isinstance(tasks, dict): + logger.warning( + "task: `tasks` was a single dict; coercing to a 1-element list. " + "Orchestrators should send `tasks=[{...}]` directly." + ) + return [tasks] + if isinstance(tasks, str): + stripped = tasks.strip() + if not stripped: + return "tasks: argument is empty." + try: + parsed = json.loads(stripped) + except json.JSONDecodeError as exc: + return ( + f"tasks: argument is a string but not valid JSON ({exc.msg}). " + "Send a JSON array of `{description, subagent_type}` objects." + ) + logger.warning( + "task: `tasks` was a JSON-encoded string; parsed to %s. " + "Orchestrators should send a JSON array directly.", + type(parsed).__name__, + ) + return _coerce_batch_arg(parsed) + return ( + f"tasks: unsupported type {type(tasks).__name__}; expected an array " + "of `{description, subagent_type}` objects." + ) + + async def _adispatch_batch( + tasks: list[dict], runtime: ToolRuntime + ) -> Command | str: + """Fan-out helper for the ``tasks`` array shape. + + Bounded by :data:`MAX_SUBAGENT_BATCH_SIZE` and concurrency-capped + at :data:`DEFAULT_SUBAGENT_BATCH_CONCURRENCY`. Returns a single + :class:`Command` that the LLM sees as one ToolMessage per child, + prefixed with ``[task <index>]`` so it can map back to the input + order. + """ + if not tasks: + return "tasks: array is empty; nothing to dispatch." + if len(tasks) > MAX_SUBAGENT_BATCH_SIZE: + return ( + f"tasks: too many children ({len(tasks)}); " + f"max is {MAX_SUBAGENT_BATCH_SIZE}. Split the batch." + ) + normalized: list[tuple[int, str, str]] = [] + for idx, item in enumerate(tasks): + if not isinstance(item, dict): + return ( + f"tasks[{idx}]: must be an object with description+subagent_type." + ) + description = item.get("description") + subagent_type = item.get("subagent_type") + if not isinstance(description, str) or not description.strip(): + return f"tasks[{idx}]: missing or empty 'description'." + if not isinstance(subagent_type, str) or not subagent_type.strip(): + return f"tasks[{idx}]: missing or empty 'subagent_type'." + normalized.append((idx, subagent_type.strip(), description)) + semaphore = asyncio.Semaphore(DEFAULT_SUBAGENT_BATCH_CONCURRENCY) + coros = [ + _ainvoke_one_batch_child( + task_index=idx, + subagent_type=subagent_type, + description=description, + runtime=runtime, + semaphore=semaphore, + ) + for idx, subagent_type, description in normalized + ] + results = await asyncio.gather(*coros) + return _merge_batch_results(list(results), runtime) + def task( description: Annotated[ - str, - "A detailed description of the task for the subagent to perform autonomously. Include all necessary context and specify the expected output format.", - ], + str | None, + "Single-mode: a detailed task description for the subagent. Required unless `tasks` is provided.", + ] = None, subagent_type: Annotated[ - str, - "The type of subagent to use. Must be one of the available agent types listed in the tool description.", - ], - runtime: ToolRuntime, + str | None, + "Single-mode: the type of subagent to use. Required unless `tasks` is provided.", + ] = None, + runtime: ToolRuntime = None, # type: ignore[assignment] + tasks: Annotated[ + list[dict] | None, + ( + "Batch-mode: array of `{description, subagent_type}` objects. " + "Synchronous path does not support batch mode; orchestrators " + "must use the async event loop to fan out." + ), + ] = None, ) -> str | Command: + if tasks is not None: + return ( + "task: batch mode (`tasks=[...]`) is only supported on the async " + "path. SurfSense orchestrators always run in an event loop, so " + "this should never fire — file a bug if you see it." + ) + if not description or not subagent_type: + return ( + "task: must provide either single-mode (`description`+`subagent_type`) " + "or batch-mode (`tasks`)." + ) if subagent_type not in subagent_graphs: allowed_types = ", ".join([f"`{k}`" for k in subagent_graphs]) return ( @@ -284,16 +807,65 @@ def build_task_tool_with_parent_config( async def atask( description: Annotated[ - str, - "A detailed description of the task for the subagent to perform autonomously. Include all necessary context and specify the expected output format.", - ], + str | None, + "Single-mode: a detailed task description for the subagent. Required unless `tasks` is provided.", + ] = None, subagent_type: Annotated[ - str, - "The type of subagent to use. Must be one of the available agent types listed in the tool description.", - ], - runtime: ToolRuntime, + str | None, + "Single-mode: the type of subagent to use. Required unless `tasks` is provided.", + ] = None, + runtime: ToolRuntime = None, # type: ignore[assignment] + tasks: Annotated[ + list[dict] | None, + ( + "Batch-mode: array of `{description, subagent_type}` objects " + "to fan out concurrently (max " + f"{MAX_SUBAGENT_BATCH_SIZE}, concurrency " + f"{DEFAULT_SUBAGENT_BATCH_CONCURRENCY}). Mutually exclusive " + "with single-mode args. Batched children do not support " + "human-in-the-loop interrupts; re-dispatch as single mode " + "if a child needs approval." + ), + ] = None, ) -> str | Command: atask_start = time.perf_counter() + # Kill switch: when ops flips the spawn-paused flag for this + # workspace, every ``task(...)`` invocation (single- or batch-mode) + # short-circuits with a clear ToolMessage so the orchestrator can + # tell the user what happened and stop hammering downstream APIs. + if await is_spawn_paused(search_space_id): + logger.warning( + "[hitl_route] atask SPAWN_PAUSED: search_space_id=%s tool_call_id=%s", + search_space_id, + runtime.tool_call_id, + ) + return ( + "task: subagent dispatch is currently paused for this workspace. " + "Acknowledge to the user that delegation is temporarily disabled " + "(ops kill switch); do not retry until the pause is lifted." + ) + if tasks is not None: + if description or subagent_type: + return ( + "task: cannot combine `tasks` with `description`/`subagent_type`. " + "Use either single-mode (description+subagent_type) or batch-mode (tasks)." + ) + if not runtime.tool_call_id: + raise ValueError("Tool call ID is required for subagent invocation") + coerced = _coerce_batch_arg(tasks) + if isinstance(coerced, str): + return coerced + logger.info( + "[hitl_route] atask BATCH ENTRY: size=%d tool_call_id=%s", + len(coerced), + runtime.tool_call_id, + ) + return await _adispatch_batch(coerced, runtime) + if not description or not subagent_type: + return ( + "task: must provide either single-mode (`description`+`subagent_type`) " + "or batch-mode (`tasks`)." + ) logger.info( "[hitl_route] atask ENTRY: subagent_type=%r tool_call_id=%s", subagent_type, @@ -358,11 +930,37 @@ def build_task_tool_with_parent_config( subagent_type=subagent_type, path=invoke_path ) as sp: try: - result = await subagent.ainvoke( - build_resume_command(resume_value, pending_id), - config=sub_config, + result = await _ainvoke_with_timeout( + subagent.ainvoke( + build_resume_command(resume_value, pending_id), + config=sub_config, + ), + subagent_type=subagent_type, + started_at=ainvoke_start, ) sp.set_attribute("subagent.outcome", ainvoke_outcome) + except SubagentInvokeTimeoutError as exc: + ainvoke_outcome = "timeout" + sp.set_attribute("subagent.outcome", ainvoke_outcome) + ot_metrics.record_subagent_invoke_duration( + (time.perf_counter() - ainvoke_start) * 1000, + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + ot_metrics.record_subagent_invoke_outcome( + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + logger.warning( + "Subagent %r ainvoke (resume) timed out after %.1fs", + subagent_type, + exc.elapsed_seconds, + ) + return _synthesize_timeout_command( + exc, tool_call_id=runtime.tool_call_id + ) except GraphInterrupt as gi: ainvoke_outcome = "interrupted" sp.set_attribute("subagent.outcome", ainvoke_outcome) @@ -408,10 +1006,34 @@ def build_task_tool_with_parent_config( subagent_type=subagent_type, path=invoke_path ) as sp: try: - result = await subagent.ainvoke( - subagent_state, config=sub_config + result = await _ainvoke_with_timeout( + subagent.ainvoke(subagent_state, config=sub_config), + subagent_type=subagent_type, + started_at=ainvoke_start, ) sp.set_attribute("subagent.outcome", ainvoke_outcome) + except SubagentInvokeTimeoutError as exc: + ainvoke_outcome = "timeout" + sp.set_attribute("subagent.outcome", ainvoke_outcome) + ot_metrics.record_subagent_invoke_duration( + (time.perf_counter() - ainvoke_start) * 1000, + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + ot_metrics.record_subagent_invoke_outcome( + subagent_type=subagent_type, + path=invoke_path, + outcome=ainvoke_outcome, + ) + logger.warning( + "Subagent %r ainvoke (fresh) timed out after %.1fs", + subagent_type, + exc.elapsed_seconds, + ) + return _synthesize_timeout_command( + exc, tool_call_id=runtime.tool_call_id + ) except GraphInterrupt as gi: ainvoke_outcome = "interrupted" sp.set_attribute("subagent.outcome", ainvoke_outcome) @@ -481,7 +1103,7 @@ def build_task_tool_with_parent_config( path=invoke_path, outcome=ainvoke_outcome, ) - return cmd + return _attach_billable(cmd, subagent_type, runtime) return StructuredTool.from_function( name="task", diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/kb_context_projection.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/kb_context_projection.py index e8a4c9899..2685d8a9b 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/kb_context_projection.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/kb_context_projection.py @@ -52,9 +52,7 @@ class KbContextProjectionMiddleware(AgentMiddleware): # type: ignore[type-arg] messages.insert(insert_at, SystemMessage(content=tree_text)) priority_count = 0 if priority: - priority_count = ( - len(priority) if hasattr(priority, "__len__") else 1 - ) + priority_count = len(priority) if hasattr(priority, "__len__") else 1 messages.insert(insert_at, _render_priority_message(priority)) _perf_log.info( "[kb_context_projection] tree_chars=%d priority_items=%d elapsed=%.3fs", diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/ask/request.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/ask/request.py index a725bfee1..3db51883d 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/ask/request.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/shared/permissions/ask/request.py @@ -17,8 +17,7 @@ from langchain_core.tools import BaseTool from langgraph.types import interrupt from app.agents.new_chat.permissions import Rule -from app.observability import metrics as ot_metrics -from app.observability import otel as ot +from app.observability import metrics as ot_metrics, otel as ot from .decision import normalize_permission_decision from .payload import PERMISSION_ASK_INTERRUPT_TYPE, build_permission_ask_payload diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/stack.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/stack.py index c1ebe31ca..3b20d8915 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/stack.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/stack.py @@ -173,6 +173,7 @@ def build_main_agent_deepagent_middleware( subagents=subagents, system_prompt=None, task_description=TASK_TOOL_DESCRIPTION, + search_space_id=search_space_id, ), resilience.model_call_limit, resilience.tool_call_limit, diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/system_prompt.md index c44f131bb..413791037 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/system_prompt.md @@ -42,14 +42,16 @@ Return **only** one JSON object (no markdown/prose): "evidence": { "artifact_type": "report" | "podcast" | "video_presentation" | "resume" | "image" | null, "artifact_id": string | null, - "artifact_location": string | null + "artifact_location": string | null, + "receipts": Receipt[] | null }, "next_step": string | null, "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` -> `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` -> `next_step` must be non-null. -- `status=blocked` due to missing required inputs -> `missing_fields` must be non-null. +Route-specific rules: +- `evidence.receipts` quotes the Receipt(s) returned by `generate_report` / `generate_podcast` / `generate_video_presentation` / `generate_resume` / `generate_image` this turn, verbatim. The Receipt's `type` enum is one of `report` | `podcast` | `video_presentation` | `resume` | `image`. +<include snippet="output_contract_base"/> </output_contract> + +<include snippet="verifiable_handle"/> diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py index ab9dbc0ea..f170a35db 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py @@ -4,11 +4,15 @@ import hashlib import logging from typing import Any +from langchain.tools import ToolRuntime from langchain_core.tools import tool +from langgraph.types import Command from litellm import aimage_generation from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.shared.receipt import make_receipt +from app.agents.shared.receipt_command import with_receipt from app.config import config from app.db import ( ImageGeneration, @@ -66,8 +70,9 @@ def create_generate_image_tool( @tool async def generate_image( prompt: str, + runtime: ToolRuntime, n: int = 1, - ) -> dict[str, Any]: + ) -> Command: """ Generate an image from a text description using AI image models. @@ -82,6 +87,21 @@ def create_generate_image_tool( Returns: A dictionary containing the generated image(s) for display in the chat. """ + + def _failed(payload: dict[str, Any], *, error: str) -> Command: + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="image", + operation="generate", + status="failed", + preview=prompt[:200] if prompt else None, + error=error, + ), + tool_call_id=runtime.tool_call_id, + ) + try: # Use a per-call session so concurrent tool calls don't share an # AsyncSession (which is not concurrency-safe). The streaming @@ -93,7 +113,10 @@ def create_generate_image_tool( ) search_space = result.scalars().first() if not search_space: - return {"error": "Search space not found"} + return _failed( + {"error": "Search space not found"}, + error="Search space not found", + ) config_id = ( search_space.image_generation_config_id or IMAGE_GEN_AUTO_MODE_ID @@ -112,19 +135,19 @@ def create_generate_image_tool( # Call litellm based on config type if is_image_gen_auto_mode(config_id): if not ImageGenRouterService.is_initialized(): - return { - "error": "No image generation models configured. " + err = ( + "No image generation models configured. " "Please add an image model in Settings > Image Models." - } + ) + return _failed({"error": err}, error=err) response = await ImageGenRouterService.aimage_generation( prompt=prompt, model="auto", **gen_kwargs ) elif config_id < 0: cfg = _get_global_image_gen_config(config_id) if not cfg: - return { - "error": f"Image generation config {config_id} not found" - } + err = f"Image generation config {config_id} not found" + return _failed({"error": err}, error=err) model_string = _build_model_string( cfg.get("provider", ""), @@ -151,9 +174,8 @@ def create_generate_image_tool( ) db_cfg = cfg_result.scalars().first() if not db_cfg: - return { - "error": f"Image generation config {config_id} not found" - } + err = f"Image generation config {config_id} not found" + return _failed({"error": err}, error=err) model_string = _build_model_string( db_cfg.provider.value, @@ -200,7 +222,10 @@ def create_generate_image_tool( # Extract image URLs from response images = response_dict.get("data", []) if not images: - return {"error": "No images were generated"} + return _failed( + {"error": "No images were generated"}, + error="No images were generated", + ) first_image = images[0] revised_prompt = first_image.get("revised_prompt", prompt) @@ -219,11 +244,14 @@ def create_generate_image_tool( f"{db_image_gen_id}/image?token={access_token}" ) else: - return {"error": "No displayable image data in the response"} + return _failed( + {"error": "No displayable image data in the response"}, + error="No displayable image data in the response", + ) image_id = f"image-{hashlib.md5(image_url.encode()).hexdigest()[:12]}" - return { + payload = { "id": image_id, "assetId": image_url, "src": image_url, @@ -236,12 +264,26 @@ def create_generate_image_tool( "prompt": prompt, "image_count": len(images), } + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="image", + operation="generate", + status="success", + external_id=str(db_image_gen_id), + verifiable_url=image_url, + preview=(revised_prompt or prompt)[:200], + ), + tool_call_id=runtime.tool_call_id, + ) except Exception as e: logger.exception("Image generation failed in tool") - return { - "error": f"Image generation failed: {e!s}", - "prompt": prompt, - } + err = f"Image generation failed: {e!s}" + return _failed( + {"error": err, "prompt": prompt}, + error=err, + ) return generate_image diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py index 55d9b3565..84617d38b 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py @@ -1,12 +1,28 @@ -"""Factory for a podcast-generation tool that queues background work and returns an ID for polling.""" +"""Factory for a podcast-generation tool. +Dispatches the heavy generation to Celery and then polls the podcast row +until it reaches a terminal status (READY/FAILED). The tool always +returns a real terminal ``Receipt`` — never a pending one. The wait is +bounded by the existing per-invocation safety net +(``SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS`` in multi-agent mode, +HTTP / process lifetime in single-agent mode). +""" + +import logging from typing import Any +from langchain.tools import ToolRuntime from langchain_core.tools import tool +from langgraph.types import Command from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.shared.deliverable_wait import wait_for_deliverable +from app.agents.shared.receipt import make_receipt +from app.agents.shared.receipt_command import with_receipt from app.db import Podcast, PodcastStatus, shielded_async_session +logger = logging.getLogger(__name__) + def create_generate_podcast_tool( search_space_id: int, @@ -19,9 +35,10 @@ def create_generate_podcast_tool( @tool async def generate_podcast( source_content: str, + runtime: ToolRuntime, podcast_title: str = "SurfSense Podcast", user_prompt: str | None = None, - ) -> dict[str, Any]: + ) -> Command: """ Generate a podcast from the provided content. @@ -70,23 +87,101 @@ def create_generate_podcast_tool( user_prompt=user_prompt, ) - print(f"[generate_podcast] Created podcast {podcast_id}, task: {task.id}") + logger.info( + "[generate_podcast] Created podcast %s, task: %s", + podcast_id, + task.id, + ) - return { - "status": PodcastStatus.PENDING.value, + # Wait until the Celery worker flips the row to a terminal + # state. The wait is bounded only by the subagent invoke + # timeout (multi-agent) or HTTP lifetime (single-agent) — + # see app.agents.shared.deliverable_wait for details. + terminal_status, columns, elapsed = await wait_for_deliverable( + model=Podcast, + row_id=podcast_id, + columns=[Podcast.status, Podcast.file_location], + terminal_statuses={PodcastStatus.READY, PodcastStatus.FAILED}, + ) + + if terminal_status == PodcastStatus.READY: + file_location = columns[1] if columns else None + logger.info( + "[generate_podcast] Podcast %s READY in %.2fs (file=%s)", + podcast_id, + elapsed, + file_location, + ) + payload: dict[str, Any] = { + "status": PodcastStatus.READY.value, + "podcast_id": podcast_id, + "title": podcast_title, + "file_location": file_location, + "message": ( + "Podcast generated and saved to your podcast panel." + ), + } + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="podcast", + operation="generate", + status="success", + external_id=str(podcast_id), + preview=podcast_title, + ), + tool_call_id=runtime.tool_call_id, + ) + + # Only other terminal state is FAILED. + logger.warning( + "[generate_podcast] Podcast %s FAILED in %.2fs", + podcast_id, + elapsed, + ) + err = "Background worker reported FAILED status for this podcast." + payload = { + "status": PodcastStatus.FAILED.value, "podcast_id": podcast_id, "title": podcast_title, - "message": "Podcast generation started. This may take a few minutes.", + "error": err, } + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="podcast", + operation="generate", + status="failed", + external_id=str(podcast_id), + preview=podcast_title, + error=err, + ), + tool_call_id=runtime.tool_call_id, + ) except Exception as e: error_message = str(e) - print(f"[generate_podcast] Error: {error_message}") - return { + logger.exception("[generate_podcast] Error: %s", error_message) + payload = { "status": PodcastStatus.FAILED.value, "error": error_message, "title": podcast_title, "podcast_id": None, } + receipt = make_receipt( + route="deliverables", + type="podcast", + operation="generate", + status="failed", + preview=podcast_title, + error=error_message, + ) + return with_receipt( + payload=payload, + receipt=receipt, + tool_call_id=runtime.tool_call_id, + ) return generate_podcast diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/report.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/report.py index 385100c62..f12ca8a90 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/report.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/report.py @@ -6,10 +6,14 @@ import logging import re from typing import Any +from langchain.tools import ToolRuntime from langchain_core.callbacks import dispatch_custom_event from langchain_core.messages import HumanMessage from langchain_core.tools import tool +from langgraph.types import Command +from app.agents.shared.receipt import make_receipt +from app.agents.shared.receipt_command import with_receipt from app.db import Report, shielded_async_session from app.services.connector_service import ConnectorService from app.services.llm_service import get_document_summary_llm @@ -573,13 +577,14 @@ def create_generate_report_tool( @tool async def generate_report( topic: str, + runtime: ToolRuntime, source_content: str = "", source_strategy: str = "provided", search_queries: list[str] | None = None, report_style: str = "detailed", user_instructions: str | None = None, parent_report_id: int | None = None, - ) -> dict[str, Any]: + ) -> Command: """ Generate a structured Markdown report artifact from provided content. @@ -692,6 +697,23 @@ def create_generate_report_tool( parent_report_content: str | None = None report_group_id: int | None = None + def _failed(payload: dict[str, Any], *, error: str) -> Command: + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="report", + operation="generate", + status="failed", + external_id=str(payload.get("report_id")) + if payload.get("report_id") is not None + else None, + preview=topic, + error=error, + ), + tool_call_id=runtime.tool_call_id, + ) + async def _save_failed_report(error_msg: str) -> int | None: """Persist a failed report row using a short-lived session.""" try: @@ -753,12 +775,15 @@ def create_generate_report_tool( "No LLM configured. Please configure a language model in Settings." ) report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": topic, - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": topic, + }, + error=error_msg, + ) # Build the user instructions string user_instructions_section = "" @@ -971,12 +996,15 @@ def create_generate_report_tool( if not report_content or not isinstance(report_content, str): error_msg = "LLM returned empty or invalid content" report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": topic, - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": topic, + }, + error=error_msg, + ) # LLMs often wrap output in ```markdown ... ``` fences — strip them report_content = _strip_wrapping_code_fences(report_content) @@ -984,12 +1012,15 @@ def create_generate_report_tool( if not report_content: error_msg = "LLM returned empty or invalid content" report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": topic, - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": topic, + }, + error=error_msg, + ) # Strip any existing footer(s) carried over from parent version(s) while report_content.rstrip().endswith(_REPORT_FOOTER): @@ -1036,7 +1067,7 @@ def create_generate_report_tool( f"{metadata.get('section_count', 0)} sections" ) - return { + payload: dict[str, Any] = { "status": "ready", "report_id": saved_report_id, "title": topic, @@ -1045,17 +1076,32 @@ def create_generate_report_tool( "report_markdown": report_content, "message": f"Report generated successfully: {topic}", } + receipt = make_receipt( + route="deliverables", + type="report", + operation="generate", + status="success", + external_id=str(saved_report_id), + preview=topic, + ) + return with_receipt( + payload=payload, + receipt=receipt, + tool_call_id=runtime.tool_call_id, + ) except Exception as e: error_message = str(e) logger.exception(f"[generate_report] Error: {error_message}") report_id = await _save_failed_report(error_message) - - return { - "status": "failed", - "error": error_message, - "report_id": report_id, - "title": topic, - } + return _failed( + { + "status": "failed", + "error": error_message, + "report_id": report_id, + "title": topic, + }, + error=error_message, + ) return generate_report diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/resume.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/resume.py index ece3ce241..ad16b7ba7 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/resume.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/resume.py @@ -8,10 +8,14 @@ from typing import Any import pypdf import typst +from langchain.tools import ToolRuntime from langchain_core.callbacks import dispatch_custom_event from langchain_core.messages import HumanMessage from langchain_core.tools import tool +from langgraph.types import Command +from app.agents.shared.receipt import make_receipt +from app.agents.shared.receipt_command import with_receipt from app.db import Report, shielded_async_session from app.services.llm_service import get_document_summary_llm @@ -429,10 +433,11 @@ def create_generate_resume_tool( @tool async def generate_resume( user_info: str, + runtime: ToolRuntime, user_instructions: str | None = None, parent_report_id: int | None = None, max_pages: int = 1, - ) -> dict[str, Any]: + ) -> Command: """ Generate a professional resume as a Typst document. @@ -476,6 +481,41 @@ def create_generate_resume_tool( template = _get_template() llm_reference = _build_llm_reference(template) + def _success(payload: dict[str, Any], *, report_id: int, title: str) -> Command: + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="resume", + operation="generate", + status="success", + external_id=str(report_id), + preview=title, + ), + tool_call_id=runtime.tool_call_id, + ) + + def _failed( + payload: dict[str, Any], + *, + report_id: int | None, + error: str, + title: str = "Resume", + ) -> Command: + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="resume", + operation="generate", + status="failed", + external_id=str(report_id) if report_id is not None else None, + preview=title, + error=error, + ), + tool_call_id=runtime.tool_call_id, + ) + async def _save_failed_report(error_msg: str) -> int | None: try: async with shielded_async_session() as session: @@ -514,13 +554,17 @@ def create_generate_resume_tool( except ValueError as e: error_msg = str(e) report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": "Resume", - "content_type": "typst", - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": "Resume", + "content_type": "typst", + }, + report_id=report_id, + error=error_msg, + ) # ── Phase 1: READ ───────────────────────────────────────────── async with shielded_async_session() as read_session: @@ -541,13 +585,17 @@ def create_generate_resume_tool( "No LLM configured. Please configure a language model in Settings." ) report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": "Resume", - "content_type": "typst", - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": "Resume", + "content_type": "typst", + }, + report_id=report_id, + error=error_msg, + ) # ── Phase 2: LLM GENERATION ─────────────────────────────────── @@ -588,13 +636,17 @@ def create_generate_resume_tool( if not body or not isinstance(body, str): error_msg = "LLM returned empty or invalid content" report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": "Resume", - "content_type": "typst", - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": "Resume", + "content_type": "typst", + }, + report_id=report_id, + error=error_msg, + ) body = _strip_typst_fences(body) body = _strip_imports(body) @@ -661,13 +713,17 @@ def create_generate_resume_tool( f"{compile_error or 'Unknown compile error'}" ) report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": "Resume", - "content_type": "typst", - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": "Resume", + "content_type": "typst", + }, + report_id=report_id, + error=error_msg, + ) actual_pages = _count_pdf_pages(pdf_bytes) if actual_pages <= validated_max_pages: @@ -700,13 +756,17 @@ def create_generate_resume_tool( ): error_msg = "LLM returned empty content while compressing resume" report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": "Resume", - "content_type": "typst", - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": "Resume", + "content_type": "typst", + }, + report_id=report_id, + error=error_msg, + ) body = _strip_typst_fences(compress_response.content) body = _strip_imports(body) @@ -718,13 +778,17 @@ def create_generate_resume_tool( f"Hard limit: <= {MAX_RESUME_PAGES} page(s), actual: {actual_pages}." ) report_id = await _save_failed_report(error_msg) - return { - "status": "failed", - "error": error_msg, - "report_id": report_id, - "title": "Resume", - "content_type": "typst", - } + return _failed( + { + "status": "failed", + "error": error_msg, + "report_id": report_id, + "title": "Resume", + "content_type": "typst", + }, + report_id=report_id, + error=error_msg, + ) # ── Phase 4: SAVE ───────────────────────────────────────────── dispatch_custom_event( @@ -768,32 +832,40 @@ def create_generate_resume_tool( logger.info(f"[generate_resume] Created resume {saved_id}: {resume_title}") - return { - "status": "ready", - "report_id": saved_id, - "title": resume_title, - "content_type": "typst", - "is_revision": bool(parent_content), - "message": ( - f"Resume generated successfully: {resume_title}" - if target_page_met - else ( - f"Resume generated, but could not fit the target of <= {validated_max_pages} " - f"page(s). Final length: {actual_pages} page(s)." - ) - ), - } + return _success( + { + "status": "ready", + "report_id": saved_id, + "title": resume_title, + "content_type": "typst", + "is_revision": bool(parent_content), + "message": ( + f"Resume generated successfully: {resume_title}" + if target_page_met + else ( + f"Resume generated, but could not fit the target of <= {validated_max_pages} " + f"page(s). Final length: {actual_pages} page(s)." + ) + ), + }, + report_id=saved_id, + title=resume_title, + ) except Exception as e: error_message = str(e) logger.exception(f"[generate_resume] Error: {error_message}") report_id = await _save_failed_report(error_message) - return { - "status": "failed", - "error": error_message, - "report_id": report_id, - "title": "Resume", - "content_type": "typst", - } + return _failed( + { + "status": "failed", + "error": error_message, + "report_id": report_id, + "title": "Resume", + "content_type": "typst", + }, + report_id=report_id, + error=error_message, + ) return generate_resume diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py index a9f3447ab..8c52293de 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py @@ -1,12 +1,29 @@ -"""Factory for a video-presentation tool that queues background work and returns an ID for polling.""" +"""Factory for a video-presentation tool. +Dispatches the heavy generation to Celery and then polls the +video-presentation row until it reaches a terminal status (READY/FAILED). +The tool always returns a real terminal ``Receipt`` — never a pending +one. The wait is bounded by the existing per-invocation safety net +(``SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS`` in multi-agent mode, +HTTP / process lifetime in single-agent mode). Video rendering can be +heavy; raise that ceiling if your generations routinely exceed it. +""" + +import logging from typing import Any +from langchain.tools import ToolRuntime from langchain_core.tools import tool +from langgraph.types import Command from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.shared.deliverable_wait import wait_for_deliverable +from app.agents.shared.receipt import make_receipt +from app.agents.shared.receipt_command import with_receipt from app.db import VideoPresentation, VideoPresentationStatus, shielded_async_session +logger = logging.getLogger(__name__) + def create_generate_video_presentation_tool( search_space_id: int, @@ -19,9 +36,10 @@ def create_generate_video_presentation_tool( @tool async def generate_video_presentation( source_content: str, + runtime: ToolRuntime, video_title: str = "SurfSense Presentation", user_prompt: str | None = None, - ) -> dict[str, Any]: + ) -> Command: """Generate a video presentation from the provided content. Use this tool when the user asks to create a video, presentation, slides, or slide deck. @@ -56,25 +74,103 @@ def create_generate_video_presentation_tool( user_prompt=user_prompt, ) - print( - f"[generate_video_presentation] Created video presentation {video_pres_id}, task: {task.id}" + logger.info( + "[generate_video_presentation] Created video presentation %s, task: %s", + video_pres_id, + task.id, ) - return { - "status": VideoPresentationStatus.PENDING.value, + # Wait until the Celery worker flips the row to a terminal + # state. The wait is bounded only by the subagent invoke + # timeout (multi-agent) or HTTP lifetime (single-agent) — + # see app.agents.shared.deliverable_wait for details. + terminal_status, _columns, elapsed = await wait_for_deliverable( + model=VideoPresentation, + row_id=video_pres_id, + columns=[VideoPresentation.status], + terminal_statuses={ + VideoPresentationStatus.READY, + VideoPresentationStatus.FAILED, + }, + ) + + if terminal_status == VideoPresentationStatus.READY: + logger.info( + "[generate_video_presentation] %s READY in %.2fs", + video_pres_id, + elapsed, + ) + payload: dict[str, Any] = { + "status": VideoPresentationStatus.READY.value, + "video_presentation_id": video_pres_id, + "title": video_title, + "message": "Video presentation generated and saved.", + } + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="video_presentation", + operation="generate", + status="success", + external_id=str(video_pres_id), + preview=video_title, + ), + tool_call_id=runtime.tool_call_id, + ) + + # Only other terminal state is FAILED. + logger.warning( + "[generate_video_presentation] %s FAILED in %.2fs", + video_pres_id, + elapsed, + ) + err = ( + "Background worker reported FAILED status for this " + "video presentation." + ) + payload = { + "status": VideoPresentationStatus.FAILED.value, "video_presentation_id": video_pres_id, "title": video_title, - "message": "Video presentation generation started. This may take a few minutes.", + "error": err, } + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="video_presentation", + operation="generate", + status="failed", + external_id=str(video_pres_id), + preview=video_title, + error=err, + ), + tool_call_id=runtime.tool_call_id, + ) except Exception as e: error_message = str(e) - print(f"[generate_video_presentation] Error: {error_message}") - return { + logger.exception( + "[generate_video_presentation] Error: %s", error_message + ) + payload = { "status": VideoPresentationStatus.FAILED.value, "error": error_message, "title": video_title, "video_presentation_id": None, } + return with_receipt( + payload=payload, + receipt=make_receipt( + route="deliverables", + type="video_presentation", + operation="generate", + status="failed", + preview=video_title, + error=error_message, + ), + tool_call_id=runtime.tool_call_id, + ) return generate_video_presentation diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_cloud.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_cloud.md index 2ae21c271..c4e36fc73 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_cloud.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_cloud.md @@ -150,11 +150,12 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: +<include snippet="output_contract_base"/> + +Route-specific rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. - `evidence.content_excerpt`: max ~500 characters. Surface a short excerpt or a one-sentence summary, not the full file body. The supervisor already sees the tool's raw output. +<include snippet="verifiable_handle"/> + Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md index b0f2dacb2..25dafa3df 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/knowledge_base/system_prompt_desktop.md @@ -117,11 +117,12 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: +<include snippet="output_contract_base"/> + +Route-specific rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. - `evidence.content_excerpt`: max ~500 characters. Surface a short excerpt or a one-sentence summary, not the full file body. The supervisor already sees the tool's raw output. +<include snippet="verifiable_handle"/> + Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/memory/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/memory/system_prompt.md index 13f7b68a5..b656c5019 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/memory/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/memory/system_prompt.md @@ -6,7 +6,7 @@ Persist durable preferences/facts/instructions with `update_memory` while avoidi </goal> <visibility_scope> -{{MEMORY_VISIBILITY_POLICY}} +Memory is search-space-scoped; do not assume cross-workspace visibility. </visibility_scope> <available_tools> @@ -53,10 +53,8 @@ Return **only** one JSON object (no markdown/prose): "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` -> `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` -> `next_step` must be non-null. -- `status=blocked` due to missing required inputs -> `missing_fields` must be non-null. +<include snippet="output_contract_base"/> +Route-specific rules: - `evidence.memory_category` is a semantic classification for supervisor logs only. It is not the persisted storage format and must not force inline `[fact|preference|instruction]` markers into saved memory. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md index f1a22ddf1..3eabd8ee0 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md @@ -46,10 +46,8 @@ Return **only** one JSON object (no markdown/prose): "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` -> `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` -> `next_step` must be non-null. -- `status=blocked` due to missing required inputs -> `missing_fields` must be non-null. +<include snippet="output_contract_base"/> +Route-specific rules: - `evidence.findings`: max 10 entries, each a single sentence stating one distinct fact. Do not paste raw paragraphs, scraped pages, or quote blocks. - `evidence.sources`: max 10 URLs, one per finding when applicable. List each URL once. </output_contract> diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/airtable/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/airtable/system_prompt.md index 9434db7a1..e6a639af3 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/airtable/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/airtable/system_prompt.md @@ -92,12 +92,12 @@ Return **only** one JSON object (no markdown, no prose): "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> +Route-specific rules: - For blocked ambiguity, populate `evidence.matched_candidates` with up to 5 options (`id` + `label` — works for any kind of candidate: base, table, field, choice, record, etc.). - For discovery-only queries (lists), set `evidence.items` to `{ "total": N }` and list the matched items in `action_summary` (record id, primary-field value, and 1-2 most relevant fields; up to 10 entries, then `"...and N more"`). </output_contract> +<include snippet="verifiable_handle"/> + Discover before you mutate; never guess identifiers, choice IDs, or required fields. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/calendar/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/calendar/system_prompt.md index a663f5b37..9168f4d2b 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/calendar/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/calendar/system_prompt.md @@ -111,11 +111,12 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +Route-specific rules: - For `search_calendar_events` results, set `evidence.items` to `{ "total": N }` and list the matched events in `action_summary` (title, date, start time; up to 10 entries, then `"...and N more"`). - For ambiguous matches across `update_calendar_event` / `delete_calendar_event`, populate `evidence.matched_candidates` with up to 5 options (`id` + `label`, where `label` should include the event title and start time for human readability). +<include snippet="verifiable_handle"/> + Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/clickup/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/clickup/system_prompt.md index 898197f14..029609670 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/clickup/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/clickup/system_prompt.md @@ -93,12 +93,12 @@ Return **only** one JSON object (no markdown, no prose): "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> +Route-specific rules: - For blocked ambiguity, populate `evidence.matched_candidates` with up to 5 options (`id` + `label` — works for any kind of candidate: task, list, member, status, custom-field choice, etc.). - For discovery-only queries (lists), set `evidence.items` to `{ "total": N }` and list the matched items in `action_summary` (task id, title, status, assignees; up to 10 entries, then `"...and N more"`). </output_contract> +<include snippet="verifiable_handle"/> + Discover before you mutate; never guess identifiers, list statuses, or assignees. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/confluence/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/confluence/system_prompt.md index 991ec3d03..5aa687cd0 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/confluence/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/confluence/system_prompt.md @@ -100,9 +100,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/discord/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/discord/system_prompt.md index 249f9ec8b..aaabd2ac3 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/discord/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/discord/system_prompt.md @@ -108,9 +108,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Resolve before you call; verify before you send; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/dropbox/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/dropbox/system_prompt.md index a963b0ec6..8e498dfdf 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/dropbox/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/dropbox/system_prompt.md @@ -98,9 +98,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/system_prompt.md index c04d69ad0..02aff5589 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/system_prompt.md @@ -110,11 +110,12 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +Route-specific rules: - For `search_gmail` results, set `evidence.items` to `{ "total": N }` and list the matched emails in `action_summary` (sender, subject, date; up to 10 entries, then `"...and N more"`). - For ambiguous matches across `update_gmail_draft` / `trash_gmail_email` / `read_gmail_email`, populate `evidence.matched_candidates` with up to 5 options (`id` + `label`). +<include snippet="verifiable_handle"/> + Infer before you call; verify before you send; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/tools/send_email.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/tools/send_email.py index 578233b57..0680e51cb 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/tools/send_email.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/gmail/tools/send_email.py @@ -5,12 +5,16 @@ from datetime import datetime from email.mime.text import MIMEText from typing import Any +from langchain.tools import ToolRuntime from langchain_core.tools import tool +from langgraph.types import Command from sqlalchemy.ext.asyncio import AsyncSession from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import ( request_approval, ) +from app.agents.shared.receipt import make_receipt +from app.agents.shared.receipt_command import with_receipt from app.services.gmail import GmailToolMetadataService logger = logging.getLogger(__name__) @@ -26,9 +30,10 @@ def create_send_gmail_email_tool( to: str, subject: str, body: str, + runtime: ToolRuntime, cc: str | None = None, bcc: str | None = None, - ) -> dict[str, Any]: + ) -> Command: """Send an email via Gmail. Use when the user explicitly asks to send an email. This sends the @@ -60,11 +65,34 @@ def create_send_gmail_email_tool( """ logger.info(f"send_gmail_email called: to='{to}', subject='{subject}'") + def _emit( + payload: dict[str, Any], + *, + success: bool, + external_id: str | None = None, + error: str | None = None, + ) -> Command: + return with_receipt( + payload=payload, + receipt=make_receipt( + route="gmail", + type="message", + operation="send", + status="success" if success else "failed", + external_id=external_id, + preview=f"to={to}: {subject}"[:200], + error=error, + ), + tool_call_id=runtime.tool_call_id, + ) + if db_session is None or search_space_id is None or user_id is None: - return { - "status": "error", - "message": "Gmail tool not properly configured. Please contact support.", - } + msg = "Gmail tool not properly configured. Please contact support." + return _emit( + {"status": "error", "message": msg}, + success=False, + error=msg, + ) try: metadata_service = GmailToolMetadataService(db_session) @@ -74,16 +102,24 @@ def create_send_gmail_email_tool( if "error" in context: logger.error(f"Failed to fetch creation context: {context['error']}") - return {"status": "error", "message": context["error"]} + return _emit( + {"status": "error", "message": context["error"]}, + success=False, + error=context["error"], + ) accounts = context.get("accounts", []) if accounts and all(a.get("auth_expired") for a in accounts): logger.warning("All Gmail accounts have expired authentication") - return { - "status": "auth_error", - "message": "All connected Gmail accounts need re-authentication. Please re-authenticate in your connector settings.", - "connector_type": "gmail", - } + return _emit( + { + "status": "auth_error", + "message": "All connected Gmail accounts need re-authentication. Please re-authenticate in your connector settings.", + "connector_type": "gmail", + }, + success=False, + error="auth_expired", + ) logger.info( f"Requesting approval for sending Gmail email: to='{to}', subject='{subject}'" @@ -103,10 +139,14 @@ def create_send_gmail_email_tool( ) if result.rejected: - return { - "status": "rejected", - "message": "User declined. The email was not sent. Do not ask again or suggest alternatives.", - } + return _emit( + { + "status": "rejected", + "message": "User declined. The email was not sent. Do not ask again or suggest alternatives.", + }, + success=False, + error="user_rejected", + ) final_to = result.params.get("to", to) final_subject = result.params.get("subject", subject) @@ -135,10 +175,14 @@ def create_send_gmail_email_tool( ) connector = result.scalars().first() if not connector: - return { - "status": "error", - "message": "Selected Gmail connector is invalid or has been disconnected.", - } + msg = ( + "Selected Gmail connector is invalid or has been disconnected." + ) + return _emit( + {"status": "error", "message": msg}, + success=False, + error=msg, + ) actual_connector_id = connector.id else: result = await db_session.execute( @@ -150,10 +194,12 @@ def create_send_gmail_email_tool( ) connector = result.scalars().first() if not connector: - return { - "status": "error", - "message": "No Gmail connector found. Please connect Gmail in your workspace settings.", - } + msg = "No Gmail connector found. Please connect Gmail in your workspace settings." + return _emit( + {"status": "error", "message": msg}, + success=False, + error=msg, + ) actual_connector_id = connector.id logger.info( @@ -166,10 +212,12 @@ def create_send_gmail_email_tool( ): cca_id = connector.config.get("composio_connected_account_id") if not cca_id: - return { - "status": "error", - "message": "Composio connected account ID not found for this Gmail connector.", - } + msg = "Composio connected account ID not found for this Gmail connector." + return _emit( + {"status": "error", "message": msg}, + success=False, + error=msg, + ) from app.services.composio_service import ComposioService @@ -187,7 +235,11 @@ def create_send_gmail_email_tool( bcc=final_bcc, ) if error: - return {"status": "error", "message": error} + return _emit( + {"status": "error", "message": error}, + success=False, + error=error, + ) sent = {"id": sent_message_id, "threadId": sent_thread_id} else: from google.oauth2.credentials import Credentials @@ -275,11 +327,15 @@ def create_send_gmail_email_tool( actual_connector_id, exc_info=True, ) - return { - "status": "insufficient_permissions", - "connector_id": actual_connector_id, - "message": "This Gmail account needs additional permissions. Please re-authenticate in connector settings.", - } + return _emit( + { + "status": "insufficient_permissions", + "connector_id": actual_connector_id, + "message": "This Gmail account needs additional permissions. Please re-authenticate in connector settings.", + }, + success=False, + error="insufficient_permissions", + ) raise logger.info( @@ -310,12 +366,16 @@ def create_send_gmail_email_tool( logger.warning(f"KB sync after send failed: {kb_err}") kb_message_suffix = " This email will be added to your knowledge base in the next scheduled sync." - return { - "status": "success", - "message_id": sent.get("id"), - "thread_id": sent.get("threadId"), - "message": f"Successfully sent email to '{final_to}' with subject '{final_subject}'.{kb_message_suffix}", - } + return _emit( + { + "status": "success", + "message_id": sent.get("id"), + "thread_id": sent.get("threadId"), + "message": f"Successfully sent email to '{final_to}' with subject '{final_subject}'.{kb_message_suffix}", + }, + success=True, + external_id=sent.get("id"), + ) except Exception as e: from langgraph.errors import GraphInterrupt @@ -324,9 +384,11 @@ def create_send_gmail_email_tool( raise logger.error(f"Error sending Gmail email: {e}", exc_info=True) - return { - "status": "error", - "message": "Something went wrong while sending the email. Please try again.", - } + msg = "Something went wrong while sending the email. Please try again." + return _emit( + {"status": "error", "message": msg}, + success=False, + error=str(e), + ) return send_gmail_email diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/google_drive/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/google_drive/system_prompt.md index b78e1f7c6..10140d842 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/google_drive/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/google_drive/system_prompt.md @@ -100,9 +100,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/jira/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/jira/system_prompt.md index 4dcc56454..d7816dead 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/jira/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/jira/system_prompt.md @@ -111,12 +111,12 @@ Return **only** one JSON object (no markdown, no prose): "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> +Route-specific rules: - For blocked ambiguity, populate `evidence.matched_candidates` with up to 5 options (`id` + `label` — works for any kind of candidate: site, project, issue, user, transition, etc.). - For discovery-only queries (lists), set `evidence.items` to `{ "total": N }` and list the matched items in `action_summary` (issue key, summary, status, assignee; up to 10 entries, then `"...and N more"`). </output_contract> +<include snippet="verifiable_handle"/> + Discover before you mutate; never guess identifiers, transitions, or required fields. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/linear/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/linear/system_prompt.md index 1d96a4105..5dfd29112 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/linear/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/linear/system_prompt.md @@ -101,12 +101,12 @@ Return **only** one JSON object (no markdown, no prose): "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> +Route-specific rules: - For blocked ambiguity, populate `evidence.matched_candidates` with up to 5 options (`id` + `label` — works for any kind of candidate: issue, user, project, state, etc.). - For discovery-only queries (lists), set `evidence.items` to `{ "total": N }` and list the matched items in `action_summary` (identifier, title, state, assignee; up to 10 entries, then `"...and N more"`). </output_contract> +<include snippet="verifiable_handle"/> + Discover before you mutate; never guess identifiers. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/luma/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/luma/system_prompt.md index 0f42161b3..e483789d5 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/luma/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/luma/system_prompt.md @@ -101,9 +101,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Infer before you call; verify before you create; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/system_prompt.md index b38c30167..909c72471 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/system_prompt.md @@ -99,9 +99,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/tools/delete_page.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/tools/delete_page.py index 85d0ef22e..c98b25811 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/tools/delete_page.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/notion/tools/delete_page.py @@ -1,12 +1,16 @@ import logging from typing import Any +from langchain.tools import ToolRuntime from langchain_core.tools import tool +from langgraph.types import Command from sqlalchemy.ext.asyncio import AsyncSession from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import ( request_approval, ) +from app.agents.shared.receipt import make_receipt +from app.agents.shared.receipt_command import with_receipt from app.connectors.notion_history import NotionAPIError, NotionHistoryConnector from app.services.notion.tool_metadata_service import NotionToolMetadataService @@ -35,8 +39,9 @@ def create_delete_notion_page_tool( @tool async def delete_notion_page( page_title: str, + runtime: ToolRuntime, delete_from_kb: bool = False, - ) -> dict[str, Any]: + ) -> Command: """Delete (archive) a Notion page. Use this tool when the user asks you to delete, remove, or archive @@ -65,14 +70,39 @@ def create_delete_notion_page_tool( f"delete_notion_page called: page_title='{page_title}', delete_from_kb={delete_from_kb}" ) + def _emit( + payload: dict[str, Any], + *, + status: str, + external_id: str | None = None, + error: str | None = None, + ) -> Command: + return with_receipt( + payload=payload, + receipt=make_receipt( + route="notion", + type="page", + operation="delete", + status="success" if status == "success" else "failed", + external_id=external_id, + preview=page_title, + error=error, + ), + tool_call_id=runtime.tool_call_id, + ) + if db_session is None or search_space_id is None or user_id is None: logger.error( "Notion tool not properly configured - missing required parameters" ) - return { - "status": "error", - "message": "Notion tool not properly configured. Please contact support.", - } + return _emit( + { + "status": "error", + "message": "Notion tool not properly configured. Please contact support.", + }, + status="error", + error="Notion tool not properly configured. Please contact support.", + ) try: # Get page context (page_id, account, title) from indexed data @@ -86,16 +116,18 @@ def create_delete_notion_page_tool( # Check if it's a "not found" error (softer handling for LLM) if "not found" in error_msg.lower(): logger.warning(f"Page not found: {error_msg}") - return { - "status": "not_found", - "message": error_msg, - } + return _emit( + {"status": "not_found", "message": error_msg}, + status="error", + error=error_msg, + ) else: logger.error(f"Failed to fetch delete context: {error_msg}") - return { - "status": "error", - "message": error_msg, - } + return _emit( + {"status": "error", "message": error_msg}, + status="error", + error=error_msg, + ) account = context.get("account", {}) if account.get("auth_expired"): @@ -103,10 +135,14 @@ def create_delete_notion_page_tool( "Notion account %s has expired authentication", account.get("id"), ) - return { - "status": "auth_error", - "message": "The Notion account for this page needs re-authentication. Please re-authenticate in your connector settings.", - } + return _emit( + { + "status": "auth_error", + "message": "The Notion account for this page needs re-authentication. Please re-authenticate in your connector settings.", + }, + status="error", + error="auth_expired", + ) page_id = context.get("page_id") connector_id_from_context = account.get("id") @@ -129,10 +165,14 @@ def create_delete_notion_page_tool( if result.rejected: logger.info("Notion page deletion rejected by user") - return { - "status": "rejected", - "message": "User declined. Do not retry or suggest alternatives.", - } + return _emit( + { + "status": "rejected", + "message": "User declined. Do not retry or suggest alternatives.", + }, + status="error", + error="user_rejected", + ) final_page_id = result.params.get("page_id", page_id) final_connector_id = result.params.get( @@ -165,18 +205,26 @@ def create_delete_notion_page_tool( logger.error( f"Invalid connector_id={final_connector_id} for search_space_id={search_space_id}" ) - return { - "status": "error", - "message": "Selected Notion account is invalid or has been disconnected. Please select a valid account.", - } + return _emit( + { + "status": "error", + "message": "Selected Notion account is invalid or has been disconnected. Please select a valid account.", + }, + status="error", + error="invalid_connector", + ) actual_connector_id = connector.id logger.info(f"Validated Notion connector: id={actual_connector_id}") else: logger.error("No connector found for this page") - return { - "status": "error", - "message": "No connector found for this page.", - } + return _emit( + { + "status": "error", + "message": "No connector found for this page.", + }, + status="error", + error="no_connector", + ) # Create connector instance notion_connector = NotionHistoryConnector( @@ -232,7 +280,13 @@ def create_delete_notion_page_tool( f"{result.get('message', '')} (also removed from knowledge base)" ) - return result + status = result.get("status", "error") + return _emit( + result, + status=status, + external_id=str(final_page_id) if final_page_id else None, + error=None if status == "success" else result.get("message"), + ) except Exception as e: from langgraph.errors import GraphInterrupt @@ -245,20 +299,28 @@ def create_delete_notion_page_tool( if isinstance(e, NotionAPIError) and ( "401" in error_str or "unauthorized" in error_str ): - return { - "status": "auth_error", - "message": str(e), - "connector_id": connector_id_from_context - if "connector_id_from_context" in dir() - else None, - "connector_type": "notion", - } + return _emit( + { + "status": "auth_error", + "message": str(e), + "connector_id": connector_id_from_context + if "connector_id_from_context" in dir() + else None, + "connector_type": "notion", + }, + status="error", + error=str(e), + ) if isinstance(e, ValueError | NotionAPIError): message = str(e) else: message = ( "Something went wrong while deleting the page. Please try again." ) - return {"status": "error", "message": message} + return _emit( + {"status": "error", "message": message}, + status="error", + error=message, + ) return delete_notion_page diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/onedrive/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/onedrive/system_prompt.md index 8ae444a58..4b45b05a9 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/onedrive/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/onedrive/system_prompt.md @@ -97,9 +97,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Infer before you call; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/slack/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/slack/system_prompt.md index 3c24b19c9..e4e0d1f6f 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/slack/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/slack/system_prompt.md @@ -87,12 +87,12 @@ Return **only** one JSON object (no markdown, no prose): "missing_fields": string[] | null, "assumptions": string[] | null } -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> +Route-specific rules: - For blocked ambiguity, populate `evidence.matched_candidates` with up to 5 options (`id` + `label` — works for any kind of candidate: channel, user, message, thread). - For discovery-only queries (lists), set `evidence.items` to `{ "total": N }` and list the matched items in `action_summary` (channel/user, key identifier, timestamp, short snippet; up to 10 entries, then `"...and N more"`). </output_contract> +<include snippet="verifiable_handle"/> + Discover before you post; never guess channel, user, or thread targets. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/teams/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/teams/system_prompt.md index c3a280f79..9b283acf5 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/teams/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/connectors/teams/system_prompt.md @@ -115,9 +115,8 @@ Return **only** one JSON object (no markdown or prose outside it): } ``` -Rules: -- `status=success` → `next_step=null`, `missing_fields=null`. -- `status=partial|blocked|error` → `next_step` must be non-null. -- `status=blocked` due to missing required inputs → `missing_fields` must be non-null. +<include snippet="output_contract_base"/> + +<include snippet="verifiable_handle"/> Resolve before you call; verify before you send; map every tool outcome faithfully. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/md_file_reader.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/md_file_reader.py index 2fce413a6..5694e4326 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/md_file_reader.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/md_file_reader.py @@ -2,8 +2,11 @@ from __future__ import annotations +from functools import lru_cache from importlib import resources +_SHARED_SNIPPETS_PACKAGE = "app.agents.multi_agent_chat.subagents.shared.snippets" + def read_md_file(package: str, stem: str) -> str: """Load ``{stem}.md`` from ``package`` via importlib resources, or return empty.""" @@ -12,3 +15,13 @@ def read_md_file(package: str, stem: str) -> str: return "" text = ref.read_text(encoding="utf-8") return text.rstrip("\n") + + +@lru_cache(maxsize=64) +def read_shared_snippet(name: str) -> str: + """Load a shared markdown snippet from the snippets package. + + Cached because snippets are static at runtime and resolved many times + (once per subagent build, plus per-subagent-per-route). + """ + return read_md_file(_SHARED_SNIPPETS_PACKAGE, name) diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/__init__.py new file mode 100644 index 000000000..802a8e241 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/__init__.py @@ -0,0 +1,6 @@ +"""Shared markdown snippets composed into every subagent system prompt. + +Resolved at build time by :func:`pack_subagent` in ``subagent_builder.py`` +via the ``<include snippet="NAME"/>`` directive. See ``output_contract_base.md`` +and ``verifiable_handle.md`` for the included content. +""" diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/output_contract_base.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/output_contract_base.md new file mode 100644 index 000000000..100daae75 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/output_contract_base.md @@ -0,0 +1,6 @@ +Rules (universal): +- `status=success` -> `next_step=null`, `missing_fields=null`. +- `status=partial|blocked|error` -> `next_step` must be non-null. +- `status=blocked` due to missing required inputs -> `missing_fields` must be non-null. +- `assumptions`: any inferences you made about the user's intent; `null` when no inferences were needed. +- The `evidence` object's fields are documented in your route-specific `<output_contract>` above; never invent fields the tool did not return. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/verifiable_handle.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/verifiable_handle.md new file mode 100644 index 000000000..bea070ce9 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/snippets/verifiable_handle.md @@ -0,0 +1,10 @@ +<verifiable_handle> +Mutating tools you call return a structured `Receipt` object alongside their normal payload (see `evidence.receipts` in your `<output_contract>`). The supervisor uses the Receipt's `verifiable_url` and `external_id` to independently confirm the operation succeeded - do not paraphrase, shorten, or guess these values. + +Rules: +- Quote each Receipt's `verifiable_url` and `external_id` **verbatim** in `evidence.receipts`. Copy character-for-character; never retype from memory. +- If a Receipt has `status="failed"`, set your own `status="error"` and put the Receipt's `error` field in `next_step`. +- If a Receipt has `status="pending"` (async backends — podcasts, video presentations, anything queued through Celery), report `status=success`, surface the pending Receipt as-is, and tell the supervisor in `action_summary` that the artefact is **being generated in the background** (e.g. "Podcast 38 queued; orchestrator should report it as kicked off, not yet ready"). A pending Receipt almost always lacks `verifiable_url` because the artefact does not exist yet — that is expected, not a defect. Do **not** wait, poll, or retry; control returns to the supervisor immediately and the asset becomes visible to the user out of band via its own UI surface. +- Never claim a mutation succeeded without a matching Receipt with `status="success"` or `"pending"` in your tool results this turn. +- For tools that do not return a Receipt (read-only operations, search, lookup), the receipt rules do not apply; only the route-specific `evidence` fields matter. +</verifiable_handle> diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/spec.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/spec.py index 797ab535b..f891f94d2 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/spec.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/spec.py @@ -2,12 +2,30 @@ from __future__ import annotations +from collections.abc import Callable, Mapping from dataclasses import dataclass +from typing import Any from deepagents import SubAgent from app.agents.new_chat.permissions import Ruleset +# A context-hint provider receives the parent-agent ``runtime.state`` mapping +# and the ``description`` the orchestrator wrote, and returns a short string +# the runtime prepends to the subagent's first ``HumanMessage``. Used for +# things like "current search-space id is X" or "the user is in workspace Y" — +# never for full corpora, since the prepended text consumes the subagent's +# prompt budget on every invocation. Return ``None`` (or an empty string) to +# skip the hint for this call. +ContextHintProvider = Callable[[Mapping[str, Any], str], str | None] + +# Custom key stashed on the deepagents ``SubAgent`` dict so the provider +# survives the trip from ``pack_subagent`` → registry → middleware → +# task_tool. ``deepagents.create_agent`` only extracts the keys it +# recognises, so an extra key here is dropped silently at compile time. +# The prefix avoids any collision with future deepagents fields. +SURF_CONTEXT_HINT_PROVIDER_KEY = "surf_context_hint_provider" + @dataclass(frozen=True, slots=True) class SurfSenseSubagentSpec: @@ -20,10 +38,22 @@ class SurfSenseSubagentSpec: layers them into the subagent's :class:`PermissionMiddleware`, so each subagent owns its own ruleset without aliasing the shared rule engine. + context_hint_provider: Optional callback invoked once per ``task(...)`` + invocation, immediately before the subagent runs. Its return + value is prepended to the subagent's first ``HumanMessage`` so + the subagent can see things it would otherwise have to discover + (active search space, KB root, current user timezone, etc.). + Kept out of the deepagents ``spec`` because that dict is forwarded + verbatim to upstream code and only recognises its own typed keys. """ spec: SubAgent ruleset: Ruleset + context_hint_provider: ContextHintProvider | None = None -__all__ = ["SurfSenseSubagentSpec"] +__all__ = [ + "SURF_CONTEXT_HINT_PROVIDER_KEY", + "ContextHintProvider", + "SurfSenseSubagentSpec", +] diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/subagent_builder.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/subagent_builder.py index 7173901f9..5025b32e7 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/subagent_builder.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/subagent_builder.py @@ -2,6 +2,8 @@ from __future__ import annotations +import logging +import re from typing import Any, cast from deepagents import SubAgent @@ -12,9 +14,48 @@ from langchain_core.tools import BaseTool from app.agents.multi_agent_chat.middleware.shared.permissions import ( build_permission_mw, ) -from app.agents.multi_agent_chat.subagents.shared.spec import SurfSenseSubagentSpec +from app.agents.multi_agent_chat.subagents.shared.md_file_reader import ( + read_shared_snippet, +) +from app.agents.multi_agent_chat.subagents.shared.spec import ( + SURF_CONTEXT_HINT_PROVIDER_KEY, + ContextHintProvider, + SurfSenseSubagentSpec, +) from app.agents.new_chat.permissions import Ruleset +logger = logging.getLogger(__name__) + +# ``<include snippet="NAME"/>`` directive. Matches an XML-style self-closing +# tag whose ``snippet`` attribute names a file in ``shared/snippets/``. +# Whitespace around the attribute and self-close is tolerated; the snippet +# name itself must be a bare identifier (letters / digits / underscores) so +# we never pull a path-traversal value into ``read_shared_snippet``. +_INCLUDE_DIRECTIVE_RE = re.compile( + r"<include\s+snippet=\"(?P<name>[A-Za-z0-9_]+)\"\s*/>" +) + + +def _resolve_includes(prompt: str, *, subagent_name: str) -> str: + """Replace ``<include snippet="X"/>`` directives with the snippet body. + + Unknown snippet names raise; an empty body is treated as unknown so a + typo or missing file fails loudly at startup instead of silently + shipping a broken prompt to the LLM. + """ + + def _replace(match: re.Match[str]) -> str: + name = match.group("name") + body = read_shared_snippet(name) + if not body.strip(): + raise ValueError( + f"Subagent {subagent_name!r}: unknown or empty shared " + f"snippet {name!r} referenced via <include>." + ) + return body + + return _INCLUDE_DIRECTIVE_RE.sub(_replace, prompt) + def _user_allowlist_for( dependencies: dict[str, Any], subagent_name: str @@ -43,6 +84,7 @@ def pack_subagent( dependencies: dict[str, Any], model: BaseChatModel | None = None, middleware_stack: dict[str, Any] | None = None, + context_hint_provider: ContextHintProvider | None = None, ) -> SurfSenseSubagentSpec: """Pack the route-local pieces into one sub-agent spec + its Ruleset. @@ -68,6 +110,8 @@ def pack_subagent( msg = f"Subagent {name!r}: system_prompt is empty" raise ValueError(msg) + system_prompt = _resolve_includes(system_prompt, subagent_name=name) + flags = dependencies["flags"] user_allowlist = _user_allowlist_for(dependencies, name) subagent_rulesets: list[Ruleset] = [ruleset] @@ -99,4 +143,12 @@ def pack_subagent( } if model is not None: spec_dict["model"] = model - return SurfSenseSubagentSpec(spec=cast(SubAgent, spec_dict), ruleset=ruleset) + if context_hint_provider is not None: + # Stash the callback on the dict so it survives the trip through + # registry / middleware unpacking (both treat the spec as opaque). + spec_dict[SURF_CONTEXT_HINT_PROVIDER_KEY] = context_hint_provider + return SurfSenseSubagentSpec( + spec=cast(SubAgent, spec_dict), + ruleset=ruleset, + context_hint_provider=context_hint_provider, + ) diff --git a/surfsense_backend/app/agents/new_chat/filesystem_state.py b/surfsense_backend/app/agents/new_chat/filesystem_state.py index cc674be76..de2c94b41 100644 --- a/surfsense_backend/app/agents/new_chat/filesystem_state.py +++ b/surfsense_backend/app/agents/new_chat/filesystem_state.py @@ -33,9 +33,11 @@ from typing_extensions import TypedDict from app.agents.new_chat.state_reducers import ( _add_unique_reducer, _dict_merge_with_tombstones_reducer, + _int_counter_merge_reducer, _list_append_reducer, _replace_reducer, ) +from app.agents.shared.receipt import Receipt class PendingMove(TypedDict, total=False): @@ -172,6 +174,35 @@ class SurfSenseFilesystemState(FilesystemState): workspace_tree_text: NotRequired[Annotated[str, _replace_reducer]] """Pre-rendered ``<workspace_tree>`` body; shared with subagents to skip re-render.""" + billable_calls: NotRequired[Annotated[dict[str, int], _int_counter_merge_reducer]] + """Per-subagent ``task(...)`` invocation counter, summed across the turn. + + Incremented by ``task_tool.py`` each time a subagent invocation + completes (single- or batch-mode). The orchestrator can read this map + to self-limit when a runaway loop sends the same specialist 20 calls + in a row; the runtime emits a soft warning ToolMessage once the + cumulative count crosses :data:`DEFAULT_SUBAGENT_BILLABLE_THRESHOLD`. + Cleared by checkpoint rollover (i.e. per turn). + """ + + receipts: NotRequired[Annotated[list[Receipt], _list_append_reducer]] + """Structured Receipt handles emitted by mutating subagent tools this turn. + + Each mutating tool (deliverables, every connector, KB writes via the + persistence middleware) wraps its native return into a + :class:`~app.agents.shared.receipt.Receipt` + and returns it under the ``"receipt"`` key alongside its existing + payload. The subagent's tool-call middleware folds the receipt into + this list, and ``_return_command_with_state_update`` in + ``checkpointed_subagent_middleware/task_tool.py`` carries the list up + to the parent automatically (``"receipts"`` is not in + ``EXCLUDED_STATE_KEYS``). + + Append-only across the turn; cleared by checkpoint rollover. The + orchestrator reads it via the ``<verification>`` teaching to confirm + side-effecting subagent claims (see ``shared/snippets/verifiable_handle.md``). + """ + __all__ = [ "KbAnonDoc", diff --git a/surfsense_backend/app/agents/new_chat/middleware/compaction.py b/surfsense_backend/app/agents/new_chat/middleware/compaction.py index 8173976fe..f8d340e5d 100644 --- a/surfsense_backend/app/agents/new_chat/middleware/compaction.py +++ b/surfsense_backend/app/agents/new_chat/middleware/compaction.py @@ -34,8 +34,7 @@ from deepagents.middleware.summarization import ( ) from langchain_core.messages import SystemMessage -from app.observability import metrics as ot_metrics -from app.observability import otel as ot +from app.observability import metrics as ot_metrics, otel as ot if TYPE_CHECKING: from deepagents.backends.protocol import BACKEND_TYPES diff --git a/surfsense_backend/app/agents/new_chat/middleware/doom_loop.py b/surfsense_backend/app/agents/new_chat/middleware/doom_loop.py index d50cadc0e..a7901c010 100644 --- a/surfsense_backend/app/agents/new_chat/middleware/doom_loop.py +++ b/surfsense_backend/app/agents/new_chat/middleware/doom_loop.py @@ -47,8 +47,7 @@ from langgraph.config import get_config from langgraph.runtime import Runtime from langgraph.types import interrupt -from app.observability import metrics as ot_metrics -from app.observability import otel as ot +from app.observability import metrics as ot_metrics, otel as ot logger = logging.getLogger(__name__) diff --git a/surfsense_backend/app/agents/new_chat/middleware/kb_persistence.py b/surfsense_backend/app/agents/new_chat/middleware/kb_persistence.py index cc30f4897..c88dced85 100644 --- a/surfsense_backend/app/agents/new_chat/middleware/kb_persistence.py +++ b/surfsense_backend/app/agents/new_chat/middleware/kb_persistence.py @@ -55,6 +55,7 @@ from app.agents.new_chat.path_resolver import ( virtual_path_to_doc, ) from app.agents.new_chat.state_reducers import _CLEAR +from app.agents.shared.receipt import Receipt, make_receipt from app.db import ( AgentActionLog, Chunk, @@ -1392,6 +1393,81 @@ async def commit_staged_filesystem_state( "pending_dir_deletes": [_CLEAR], "dirty_path_tool_calls": {_CLEAR: True}, } + + # Emit one Receipt per committed mutation, folded into ``state['receipts']`` + # via ``_list_append_reducer``. The receipts surface what actually committed + # (post-savepoint) rather than what the LLM intended; the orchestrator uses + # them as ground truth in the ``<verification>`` teaching. KB writes do not + # have public verifiable URLs, so ``verifiable_url`` stays unset. + receipts: list[Receipt] = [] + + def _kb_receipt( + *, + type: str, + operation: str, + path: str, + external_id: int | None = None, + ) -> None: + if not path: + return + preview = path.rsplit("/", 1)[-1] or path + receipts.append( + make_receipt( + route="knowledge_base", + type=type, + operation=operation, + status="success", + external_id=str(external_id) if external_id is not None else path, + preview=preview, + ) + ) + + for payload in committed_creates: + path = str(payload.get("virtualPath") or "") + _kb_receipt( + type="file", + operation="write_file", + path=path, + external_id=payload.get("id"), + ) + for payload in committed_updates: + path = str(payload.get("virtualPath") or "") + _kb_receipt( + type="file", + operation="edit_file", + path=path, + external_id=payload.get("id"), + ) + for payload in applied_moves: + # ``applied_moves`` rows carry the destination ``virtualPath`` because + # the move has already landed in the DB by the time we reach this code. + path = str(payload.get("virtualPath") or "") + _kb_receipt( + type="file", + operation="move_file", + path=path, + external_id=payload.get("id"), + ) + for path in staged_dirs: + _kb_receipt(type="folder", operation="mkdir", path=path) + for payload in committed_deletes: + path = str(payload.get("virtualPath") or "") + _kb_receipt( + type="file", + operation="rm", + path=path, + external_id=payload.get("id"), + ) + for payload in committed_folder_deletes: + path = str(payload.get("virtualPath") or "") + _kb_receipt( + type="folder", + operation="rmdir", + path=path, + external_id=payload.get("id"), + ) + if receipts: + delta["receipts"] = receipts files_delta: dict[str, Any] = {} if temp_paths: files_delta.update(dict.fromkeys(temp_paths)) diff --git a/surfsense_backend/app/agents/new_chat/middleware/permission.py b/surfsense_backend/app/agents/new_chat/middleware/permission.py index e174ab0bd..07549bedb 100644 --- a/surfsense_backend/app/agents/new_chat/middleware/permission.py +++ b/surfsense_backend/app/agents/new_chat/middleware/permission.py @@ -61,8 +61,7 @@ from app.agents.new_chat.permissions import ( aggregate_action, evaluate_many, ) -from app.observability import metrics as ot_metrics -from app.observability import otel as ot +from app.observability import metrics as ot_metrics, otel as ot logger = logging.getLogger(__name__) diff --git a/surfsense_backend/app/agents/new_chat/state_reducers.py b/surfsense_backend/app/agents/new_chat/state_reducers.py index 89fc86367..c7b7685f0 100644 --- a/surfsense_backend/app/agents/new_chat/state_reducers.py +++ b/surfsense_backend/app/agents/new_chat/state_reducers.py @@ -171,6 +171,39 @@ def _dict_merge_with_tombstones_reducer( return result +def _int_counter_merge_reducer( + left: dict[str, int] | None, + right: dict[str, int] | None, +) -> dict[str, int]: + """Merge ``right`` into ``left`` by **summing** per-key integer counters. + + Used for state fields that accumulate counts across multiple updates + within the same turn (e.g. per-subagent ``billable_calls``). Unknown + keys are added; existing keys are summed. ``_CLEAR`` sentinels reset + the accumulator the same way the other reducers do, so the orchestrator + can wipe the counter at end-of-turn if needed. + """ + if right is None: + return dict(left or {}) + + if _CLEAR in right or any(_is_clear(k) for k in right): + result: dict[str, int] = {} + for key, value in right.items(): + if _is_clear(key): + continue + if not isinstance(value, int): + continue + result[key] = result.get(key, 0) + value + return result + + base = dict(left or {}) + for key, value in right.items(): + if not isinstance(value, int): + continue + base[key] = base.get(key, 0) + value + return base + + def _initial_filesystem_state() -> dict[str, Any]: """Default empty values for SurfSense filesystem state fields. @@ -200,6 +233,7 @@ __all__ = [ "_add_unique_reducer", "_dict_merge_with_tombstones_reducer", "_initial_filesystem_state", + "_int_counter_merge_reducer", "_list_append_reducer", "_replace_reducer", ] diff --git a/surfsense_backend/app/agents/new_chat/tools/podcast.py b/surfsense_backend/app/agents/new_chat/tools/podcast.py index 2c9b7fa0c..36aecfe49 100644 --- a/surfsense_backend/app/agents/new_chat/tools/podcast.py +++ b/surfsense_backend/app/agents/new_chat/tools/podcast.py @@ -2,17 +2,23 @@ Podcast generation tool for the SurfSense agent. This module provides a factory function for creating the generate_podcast tool -that submits a Celery task for background podcast generation. The frontend -polls for completion and auto-updates when the podcast is ready. +that submits a Celery task for background podcast generation. The tool then +polls the podcast row until it reaches a terminal status (READY/FAILED) and +returns that status. The wait is bounded by the chat's HTTP / process +lifetime; see app.agents.shared.deliverable_wait for details. """ +import logging from typing import Any from langchain_core.tools import tool from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.shared.deliverable_wait import wait_for_deliverable from app.db import Podcast, PodcastStatus, shielded_async_session +logger = logging.getLogger(__name__) + def create_generate_podcast_tool( search_space_id: int, @@ -97,18 +103,57 @@ def create_generate_podcast_tool( user_prompt=user_prompt, ) - print(f"[generate_podcast] Created podcast {podcast_id}, task: {task.id}") + logger.info( + "[generate_podcast] Created podcast %s, task: %s", + podcast_id, + task.id, + ) + # Wait until the Celery worker flips the row to a terminal + # state. No internal budget — see deliverable_wait module. + terminal_status, columns, elapsed = await wait_for_deliverable( + model=Podcast, + row_id=podcast_id, + columns=[Podcast.status, Podcast.file_location], + terminal_statuses={PodcastStatus.READY, PodcastStatus.FAILED}, + ) + + if terminal_status == PodcastStatus.READY: + file_location = columns[1] if columns else None + logger.info( + "[generate_podcast] Podcast %s READY in %.2fs (file=%s)", + podcast_id, + elapsed, + file_location, + ) + return { + "status": PodcastStatus.READY.value, + "podcast_id": podcast_id, + "title": podcast_title, + "file_location": file_location, + "message": ( + "Podcast generated and saved to your podcast panel." + ), + } + + # Only other terminal state is FAILED. + logger.warning( + "[generate_podcast] Podcast %s FAILED in %.2fs", + podcast_id, + elapsed, + ) return { - "status": PodcastStatus.PENDING.value, + "status": PodcastStatus.FAILED.value, "podcast_id": podcast_id, "title": podcast_title, - "message": "Podcast generation started. This may take a few minutes.", + "error": ( + "Background worker reported FAILED status for this podcast." + ), } except Exception as e: error_message = str(e) - print(f"[generate_podcast] Error: {error_message}") + logger.exception("[generate_podcast] Error: %s", error_message) return { "status": PodcastStatus.FAILED.value, "error": error_message, diff --git a/surfsense_backend/app/agents/new_chat/tools/video_presentation.py b/surfsense_backend/app/agents/new_chat/tools/video_presentation.py index 7bf9a1c3b..4bf13b28e 100644 --- a/surfsense_backend/app/agents/new_chat/tools/video_presentation.py +++ b/surfsense_backend/app/agents/new_chat/tools/video_presentation.py @@ -2,17 +2,23 @@ Video presentation generation tool for the SurfSense agent. This module provides a factory function for creating the generate_video_presentation -tool that submits a Celery task for background video presentation generation. -The frontend polls for completion and auto-updates when the presentation is ready. +tool that submits a Celery task for background video presentation generation. The +tool then polls the row until it reaches a terminal status (READY/FAILED) and +returns that status. The wait is bounded by the chat's HTTP / process lifetime; +see app.agents.shared.deliverable_wait for details. """ +import logging from typing import Any from langchain_core.tools import tool from sqlalchemy.ext.asyncio import AsyncSession +from app.agents.shared.deliverable_wait import wait_for_deliverable from app.db import VideoPresentation, VideoPresentationStatus, shielded_async_session +logger = logging.getLogger(__name__) + def create_generate_video_presentation_tool( search_space_id: int, @@ -72,20 +78,58 @@ def create_generate_video_presentation_tool( user_prompt=user_prompt, ) - print( - f"[generate_video_presentation] Created video presentation {video_pres_id}, task: {task.id}" + logger.info( + "[generate_video_presentation] Created video presentation %s, task: %s", + video_pres_id, + task.id, ) + # Wait until the Celery worker flips the row to a terminal + # state. No internal budget — see deliverable_wait module. + terminal_status, _columns, elapsed = await wait_for_deliverable( + model=VideoPresentation, + row_id=video_pres_id, + columns=[VideoPresentation.status], + terminal_statuses={ + VideoPresentationStatus.READY, + VideoPresentationStatus.FAILED, + }, + ) + + if terminal_status == VideoPresentationStatus.READY: + logger.info( + "[generate_video_presentation] %s READY in %.2fs", + video_pres_id, + elapsed, + ) + return { + "status": VideoPresentationStatus.READY.value, + "video_presentation_id": video_pres_id, + "title": video_title, + "message": "Video presentation generated and saved.", + } + + # Only other terminal state is FAILED. + logger.warning( + "[generate_video_presentation] %s FAILED in %.2fs", + video_pres_id, + elapsed, + ) return { - "status": VideoPresentationStatus.PENDING.value, + "status": VideoPresentationStatus.FAILED.value, "video_presentation_id": video_pres_id, "title": video_title, - "message": "Video presentation generation started. This may take a few minutes.", + "error": ( + "Background worker reported FAILED status for this " + "video presentation." + ), } except Exception as e: error_message = str(e) - print(f"[generate_video_presentation] Error: {error_message}") + logger.exception( + "[generate_video_presentation] Error: %s", error_message + ) return { "status": VideoPresentationStatus.FAILED.value, "error": error_message, diff --git a/surfsense_backend/app/agents/shared/__init__.py b/surfsense_backend/app/agents/shared/__init__.py new file mode 100644 index 000000000..7c46c65ff --- /dev/null +++ b/surfsense_backend/app/agents/shared/__init__.py @@ -0,0 +1,9 @@ +"""Cross-package agent contracts. + +Symbols here are intentionally framework-light (no LangGraph / deepagents +internals) so they can be imported from both ``app.agents.new_chat`` and +``app.agents.multi_agent_chat`` without creating a circular dependency +between the two packages. See ``receipt.py`` for the rationale. +""" + +from __future__ import annotations diff --git a/surfsense_backend/app/agents/shared/deliverable_wait.py b/surfsense_backend/app/agents/shared/deliverable_wait.py new file mode 100644 index 000000000..abaa017ea --- /dev/null +++ b/surfsense_backend/app/agents/shared/deliverable_wait.py @@ -0,0 +1,123 @@ +"""Shared poll-until-terminal helper for Celery-backed deliverables. + +Lives in ``app.agents.shared`` (neutral package, no dependencies on either +``new_chat`` or ``multi_agent_chat``) so both the flat single-agent tools +under ``app/agents/new_chat/tools/`` and the multi-agent subagent tools +under ``app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/`` +can import it without creating a circular dependency. + +Background +---------- +Tools like ``generate_podcast`` and ``generate_video_presentation`` enqueue +the heavy work to Celery and historically returned immediately with a +"pending" status. That works for very-long deliverables but hurts UX for +the common case (most podcasts finish in 10-30 seconds): the agent sends +a "kicked off, check back in a minute" reply *before* the worker is done, +so the user never gets a "ready" confirmation. + +This helper bridges that gap. The tool dispatches the Celery task as +before, then polls the artefact row's ``status`` column **until it +reaches a terminal value** (READY / FAILED). The tool then returns a +real terminal outcome — never a pending one. + +No wall-clock budget here on purpose +------------------------------------ +Layering a second budget on top of the existing per-invocation safety +nets just confused the UX. The real ceilings are: + +* **Multi-agent mode** — ``SURFSENSE_SUBAGENT_INVOKE_TIMEOUT_SECONDS`` + (default ``300.0``, ``0`` to disable) caps how long any single + ``task(subagent, ...)`` invocation can run. If a deliverable needs + longer than this, the subagent invocation is cancelled and the + orchestrator surfaces a "subagent timed out" ToolMessage. Operators + who routinely generate long videos should raise that ceiling (or set + it to ``0`` for true unbounded waits). +* **Single-agent mode** — the chat's HTTP stream / process lifetime is + the only ceiling. Truly indefinite waits work here, but a dead Celery + worker will leave the row in PENDING/GENERATING forever; treat that + as an operational concern, not a UX concern. + +Configuration +------------- +None. The poll cadence is hardcoded at 1.5s — small enough to feel +responsive (~6 polls per typical 10s podcast), large enough to avoid +hammering the DB under burst traffic. Override at the call site if a +specific tool needs a different cadence. +""" + +from __future__ import annotations + +import asyncio +import logging +import time +from enum import Enum +from typing import Any + +from sqlalchemy import select +from sqlalchemy.orm import InstrumentedAttribute + +from app.db import shielded_async_session + +logger = logging.getLogger(__name__) + + +_DEFAULT_POLL_INTERVAL_SECONDS: float = 1.5 + + +async def wait_for_deliverable( + *, + model: type, + row_id: int, + columns: list[InstrumentedAttribute[Any]], + terminal_statuses: set[Enum], + poll_interval_s: float = _DEFAULT_POLL_INTERVAL_SECONDS, +) -> tuple[Enum, tuple[Any, ...], float]: + """Poll ``model`` row ``row_id`` until ``columns[0]`` reaches a terminal status. + + Blocks until the row's status column matches one of + ``terminal_statuses``. There is no internal wall-clock budget; cancel + from the outside (subagent timeout, HTTP disconnect, task + cancellation) if you need a ceiling. See module docstring. + + The first entry of ``columns`` must be the status column; additional + columns (e.g. ``Podcast.file_location``) are returned alongside the + final status so callers can build their payload without a second + roundtrip. + + A fresh ``shielded_async_session`` is opened per poll so we never + hold a transaction across the wait, and a failed poll is logged but + does not abort the wait — transient DB hiccups should not collapse + the tool call. + + Returns + ------- + ``(terminal_status, columns, elapsed_seconds)`` + ``columns`` mirrors the requested ``columns`` (including the + status itself in position 0). + """ + if not columns: + raise ValueError("wait_for_deliverable requires at least the status column") + + start = time.monotonic() + + while True: + await asyncio.sleep(poll_interval_s) + row: tuple[Any, ...] | None = None + try: + async with shielded_async_session() as session: + result = await session.execute( + select(*columns).where(model.id == row_id) + ) + row = result.first() + except Exception as exc: + logger.warning( + "[deliverable_wait] poll failed model=%s id=%s err=%r", + getattr(model, "__name__", str(model)), + row_id, + exc, + ) + + if row is not None: + status_val = row[0] + if status_val in terminal_statuses: + return status_val, tuple(row), time.monotonic() - start diff --git a/surfsense_backend/app/agents/shared/receipt.py b/surfsense_backend/app/agents/shared/receipt.py new file mode 100644 index 000000000..6f30067ee --- /dev/null +++ b/surfsense_backend/app/agents/shared/receipt.py @@ -0,0 +1,161 @@ +"""Receipt: structured handle returned by every mutating subagent tool. + +Generalises the Hermes ``entry`` dict (see ``references/hermes-agent/tools/ +delegate_tool.py:1663-1697``) for our 5 deliverable types + 15 connectors + +KB writes. The supervisor reads the Receipt to verify what actually happened +without round-tripping through LLM paraphrase. + +**Why this lives under ``app.agents.shared`` and not under either of the +two agent packages:** the Receipt is a *contract* shared between +``multi_agent_chat`` (where mutating tools emit it) and ``new_chat`` +(where ``filesystem_state.SurfSenseFilesystemState`` declares the +``receipts`` reducer that accumulates it, and where +``middleware.kb_persistence`` emits its own KB-write receipts). Putting +the contract in either package would create a bidirectional import +between the two — see the commit that introduced this module for the +``ImportError`` chain it broke. + +Each mutating tool wraps its native return shape into a Receipt via +:func:`make_receipt` (or builds one directly) and returns it under the +``"receipt"`` key alongside its existing payload. The subagent boundary +machinery in ``checkpointed_subagent_middleware.task_tool`` then folds +the receipt into the parent's ``receipts`` state via the append reducer. + +The KB write path is the one exception: file-tool calls cannot emit a +durable receipt because the actual DB writes happen end-of-turn inside +:class:`app.agents.new_chat.middleware.kb_persistence.KnowledgeBasePersistenceMiddleware`. +KB tools therefore emit a *provisional* receipt with ``status="pending"``; +the persistence middleware flips it to ``"success"`` or ``"failed"`` +before returning control to the parent. +""" + +from __future__ import annotations + +from typing import Any, Literal, TypedDict + +# Subagent that emitted this receipt. +ReceiptRoute = Literal[ + "deliverables", + "knowledge_base", + "notion", + "slack", + "gmail", + "linear", + "jira", + "clickup", + "confluence", + "calendar", + "luma", + "airtable", + "google_drive", + "dropbox", + "onedrive", + "discord", + "teams", +] + +# Within-route kind of artefact / external resource the operation touched. +# Left as ``str`` rather than a giant union so each route file documents +# its own enum next to its tools. +ReceiptType = str + +# Operation verb. Kept open for the same reason as ``ReceiptType``. +ReceiptOperation = str + +# Pending = async backend (Celery podcast / video) that the orchestrator +# will surface progress for out of band; persistence-MW flipped this to +# ``success`` for KB writes that committed. +ReceiptStatus = Literal["success", "pending", "failed"] + + +class Receipt(TypedDict, total=False): + """Structured per-mutation handle returned to the parent subagent. + + All fields are ``NotRequired`` (TypedDict ``total=False``) so each + route's tool can populate only the fields it actually has — e.g. Gmail + never sets ``verifiable_url`` because Gmail doesn't expose per-message + URLs. The receipts state reducer treats missing keys as missing rather + than ``null`` so we don't double-count. + """ + + route: ReceiptRoute + """Subagent name. Lets the orchestrator filter ``state['receipts']`` + by route without re-deriving from ``type``.""" + + type: ReceiptType + """Within-route kind. e.g. for ``deliverables`` one of ``{report, + podcast, video_presentation, resume, image}``; for ``notion`` ``page``; + for ``slack`` ``message``.""" + + operation: ReceiptOperation + """Verb. e.g. ``generate`` (deliverables), ``create`` / ``update`` / + ``delete`` (most connectors), ``send`` / ``post`` (chat), ``write_file`` + / ``edit_file`` / ``rm`` / ``rmdir`` / ``move_file`` / ``mkdir`` (KB).""" + + status: ReceiptStatus + """``success`` / ``pending`` / ``failed``. The verification teaching + in ``shared/snippets/verifiable_handle.md`` keys off this field.""" + + external_id: str | None + """Backend identifier. Report row id, Notion ``page_id``, Slack ``ts``, + Gmail ``message_id``, Linear identifier, KB ``virtualPath``, etc. + ``None`` only when the operation failed before the backend assigned one.""" + + verifiable_url: str | None + """URL the parent can pass to ``scrape_webpage`` to verify the + operation. ``None`` when no public URL exists (Gmail, KB, raw images + stored in the DB).""" + + preview: str | None + """Short snippet (~200 chars) of what was produced. First lines of + a generated report's markdown, transcript opener for a podcast, + thumbnail URL for an image. Lets the orchestrator decide whether to + re-render in the UI without re-loading the artefact.""" + + error: str | None + """Filled iff ``status == "failed"``. Plain-text reason; the parent + surfaces it in its own ``next_step``.""" + + +def make_receipt( + *, + route: ReceiptRoute, + type: str, + operation: str, + status: ReceiptStatus, + external_id: str | None = None, + verifiable_url: str | None = None, + preview: str | None = None, + error: str | None = None, +) -> Receipt: + """Construct a :class:`Receipt` with non-``None`` fields only. + + Drops keys whose value is ``None`` so downstream consumers can use + ``"verifiable_url" in receipt`` to distinguish "tool returned no URL" + from "tool deliberately surfaced ``null``". + """ + out: dict[str, Any] = { + "route": route, + "type": type, + "operation": operation, + "status": status, + } + if external_id is not None: + out["external_id"] = external_id + if verifiable_url is not None: + out["verifiable_url"] = verifiable_url + if preview is not None: + out["preview"] = preview + if error is not None: + out["error"] = error + return out # type: ignore[return-value] + + +__all__ = [ + "Receipt", + "ReceiptOperation", + "ReceiptRoute", + "ReceiptStatus", + "ReceiptType", + "make_receipt", +] diff --git a/surfsense_backend/app/agents/shared/receipt_command.py b/surfsense_backend/app/agents/shared/receipt_command.py new file mode 100644 index 000000000..f1c269e90 --- /dev/null +++ b/surfsense_backend/app/agents/shared/receipt_command.py @@ -0,0 +1,71 @@ +"""Helper for wrapping a tool result with a Receipt in a ``Command(update=...)``. + +Most mutating subagent tools historically returned a plain ``dict`` payload +which deepagents serialised straight into the ``ToolMessage`` content. To +participate in the verification teaching from +``multi_agent_chat/subagents/shared/snippets/verifiable_handle.md`` those +tools now also need to write a :class:`Receipt` into the parent's +``state['receipts']`` list (declared on +:class:`~app.agents.new_chat.filesystem_state.SurfSenseFilesystemState` +and backed by the append reducer). + +:func:`with_receipt` wraps both behaviours: it returns the tool payload as +a JSON-encoded ``ToolMessage`` AND appends the receipt to state in a single +:class:`~langgraph.types.Command`. Use it at every ``return`` site of a +mutating tool — including failure paths (emit a receipt with +``status="failed"`` and the error message in ``error``). +""" + +from __future__ import annotations + +import json +from typing import Any + +from langchain_core.messages import ToolMessage +from langgraph.types import Command + +from app.agents.shared.receipt import Receipt + + +def _content_to_text(payload: dict[str, Any] | str) -> str: + """Serialise a tool payload to ``ToolMessage`` content. + + Dicts go through ``json.dumps`` (matching deepagents' default tool-result + serialisation); strings are passed through. Anything else is coerced via + ``str`` so we never raise here — a mis-typed tool return would already + have failed inside the tool body. + """ + if isinstance(payload, str): + return payload + if isinstance(payload, dict): + return json.dumps(payload, default=str) + return str(payload) + + +def with_receipt( + *, + payload: dict[str, Any] | str, + receipt: Receipt, + tool_call_id: str, +) -> Command: + """Return a Command that ships ``payload`` as a ToolMessage AND appends ``receipt``. + + The append happens via the ``_list_append_reducer`` on the ``receipts`` + field of :class:`~app.agents.new_chat.filesystem_state.SurfSenseFilesystemState`, + so concurrent subagent batches (item 4 in the plan) won't clobber each + other's receipts. + """ + return Command( + update={ + "messages": [ + ToolMessage( + content=_content_to_text(payload), + tool_call_id=tool_call_id, + ) + ], + "receipts": [receipt], + } + ) + + +__all__ = ["with_receipt"] diff --git a/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py b/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py index 496c6d0c3..a2f4d0bbd 100644 --- a/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py +++ b/surfsense_backend/app/etl_pipeline/etl_pipeline_service.py @@ -62,7 +62,9 @@ class EtlPipelineService: return result if category == FileCategory.AUDIO: - content = await transcribe_audio(request.file_path, request.filename) + content = await transcribe_audio( + request.file_path, request.filename + ) result = EtlResult( markdown_content=content, etl_service="AUDIO", diff --git a/surfsense_backend/app/services/composio_service.py b/surfsense_backend/app/services/composio_service.py index d73a0d4ce..920f51d84 100644 --- a/surfsense_backend/app/services/composio_service.py +++ b/surfsense_backend/app/services/composio_service.py @@ -835,7 +835,14 @@ class ComposioService: ) if not result.get("success"): - return [], None, result.get("error", "Unknown error") + # 4-tuple to match this function's declared return shape + # ``(messages, next_page_token, result_size_estimate, error)``. + # The error branch previously dropped the + # ``result_size_estimate`` slot, which crashed the caller's + # unpack with ``ValueError: not enough values to unpack + # (expected 4, got 3)`` and hid the real Composio error + # (e.g. expired connected account / invalid API key). + return [], None, None, result.get("error", "Unknown error") data = result.get("data", {}) diff --git a/surfsense_backend/app/services/gmail/kb_sync_service.py b/surfsense_backend/app/services/gmail/kb_sync_service.py index 6ff5f3c2b..85e25fcb6 100644 --- a/surfsense_backend/app/services/gmail/kb_sync_service.py +++ b/surfsense_backend/app/services/gmail/kb_sync_service.py @@ -101,9 +101,7 @@ class GmailKBSyncService: else: logger.warning("No LLM configured -- using fallback summary") summary_content = f"Gmail Message: {subject}\n\n{indexable_content}" - summary_embedding = await asyncio.to_thread( - embed_text, summary_content - ) + summary_embedding = await asyncio.to_thread(embed_text, summary_content) chunks = await create_document_chunks(indexable_content) now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") diff --git a/surfsense_backend/app/services/google_calendar/kb_sync_service.py b/surfsense_backend/app/services/google_calendar/kb_sync_service.py index 1f017ec4d..e59868aff 100644 --- a/surfsense_backend/app/services/google_calendar/kb_sync_service.py +++ b/surfsense_backend/app/services/google_calendar/kb_sync_service.py @@ -116,9 +116,7 @@ class GoogleCalendarKBSyncService: summary_content = ( f"Google Calendar Event: {event_summary}\n\n{indexable_content}" ) - summary_embedding = await asyncio.to_thread( - embed_text, summary_content - ) + summary_embedding = await asyncio.to_thread(embed_text, summary_content) chunks = await create_document_chunks(indexable_content) now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -297,9 +295,7 @@ class GoogleCalendarKBSyncService: summary_content = ( f"Google Calendar Event: {event_summary}\n\n{indexable_content}" ) - summary_embedding = await asyncio.to_thread( - embed_text, summary_content - ) + summary_embedding = await asyncio.to_thread(embed_text, summary_content) chunks = await create_document_chunks(indexable_content) now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") diff --git a/surfsense_backend/app/services/jira/kb_sync_service.py b/surfsense_backend/app/services/jira/kb_sync_service.py index 5f6668377..37001a476 100644 --- a/surfsense_backend/app/services/jira/kb_sync_service.py +++ b/surfsense_backend/app/services/jira/kb_sync_service.py @@ -98,9 +98,7 @@ class JiraKBSyncService: summary_content = ( f"Jira Issue {issue_identifier}: {issue_title}\n\n{issue_content}" ) - summary_embedding = await asyncio.to_thread( - embed_text, summary_content - ) + summary_embedding = await asyncio.to_thread(embed_text, summary_content) chunks = await create_document_chunks(issue_content) now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -214,9 +212,7 @@ class JiraKBSyncService: summary_content = ( f"Jira Issue {issue_identifier}: {issue_title}\n\n{issue_content}" ) - summary_embedding = await asyncio.to_thread( - embed_text, summary_content - ) + summary_embedding = await asyncio.to_thread(embed_text, summary_content) chunks = await create_document_chunks(issue_content) diff --git a/surfsense_backend/app/services/llm_service.py b/surfsense_backend/app/services/llm_service.py index fa97fb33a..aadb60cde 100644 --- a/surfsense_backend/app/services/llm_service.py +++ b/surfsense_backend/app/services/llm_service.py @@ -682,11 +682,7 @@ def get_planner_llm() -> ChatLiteLLM | None: from app.agents.new_chat.llm_config import create_chat_litellm_from_config planner_cfg = next( - ( - cfg - for cfg in config.GLOBAL_LLM_CONFIGS - if cfg.get("is_planner") is True - ), + (cfg for cfg in config.GLOBAL_LLM_CONFIGS if cfg.get("is_planner") is True), None, ) if not planner_cfg: diff --git a/surfsense_backend/app/services/onedrive/kb_sync_service.py b/surfsense_backend/app/services/onedrive/kb_sync_service.py index e1da3b4a1..731f081dd 100644 --- a/surfsense_backend/app/services/onedrive/kb_sync_service.py +++ b/surfsense_backend/app/services/onedrive/kb_sync_service.py @@ -96,9 +96,7 @@ class OneDriveKBSyncService: else: logger.warning("No LLM configured — using fallback summary") summary_content = f"OneDrive File: {file_name}\n\n{indexable_content}" - summary_embedding = await asyncio.to_thread( - embed_text, summary_content - ) + summary_embedding = await asyncio.to_thread(embed_text, summary_content) chunks = await create_document_chunks(indexable_content) now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") diff --git a/surfsense_backend/app/tasks/chat/stream_new_chat.py b/surfsense_backend/app/tasks/chat/stream_new_chat.py index 1b2a4cfbb..78f80c955 100644 --- a/surfsense_backend/app/tasks/chat/stream_new_chat.py +++ b/surfsense_backend/app/tasks/chat/stream_new_chat.py @@ -2608,9 +2608,7 @@ async def stream_resume_chat( visibility = thread_visibility or ChatVisibility.PRIVATE from app.config import config as _app_config - chat_agent_mode = ( - "multi" if _app_config.MULTI_AGENT_CHAT_ENABLED else "single" - ) + chat_agent_mode = "multi" if _app_config.MULTI_AGENT_CHAT_ENABLED else "single" with contextlib.suppress(Exception): chat_span.set_attribute("agent.mode", chat_agent_mode) _t0 = time.perf_counter() diff --git a/surfsense_backend/app/tasks/chat/streaming/handlers/tool_end.py b/surfsense_backend/app/tasks/chat/streaming/handlers/tool_end.py index ad4a17d08..2ff810447 100644 --- a/surfsense_backend/app/tasks/chat/streaming/handlers/tool_end.py +++ b/surfsense_backend/app/tasks/chat/streaming/handlers/tool_end.py @@ -6,6 +6,9 @@ import json from collections.abc import Iterator from typing import Any +from langchain_core.messages import ToolMessage +from langgraph.types import Command + from app.tasks.chat.streaming.handlers.tools import ( ToolCompletionEmissionContext, iter_tool_completion_emission_frames, @@ -19,6 +22,38 @@ from app.tasks.chat.streaming.relay.task_span import ( from app.tasks.chat.streaming.relay.thinking_step_sse import emit_thinking_step_frame +def _unwrap_command_output(raw_output: Any) -> Any: + """Replace a ``Command`` from a tool return with its inner ``ToolMessage``. + + Tools that participate in receipt-style state writes (see + ``app.agents.shared.receipt_command.with_receipt``) return a + ``Command(update={"messages": [ToolMessage(...)], "receipts": [...]})``. + LangChain's ``on_tool_end`` event surfaces that ``Command`` verbatim as + ``data.output``, which the rest of this handler can't introspect: it has + no ``.content``, isn't a ``dict``, and stringifies to ``"Command(...)"``. + That stringified payload reaches the frontend and breaks tool-specific + UI components (e.g. the podcast card) that look for ``status`` / + ``podcast_id`` at the top level. + + We extract the first ``ToolMessage`` from the Command's ``messages`` list + so downstream code can read ``.content`` normally. Commands that don't + contain a ``ToolMessage`` (rare, e.g. pure state updates) are returned + unchanged — the existing ``str(raw_output)`` fallback handles them. + """ + if not isinstance(raw_output, Command): + return raw_output + update = raw_output.update + if not isinstance(update, dict): + return raw_output + messages = update.get("messages") + if not isinstance(messages, list): + return raw_output + for msg in messages: + if isinstance(msg, ToolMessage): + return msg + return raw_output + + def iter_tool_end_frames( event: dict[str, Any], *, @@ -33,7 +68,7 @@ def iter_tool_end_frames( state.active_tool_depth = max(0, state.active_tool_depth - 1) run_id = event.get("run_id", "") tool_name = event.get("name", "unknown_tool") - raw_output = event.get("data", {}).get("output", "") + raw_output = _unwrap_command_output(event.get("data", {}).get("output", "")) staged_file_path = state.file_path_by_run.pop(run_id, None) if run_id else None if hasattr(raw_output, "content"): diff --git a/surfsense_backend/app/tasks/chat/streaming/handlers/tools/deliverables/generate_video_presentation/emission.py b/surfsense_backend/app/tasks/chat/streaming/handlers/tools/deliverables/generate_video_presentation/emission.py index 21e27d4c3..51a67f369 100644 --- a/surfsense_backend/app/tasks/chat/streaming/handlers/tools/deliverables/generate_video_presentation/emission.py +++ b/surfsense_backend/app/tasks/chat/streaming/handlers/tools/deliverables/generate_video_presentation/emission.py @@ -15,12 +15,24 @@ def iter_completion_emission_frames( out = ctx.tool_output payload = out if isinstance(out, dict) else {"result": out} yield ctx.emit_tool_output_card(payload) - if isinstance(out, dict) and out.get("status") == "pending": + if not isinstance(out, dict): + return + status = out.get("status") + # ``ready`` is the live success status now that the tool waits for the + # Celery worker to reach a terminal state. ``pending`` is retained as a + # legacy branch for old saved chats that pre-date the wait-for-terminal + # change (see ``app.agents.shared.deliverable_wait``). + if status == "ready": + yield ctx.streaming_service.format_terminal_info( + f"Video presentation generated successfully: {out.get('title', 'Presentation')}", + "success", + ) + elif status == "pending": yield ctx.streaming_service.format_terminal_info( f"Video presentation queued: {out.get('title', 'Presentation')}", "success", ) - elif isinstance(out, dict) and out.get("status") == "failed": + elif status == "failed": error_msg = out.get("error", "Unknown error") yield ctx.streaming_service.format_terminal_info( f"Presentation generation failed: {error_msg}", diff --git a/surfsense_backend/app/utils/document_converters.py b/surfsense_backend/app/utils/document_converters.py index 9bc8103c5..059d91806 100644 --- a/surfsense_backend/app/utils/document_converters.py +++ b/surfsense_backend/app/utils/document_converters.py @@ -222,9 +222,7 @@ async def generate_document_summary( else: enhanced_summary_content = summary_content - summary_embedding = await asyncio.to_thread( - embed_text, enhanced_summary_content - ) + summary_embedding = await asyncio.to_thread(embed_text, enhanced_summary_content) return enhanced_summary_content, summary_embedding From 2b7d91aa0323a757b169294302c3ab12a4b39881 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 00:12:02 +0200 Subject: [PATCH 130/198] =?UTF-8?q?feat(automations):=20add=20create=5Faut?= =?UTF-8?q?omation=20HITL=20tool=20(NL=20=E2=86=92=20draft=20=E2=86=92=20a?= =?UTF-8?q?pprove=20=E2=86=92=20save)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single tool exposed to the main agent. The main agent passes a natural-language `intent`; a focused drafter sub-LLM turns it into a full AutomationCreate JSON; that JSON is surfaced via request_approval (action_type "automation_create") so the user can edit/approve it on a frontend card; on approval the tool persists via AutomationService. Three phases, one tool call. Scope split: - main agent sees only `intent: str` (no schema knowledge leaks into the calling graph) — prompt fragments scoped accordingly. - drafter sub-LLM owns the schema + few-shot intent→JSON examples — lives in the generating graph's prompt (tools/automation/prompt.py). Files: - main_agent/tools/automation/{create.py, prompt.py, __init__.py}: new tool + drafter system prompt with two few-shot intent→JSON examples. - system_prompt/prompts/tools/create_automation/{description.md, example.md}: intent-only guidance for the main agent. - main_agent/tools/index.py: add create_automation to the main-agent allowlist. - new_chat/tools/registry.py: deferred-import factory to break the multi_agent_chat ↔ registry cycle; one ToolDefinition entry. --- .../tools/create_automation/__init__.py | 1 + .../tools/create_automation/description.md | 31 +++ .../tools/create_automation/example.md | 13 ++ .../main_agent/tools/automation/__init__.py | 7 + .../main_agent/tools/automation/create.py | 203 ++++++++++++++++++ .../main_agent/tools/automation/prompt.py | 179 +++++++++++++++ .../main_agent/tools/index.py | 1 + .../app/agents/new_chat/tools/registry.py | 37 ++++ 8 files changed, 472 insertions(+) create mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/__init__.py create mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md create mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/example.md create mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/__init__.py create mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py create mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/__init__.py new file mode 100644 index 000000000..30699a4a1 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/__init__.py @@ -0,0 +1 @@ +"""``create_automation`` — description + few-shot examples.""" diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md new file mode 100644 index 000000000..25b4eec47 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md @@ -0,0 +1,31 @@ +- `create_automation` — Draft and author a new automation. You describe the + user's intent; a focused drafter inside the tool turns it into the full + automation JSON; the user reviews and edits it on an approval card; on + approval it's saved. All three phases happen in a single tool call. + - Call when the user wants SurfSense to do something on its own: anything + recurring or scheduled ("every morning…", "each Monday…", "weekly + recap…"). + - Args: + - `intent` (string): restate the user's request **concretely**, in one + paragraph. Cover three things: + - **What** should run (the action: summarize, recap, post, draft, …). + - **When** it should run (schedule + timezone if the user mentioned one; + otherwise leave the timezone for the drafter to default to UTC). + - **Static values** the automation needs (folder ids, channel names, + project keys, parent page ids, …) — list them with their values. + If the user did NOT supply one the automation needs, say so + explicitly ("the Notion parent page id was not specified") so the + drafter leaves a placeholder. + - Do NOT prompt the user to confirm before calling — the approval card + IS the confirmation. The user can edit any field on the card. + - Returns: + - `{status: "saved", automation_id, name}` — confirm briefly to the + user ("Saved as automation #N — runs <when>."). Don't dump JSON back. + - `{status: "rejected", message}` — the user declined on the card. + Acknowledge once ("Understood, I didn't create it.") and stop. Do + NOT retry or pitch variants. + - `{status: "invalid", issues, raw?}` — drafting/validation failed + before the card was shown. Read the issues, refine your `intent` + with the missing details, call again. + - `{status: "error", message}` — surface the message verbatim and + offer to retry. diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/example.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/example.md new file mode 100644 index 000000000..19311bef0 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/example.md @@ -0,0 +1,13 @@ +<example> +user: "Every weekday at 9am, summarize new documents in folder 12 and post the summary to Slack channel #daily-digest." +→ create_automation(intent="Every weekday at 09:00 UTC, summarize documents added to folder_id=12 since the last run, then post the summary to Slack channel '#daily-digest'. Static inputs: folder_id=12, slack_channel='#daily-digest'.") +tool returns: {"status": "saved", "automation_id": 42, "name": "Daily folder 12 digest"} +(Reply briefly: "Saved as automation #42 — runs weekdays at 9am UTC.") +</example> + +<example> +user: "Once a week on Mondays at 7am Paris time, draft a Notion page recapping last week's Jira tickets in project CORE." +→ create_automation(intent="Every Monday at 07:00 Europe/Paris, read last week's Jira issues in project CORE, then draft a Notion page recapping them. Static inputs: jira_project_key='CORE'. The user did NOT specify which Notion page the recap should sit under — leave notion_parent_page_id as a placeholder.") +tool returns: {"status": "saved", "automation_id": 51, "name": "Weekly CORE Jira recap"} +(Reply: "Saved as automation #51. I left the Notion parent page id as a placeholder — set it on the automation before next Monday.") +</example> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/__init__.py new file mode 100644 index 000000000..d47bbac7e --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/__init__.py @@ -0,0 +1,7 @@ +"""``create_automation`` — author + persist an automation via a HITL card.""" + +from __future__ import annotations + +from .create import create_create_automation_tool + +__all__ = ["create_create_automation_tool"] diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py new file mode 100644 index 000000000..78fedde22 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py @@ -0,0 +1,203 @@ +"""``create_automation`` — NL intent → drafted JSON → HITL approval card → persisted. + +Single tool that: + +1. Drafts a structured automation from the user's intent via a focused sub-LLM + (system prompt in :mod:`.prompt`). +2. Surfaces the validated draft in a HITL approval card + (``action_type="automation_create"``). +3. On approval, validates the (possibly edited) payload again and persists + it via :class:`AutomationService`. + +The main agent only restates the user's request as a single ``intent`` string. +The drafting sub-LLM owns the JSON shape; the HITL card is the user's review. +""" + +from __future__ import annotations + +import json +import logging +import re +from typing import Any +from uuid import UUID + +from fastapi import HTTPException +from langchain_core.messages import HumanMessage +from langchain_core.tools import tool +from pydantic import ValidationError + +from app.agents.new_chat.tools.hitl import request_approval +from app.automations.schemas.api import AutomationCreate +from app.automations.services.automation import AutomationService +from app.db import User, async_session_maker +from app.utils.content_utils import extract_text_content + +from .prompt import build_draft_prompt + +logger = logging.getLogger(__name__) + +_JSON_FENCE = re.compile(r"```(?:json)?\s*(.*?)\s*```", re.DOTALL) + + +def create_create_automation_tool( + *, + search_space_id: int, + user_id: str | UUID, + llm: Any, +): + """Factory for the ``create_automation`` tool. + + ``search_space_id`` is injected from the chat session (the model never + has to guess it). ``llm`` is the drafting sub-model — we reuse the main + agent's LLM and tag the call so it's identifiable in traces. A fresh + ``AsyncSession`` is opened per call to avoid stale sessions on + compiled-agent cache hits (same pattern as the Notion / memory tools). + """ + uid = UUID(user_id) if isinstance(user_id, str) else user_id + + @tool + async def create_automation(intent: str) -> dict[str, Any]: + """Draft + save an automation from a natural-language intent. + + Use this when the user wants SurfSense to do something on its own + on a schedule (e.g. "every morning summarize folder 12 to Slack"). + Restate the user's request as ONE concrete ``intent`` string: what + should run, when, and which static values (folder ids, channel + names, …) it needs. + + The tool drafts the full automation JSON internally, shows the user + an approval card for review, and persists on approval. Do NOT + prompt the user to confirm before calling — the card IS the + confirmation. The user can edit any field there. + + Args: + intent: Concrete restatement of the user's request. Include + the schedule (with timezone if mentioned), the action to + take, and any static values. Example: "Every weekday at + 09:00 UTC, summarize new docs added to folder_id=12 since + the last run, then post the summary to Slack channel + '#daily-digest'." + + Returns: + ``{"status": "saved", "automation_id": int, "name": str}`` on + approval + save. + ``{"status": "rejected", "message": "..."}`` when the user + declines on the card. + ``{"status": "invalid", "issues": [...], "raw": ...}`` when + the drafter produced output that did not validate (call again + with a more precise intent). + ``{"status": "error", "message": "..."}`` on drafter or + persistence failure. + + IMPORTANT: when status is ``"rejected"`` the user explicitly + declined. Acknowledge once and stop — do NOT retry or pitch + variants without a fresh user request. + """ + # --- 1. Draft via sub-LLM --- + prompt = build_draft_prompt(search_space_id=search_space_id, intent=intent) + try: + response = await llm.ainvoke( + [HumanMessage(content=prompt)], + config={"tags": ["surfsense:internal", "automation-draft"]}, + ) + except Exception as exc: + logger.exception("create_automation drafting LLM call failed") + return {"status": "error", "message": f"drafting failed: {exc}"} + + raw_text = extract_text_content(response.content).strip() + draft = _extract_json(raw_text) + if draft is None: + return { + "status": "invalid", + "issues": ["model output was not parseable JSON"], + "raw": raw_text, + } + + # search_space_id is injected here so the sub-LLM never has to guess. + draft["search_space_id"] = search_space_id + try: + validated_draft = AutomationCreate.model_validate(draft) + except ValidationError as exc: + return { + "status": "invalid", + "issues": _format_validation_issues(exc), + "raw": draft, + } + + # --- 2. HITL approval card --- + try: + card_params = validated_draft.model_dump(mode="json", by_alias=True) + # search_space_id is session-scoped, not user-editable. + card_params.pop("search_space_id", None) + + result = request_approval( + action_type="automation_create", + tool_name="create_automation", + params=card_params, + context={"search_space_id": search_space_id}, + ) + + if result.rejected: + return { + "status": "rejected", + "message": "User declined. Do not retry or suggest alternatives.", + } + + # --- 3. Persist (re-validate in case the user edited) --- + final_payload = {**result.params, "search_space_id": search_space_id} + try: + final_validated = AutomationCreate.model_validate(final_payload) + except ValidationError as exc: + return { + "status": "invalid", + "issues": _format_validation_issues(exc), + } + + async with async_session_maker() as session: + user = await session.get(User, uid) + if user is None: + return { + "status": "error", + "message": "user not found in this session", + } + service = AutomationService(session=session, user=user) + created = await service.create(final_validated) + return { + "status": "saved", + "automation_id": created.id, + "name": created.name, + } + + except HTTPException as exc: + return {"status": "error", "message": exc.detail} + except Exception as exc: + from langgraph.errors import GraphInterrupt + + if isinstance(exc, GraphInterrupt): + raise + logger.exception("create_automation failed") + return {"status": "error", "message": f"persistence failed: {exc}"} + + return create_automation + + +def _extract_json(text: str) -> dict[str, Any] | None: + """Pull a JSON object out of the model response, tolerating ``` fences.""" + if not text: + return None + candidate = text + fence_match = _JSON_FENCE.search(text) + if fence_match: + candidate = fence_match.group(1) + try: + parsed = json.loads(candidate) + except json.JSONDecodeError: + return None + return parsed if isinstance(parsed, dict) else None + + +def _format_validation_issues(exc: ValidationError) -> list[str]: + return [ + f"{'.'.join(str(p) for p in err['loc'])}: {err['msg']}" + for err in exc.errors() + ] diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py new file mode 100644 index 000000000..45870e768 --- /dev/null +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py @@ -0,0 +1,179 @@ +"""System prompt for the drafting sub-LLM inside ``create_automation``. + +Converts a natural-language ``intent`` into a structured ``AutomationCreate`` +JSON object. That object becomes the payload the HITL approval card surfaces. + +Scope split: + Real automation JSONs live here — this is the graph that *generates* + the JSON. The main agent's prompt fragments (``description.md`` / + ``example.md``) only carry intent-string examples; the main agent + never sees the schema. + +Layout: + The prompt is concatenated from four format-safe pieces. ``_HEADER`` / + ``_FOOTER`` carry the only ``str.format`` placeholders; ``_SCHEMA`` and + ``_FEW_SHOTS`` are plain strings so their JSON literals (and the + ``{{ inputs.X }}`` Jinja references in queries) can stay readable + without doubled-brace escaping. + +Catalog handling: + v1 hard-codes the action/trigger catalog (one action, one trigger). + When new types ship, swap the inline lines for a render-time pull + from ``app.automations.actions`` / ``app.automations.triggers`` via + lazy imports inside :func:`build_draft_prompt` so this module never + participates in the ``multi_agent_chat`` import cycle. +""" + +from __future__ import annotations + +from datetime import UTC, datetime + + +_HEADER = """\ +You are the SurfSense automation drafter. Convert the user intent below +into a SINGLE JSON object matching the AutomationCreate schema. Output +ONLY that JSON object — no prose, no markdown fence, no commentary. + +Current UTC time (for cron context): {now} +Target search_space_id: {search_space_id} +""" + + +_SCHEMA = """ +Required JSON shape: +{ + "name": "<1-200 char identifier>", + "description": "<one-liner or null>", + "definition": { + "schema_version": "1.0", + "name": "<same as outer name>", + "goal": "<one sentence>", + "plan": [ + { + "step_id": "<slug>", + "action": "agent_task", + "params": { + "query": "<Jinja string referencing {{ inputs.X }}>", + "auto_approve_all": true + } + } + ], + "metadata": {"tags": ["..."]} + }, + "triggers": [ + { + "type": "schedule", + "params": {"cron": "<5-field cron>", "timezone": "<IANA tz, default UTC>"}, + "static_inputs": {"<key>": <value>, ...}, + "enabled": true + } + ] +} + +v1 catalog (only these are valid): +- Actions: agent_task — params: query (string, Jinja), auto_approve_all (bool). +- Triggers: schedule — params: cron (5-field), timezone (IANA, e.g. "UTC", + "Europe/Paris"). Has static_inputs (object). + +Conventions: +- Whatever the plan references via {{ inputs.X }} MUST appear either in a + trigger's static_inputs OR in definition.inputs.schema_.properties so the + executor can resolve it at fire time. +- static_inputs carries values that stay the same across every fire + (folder ids, channel names, project keys, parent page ids). Put them on + the trigger that supplies them, not in the plan. +- If the user did NOT supply a value the plan needs, put "REPLACE_ME" in + static_inputs. Do NOT invent ids, channels, or paths. +- Cron is 5-field (minute hour day-of-month month day-of-week). Use the + timezone the user mentioned; default "UTC" when unspecified. +- Templating variables available at fire time: inputs.* (merged + static_inputs + runtime), inputs.fired_at, inputs.last_fired_at. +""" + + +_FEW_SHOTS = """ +Few-shot examples (intent → JSON output): + +### Example 1 — schedule with all static values supplied +intent: "Every weekday at 09:00 UTC, summarize documents added to folder_id=12 since the last run, then post the summary to Slack channel '#daily-digest'. Static inputs: folder_id=12, slack_channel='#daily-digest'." +output: +{ + "name": "Daily folder 12 digest", + "description": "Weekday 09:00 UTC summary of folder 12 documents posted to #daily-digest", + "definition": { + "schema_version": "1.0", + "name": "Daily folder 12 digest", + "goal": "Summarize new docs in folder 12 since the last run and post to #daily-digest", + "plan": [ + { + "step_id": "summarize_and_post", + "action": "agent_task", + "params": { + "query": "Summarize documents added to folder {{ inputs.folder_id }} since {{ inputs.last_fired_at or 'yesterday' }}, then send the summary to Slack channel {{ inputs.slack_channel }}.", + "auto_approve_all": true + } + } + ], + "metadata": {"tags": ["daily", "digest", "slack"]} + }, + "triggers": [ + { + "type": "schedule", + "params": {"cron": "0 9 * * 1-5", "timezone": "UTC"}, + "static_inputs": {"folder_id": 12, "slack_channel": "#daily-digest"}, + "enabled": true + } + ] +} + +### Example 2 — schedule with a missing value (REPLACE_ME placeholder) +intent: "Every Monday at 07:00 Europe/Paris, read last week's Jira issues in project CORE, then draft a Notion page recapping them. Static inputs: jira_project_key='CORE'. The user did NOT specify the Notion parent page id — leave it as a placeholder." +output: +{ + "name": "Weekly CORE Jira recap", + "description": "Monday 07:00 Europe/Paris recap of last week's CORE Jira issues, drafted to Notion", + "definition": { + "schema_version": "1.0", + "name": "Weekly CORE Jira recap", + "goal": "Recap last week's CORE Jira issues into a Notion page", + "plan": [ + { + "step_id": "recap", + "action": "agent_task", + "params": { + "query": "List Jira issues in project {{ inputs.jira_project_key }} updated in the 7 days before {{ inputs.fired_at }}. Draft a Notion page under parent id {{ inputs.notion_parent_page_id }} titled 'CORE recap — week of {{ inputs.fired_at }}'.", + "auto_approve_all": true + } + } + ], + "metadata": {"tags": ["weekly", "recap", "jira", "notion"]} + }, + "triggers": [ + { + "type": "schedule", + "params": {"cron": "0 7 * * 1", "timezone": "Europe/Paris"}, + "static_inputs": {"jira_project_key": "CORE", "notion_parent_page_id": "REPLACE_ME"}, + "enabled": true + } + ] +} +""" + + +_FOOTER = """ +User intent: +{intent} +""" + + +def build_draft_prompt(*, search_space_id: int, intent: str) -> str: + """Render the drafting sub-LLM system prompt for the given intent.""" + return ( + _HEADER.format( + now=datetime.now(UTC).isoformat(timespec="seconds"), + search_space_id=search_space_id, + ) + + _SCHEMA + + _FEW_SHOTS + + _FOOTER.format(intent=intent.strip()) + ) diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py index 5d309261c..88509eda7 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py @@ -10,6 +10,7 @@ MAIN_AGENT_SURFSENSE_TOOL_NAMES_ORDERED: tuple[str, ...] = ( "web_search", "scrape_webpage", "update_memory", + "create_automation", ) MAIN_AGENT_SURFSENSE_TOOL_NAMES: frozenset[str] = frozenset( diff --git a/surfsense_backend/app/agents/new_chat/tools/registry.py b/surfsense_backend/app/agents/new_chat/tools/registry.py index b842d7a20..8c263ca20 100644 --- a/surfsense_backend/app/agents/new_chat/tools/registry.py +++ b/surfsense_backend/app/agents/new_chat/tools/registry.py @@ -150,6 +150,28 @@ class ToolDefinition: reverse: Callable[[dict[str, Any], Any], dict[str, Any]] | None = None +# ============================================================================= +# Deferred-import factories +# ============================================================================= +# Used for tools whose impls live under ``multi_agent_chat``. Importing those +# at module-load time would cycle (``multi_agent_chat`` middleware imports +# this registry). The import inside the factory runs only when +# ``build_tools`` is called, by which point ``multi_agent_chat`` is fully +# initialised. + + +def _build_create_automation_tool(deps: dict[str, Any]) -> BaseTool: + from app.agents.multi_agent_chat.main_agent.tools.automation import ( + create_create_automation_tool, + ) + + return create_create_automation_tool( + search_space_id=deps["search_space_id"], + user_id=deps["user_id"], + llm=deps["llm"], + ) + + # ============================================================================= # Built-in Tools Registry # ============================================================================= @@ -261,6 +283,21 @@ BUILTIN_TOOLS: list[ToolDefinition] = [ requires=["db_session", "search_space_id", "user_id"], ), # ========================================================================= + # AUTOMATION AUTHORING - single HITL tool. The tool takes an NL ``intent`` + # from the main agent, drafts the full AutomationCreate JSON via a focused + # sub-LLM, surfaces it on an approval card, and persists on approval. The + # factory defers its import because the impl lives under ``multi_agent_chat`` + # and that package transitively pulls this registry via middleware; + # deferring to ``build_tools`` call-time breaks the cycle without a + # parallel registry. + # ========================================================================= + ToolDefinition( + name="create_automation", + description="Draft an automation from an NL intent; user approves the card; tool saves", + factory=_build_create_automation_tool, + requires=["search_space_id", "user_id", "llm"], + ), + # ========================================================================= # MEMORY TOOL - single update_memory, private or team by thread_visibility # ========================================================================= ToolDefinition( From 79f0218360e713ad552d2d8138cd744efc1687ca Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 00:30:40 +0200 Subject: [PATCH 131/198] rbac: surface automations permissions in the UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend already defined automations:create/read/update/delete/execute and seeded them on Owner/Editor/Viewer roles, but the Settings → Roles UI was missing the metadata to render them properly. - backend: add PERMISSION_DESCRIPTIONS entries for the 5 automations perms so the role editor stops falling back to "Permission for automations:create". - frontend: add automations to CATEGORY_CONFIG (Workflow icon, slotted between podcasts and connectors) so the role editor groups them as a real section. - frontend: extend the three ROLE_PRESETS — Editor and Contributor get create/read/update/execute (mirroring backend Editor); Viewer gets read. Prep work for the automations frontend; canPerform/usePermissionGate already handle the runtime gating, so no new hook is needed. --- surfsense_backend/app/routes/rbac_routes.py | 6 ++++++ .../components/settings/roles-manager.tsx | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/surfsense_backend/app/routes/rbac_routes.py b/surfsense_backend/app/routes/rbac_routes.py index 38ae31269..3b91e456d 100644 --- a/surfsense_backend/app/routes/rbac_routes.py +++ b/surfsense_backend/app/routes/rbac_routes.py @@ -107,6 +107,12 @@ PERMISSION_DESCRIPTIONS = { "settings:view": "View search space settings", "settings:update": "Modify search space settings", "settings:delete": "Delete the entire search space", + # Automations + "automations:create": "Create automations from chat or JSON", + "automations:read": "View automations, their triggers, and run history", + "automations:update": "Edit automations and manage their triggers", + "automations:delete": "Remove automations from the search space", + "automations:execute": "Manually fire automations", # Full access "*": "Full access to all features and settings", } diff --git a/surfsense_web/components/settings/roles-manager.tsx b/surfsense_web/components/settings/roles-manager.tsx index 88595e748..5c034470d 100644 --- a/surfsense_web/components/settings/roles-manager.tsx +++ b/surfsense_web/components/settings/roles-manager.tsx @@ -23,6 +23,7 @@ import { Unplug, Users, Video, + Workflow, } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; @@ -126,6 +127,12 @@ const CATEGORY_CONFIG: Record< description: "Generate AI podcasts from content", order: 5, }, + automations: { + label: "Automations", + icon: Workflow, + description: "Scheduled and event-driven agent tasks", + order: 5.5, + }, connectors: { label: "Connectors", icon: Unplug, @@ -200,6 +207,10 @@ const ROLE_PRESETS = { "podcasts:create", "podcasts:read", "podcasts:update", + "automations:create", + "automations:read", + "automations:update", + "automations:execute", "connectors:create", "connectors:read", "connectors:update", @@ -220,6 +231,7 @@ const ROLE_PRESETS = { "comments:read", "llm_configs:read", "podcasts:read", + "automations:read", "connectors:read", "logs:read", "members:view", @@ -240,6 +252,10 @@ const ROLE_PRESETS = { "comments:read", "llm_configs:read", "podcasts:read", + "automations:create", + "automations:read", + "automations:update", + "automations:execute", "connectors:read", "logs:read", "members:view", From d48bb2033be03f4a86d12ebff79468c1f1913314 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 00:55:46 +0200 Subject: [PATCH 132/198] fix(web): handle 204 No Content responses in base API service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DELETE endpoints in the automations API return 204; calling .json() on an empty body throws SyntaxError. Treat 204 as data=null and skip schema validation so callers can opt out of response bodies without errors or spurious schema-mismatch warnings. Also drops a pre-existing 'unknown → BodyInit' type error on the non-JSON body branch via a narrow cast (caller is responsible for passing a real BodyInit when Content-Type isn't application/json). --- surfsense_web/lib/apis/base-api.service.ts | 57 +++++++++++++--------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/surfsense_web/lib/apis/base-api.service.ts b/surfsense_web/lib/apis/base-api.service.ts index 0819cbc7c..a0039b63a 100644 --- a/surfsense_web/lib/apis/base-api.service.ts +++ b/surfsense_web/lib/apis/base-api.service.ts @@ -1,4 +1,5 @@ import type { ZodType } from "zod"; +import { BACKEND_URL } from "@/lib/env-config"; import { getClientPlatform } from "../agent-filesystem"; import { getBearerToken, handleUnauthorized, refreshAccessToken } from "../auth-utils"; import { @@ -9,7 +10,7 @@ import { NetworkError, NotFoundError, } from "../error"; -import { BACKEND_URL } from "@/lib/env-config"; + enum ResponseType { JSON = "json", TEXT = "text", @@ -122,8 +123,9 @@ class BaseApiService { if (contentType === "application/json" && typeof mergedOptions.body === "object") { fetchOptions.body = JSON.stringify(mergedOptions.body); } else { - // Pass body as-is for other content types (e.g., form data, already stringified) - fetchOptions.body = mergedOptions.body; + // Pass body as-is for other content types (form data, already stringified). + // Caller is responsible for passing a real BodyInit when Content-Type is not JSON. + fetchOptions.body = mergedOptions.body as BodyInit; } } @@ -210,32 +212,39 @@ class BaseApiService { let data; const responseType = mergedOptions.responseType; - try { - switch (responseType) { - case ResponseType.JSON: - data = await response.json(); - break; - case ResponseType.TEXT: - data = await response.text(); - break; - case ResponseType.BLOB: - data = await response.blob(); - break; - case ResponseType.ARRAY_BUFFER: - data = await response.arrayBuffer(); - break; - // Add more cases as needed - default: - data = await response.json(); + if (response.status === 204) { + // 204 No Content has no body; .json() would throw SyntaxError. + // Leave data as null and skip schema validation below so endpoints + // that opt out of bodies (REST-style DELETE) don't error on success. + data = null; + } else { + try { + switch (responseType) { + case ResponseType.JSON: + data = await response.json(); + break; + case ResponseType.TEXT: + data = await response.text(); + break; + case ResponseType.BLOB: + data = await response.blob(); + break; + case ResponseType.ARRAY_BUFFER: + data = await response.arrayBuffer(); + break; + // Add more cases as needed + default: + data = await response.json(); + } + } catch (error) { + console.error("Failed to parse response as JSON:", error); + throw new AppError("Failed to parse response", response.status, response.statusText); } - } catch (error) { - console.error("Failed to parse response as JSON:", error); - throw new AppError("Failed to parse response", response.status, response.statusText); } // Validate response if (responseType === ResponseType.JSON) { - if (!responseSchema) { + if (!responseSchema || response.status === 204) { return data; } const parsedData = responseSchema.safeParse(data); From b18a5fdca92ba7fcfd9e3240747e47dc2adedbf0 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 00:55:57 +0200 Subject: [PATCH 133/198] feat(web): automations contracts, API client, atoms and hooks Foundation for the v1 automations UI. Mirrors backend Pydantic schemas into Zod and wires the data layer end-to-end so feature surfaces can be built on top. contracts/types/automation.types.ts: - AutomationStatus, TriggerType, RunStatus enums. - AutomationDefinition envelope (PlanStep, TriggerSpec, Execution, Metadata, Inputs). - AutomationCreate/Update/Detail/Summary/List + listParams. - TriggerCreate/Update/Detail. - RunSummary/Detail/List + runListParams. lib/apis/automations-api.service.ts: - list/get/create/update/delete automations. - add/update/remove triggers (sub-resource). - list/get runs (read-only sub-resource). - safeParse on every write, 204-safe deletes. atoms/automations/: - automationsListAtom (active search space, first page). - 6 mutation atoms with toast + cache invalidation. hooks/: - use-automations.ts wraps the list atom. - use-automation.ts: parameterized detail by id. - use-automation-runs.ts: useAutomationRuns + useAutomationRun. lib/query-client/cache-keys.ts: automations namespace (list, detail, runs, run) keyed by (id, limit, offset) where relevant. Smoke: zod round-trip OK on backend-shape payloads (Automation, AutomationCreate, Trigger, Run); typecheck clean for new files; biome clean. --- .../automations/automations-mutation.atoms.ts | 127 ++++++++++++ .../automations/automations-query.atoms.ts | 31 +++ .../contracts/types/automation.types.ts | 193 ++++++++++++++++++ surfsense_web/hooks/use-automation-runs.ts | 42 ++++ surfsense_web/hooks/use-automation.ts | 19 ++ surfsense_web/hooks/use-automations.ts | 24 +++ .../lib/apis/automations-api.service.ts | 102 +++++++++ surfsense_web/lib/query-client/cache-keys.ts | 10 + 8 files changed, 548 insertions(+) create mode 100644 surfsense_web/atoms/automations/automations-mutation.atoms.ts create mode 100644 surfsense_web/atoms/automations/automations-query.atoms.ts create mode 100644 surfsense_web/contracts/types/automation.types.ts create mode 100644 surfsense_web/hooks/use-automation-runs.ts create mode 100644 surfsense_web/hooks/use-automation.ts create mode 100644 surfsense_web/hooks/use-automations.ts create mode 100644 surfsense_web/lib/apis/automations-api.service.ts diff --git a/surfsense_web/atoms/automations/automations-mutation.atoms.ts b/surfsense_web/atoms/automations/automations-mutation.atoms.ts new file mode 100644 index 000000000..f5e4fd5f4 --- /dev/null +++ b/surfsense_web/atoms/automations/automations-mutation.atoms.ts @@ -0,0 +1,127 @@ +import { atomWithMutation } from "jotai-tanstack-query"; +import { toast } from "sonner"; +import type { + AutomationCreateRequest, + AutomationUpdateRequest, + TriggerCreateRequest, + TriggerUpdateRequest, +} from "@/contracts/types/automation.types"; +import { automationsApiService } from "@/lib/apis/automations-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; +import { queryClient } from "@/lib/query-client/client"; + +// Cache invalidation strategy: +// - Automation writes invalidate the search-space list + the touched detail. +// - Trigger writes only invalidate the parent automation detail (triggers +// come back inline in AutomationDetail). +// We deliberately invalidate the whole "automations" prefix on the list side +// because list is keyed by (searchSpaceId, limit, offset) and we don't track +// the active pagination in this layer. + +function invalidateList(searchSpaceId: number) { + queryClient.invalidateQueries({ queryKey: ["automations", "list", searchSpaceId] }); +} + +function invalidateDetail(automationId: number) { + queryClient.invalidateQueries({ + queryKey: cacheKeys.automations.detail(automationId), + }); +} + +export const createAutomationMutationAtom = atomWithMutation(() => ({ + meta: { suppressGlobalErrorToast: true }, + mutationFn: async (request: AutomationCreateRequest) => { + return automationsApiService.createAutomation(request); + }, + onSuccess: (_, variables) => { + invalidateList(variables.search_space_id); + toast.success("Automation created"); + }, + onError: (error: Error) => { + console.error("Error creating automation:", error); + toast.error("Failed to create automation"); + }, +})); + +export const updateAutomationMutationAtom = atomWithMutation(() => ({ + meta: { suppressGlobalErrorToast: true }, + mutationFn: async (vars: { automationId: number; patch: AutomationUpdateRequest }) => { + return automationsApiService.updateAutomation(vars.automationId, vars.patch); + }, + onSuccess: (automation, vars) => { + invalidateDetail(vars.automationId); + invalidateList(automation.search_space_id); + toast.success("Automation updated"); + }, + onError: (error: Error) => { + console.error("Error updating automation:", error); + toast.error("Failed to update automation"); + }, +})); + +export const deleteAutomationMutationAtom = atomWithMutation(() => ({ + meta: { suppressGlobalErrorToast: true }, + mutationFn: async (vars: { automationId: number; searchSpaceId: number }) => { + await automationsApiService.deleteAutomation(vars.automationId); + return vars; + }, + onSuccess: (vars) => { + invalidateList(vars.searchSpaceId); + invalidateDetail(vars.automationId); + toast.success("Automation deleted"); + }, + onError: (error: Error) => { + console.error("Error deleting automation:", error); + toast.error("Failed to delete automation"); + }, +})); + +export const addTriggerMutationAtom = atomWithMutation(() => ({ + meta: { suppressGlobalErrorToast: true }, + mutationFn: async (vars: { automationId: number; payload: TriggerCreateRequest }) => { + return automationsApiService.addTrigger(vars.automationId, vars.payload); + }, + onSuccess: (_, vars) => { + invalidateDetail(vars.automationId); + toast.success("Trigger added"); + }, + onError: (error: Error) => { + console.error("Error adding trigger:", error); + toast.error("Failed to add trigger"); + }, +})); + +export const updateTriggerMutationAtom = atomWithMutation(() => ({ + meta: { suppressGlobalErrorToast: true }, + mutationFn: async (vars: { + automationId: number; + triggerId: number; + patch: TriggerUpdateRequest; + }) => { + return automationsApiService.updateTrigger(vars.automationId, vars.triggerId, vars.patch); + }, + onSuccess: (_, vars) => { + invalidateDetail(vars.automationId); + toast.success("Trigger updated"); + }, + onError: (error: Error) => { + console.error("Error updating trigger:", error); + toast.error("Failed to update trigger"); + }, +})); + +export const removeTriggerMutationAtom = atomWithMutation(() => ({ + meta: { suppressGlobalErrorToast: true }, + mutationFn: async (vars: { automationId: number; triggerId: number }) => { + await automationsApiService.removeTrigger(vars.automationId, vars.triggerId); + return vars; + }, + onSuccess: (vars) => { + invalidateDetail(vars.automationId); + toast.success("Trigger removed"); + }, + onError: (error: Error) => { + console.error("Error removing trigger:", error); + toast.error("Failed to remove trigger"); + }, +})); diff --git a/surfsense_web/atoms/automations/automations-query.atoms.ts b/surfsense_web/atoms/automations/automations-query.atoms.ts new file mode 100644 index 000000000..4117f9bc8 --- /dev/null +++ b/surfsense_web/atoms/automations/automations-query.atoms.ts @@ -0,0 +1,31 @@ +import { atomWithQuery } from "jotai-tanstack-query"; +import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; +import { automationsApiService } from "@/lib/apis/automations-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; + +// First page of the active search space's automations. +// Detail + paginated/parameterized reads live in hooks (see use-automation.ts, +// use-automation-runs.ts) so atoms stay tied to "current scope" and don't +// proliferate atom families for every (id, limit, offset) tuple. +const DEFAULT_LIMIT = 50; +const DEFAULT_OFFSET = 0; + +export const automationsListAtom = atomWithQuery((get) => { + const searchSpaceId = get(activeSearchSpaceIdAtom); + + return { + queryKey: cacheKeys.automations.list(Number(searchSpaceId ?? 0), DEFAULT_LIMIT, DEFAULT_OFFSET), + enabled: !!searchSpaceId, + staleTime: 60 * 1000, + queryFn: async () => { + if (!searchSpaceId) { + return { items: [], total: 0 }; + } + return automationsApiService.listAutomations({ + search_space_id: Number(searchSpaceId), + limit: DEFAULT_LIMIT, + offset: DEFAULT_OFFSET, + }); + }, + }; +}); diff --git a/surfsense_web/contracts/types/automation.types.ts b/surfsense_web/contracts/types/automation.types.ts new file mode 100644 index 000000000..a93249735 --- /dev/null +++ b/surfsense_web/contracts/types/automation.types.ts @@ -0,0 +1,193 @@ +import { z } from "zod"; + +// ============================================================================= +// Enums — mirror app/automations/persistence/enums/* +// ============================================================================= + +export const automationStatus = z.enum(["active", "paused", "archived"]); +export type AutomationStatus = z.infer<typeof automationStatus>; + +export const triggerType = z.enum(["schedule", "manual"]); +export type TriggerType = z.infer<typeof triggerType>; + +export const runStatus = z.enum([ + "pending", + "running", + "succeeded", + "failed", + "cancelled", + "timed_out", +]); +export type RunStatus = z.infer<typeof runStatus>; + +// ============================================================================= +// Definition envelope — mirror app/automations/schemas/definition/* +// ============================================================================= + +export const planStep = z.object({ + step_id: z.string().min(1), + action: z.string().min(1), + when: z.string().nullable().optional(), + params: z.record(z.string(), z.any()).default({}), + output_as: z.string().nullable().optional(), + max_retries: z.number().int().min(0).nullable().optional(), + timeout_seconds: z.number().int().positive().nullable().optional(), +}); +export type PlanStep = z.infer<typeof planStep>; + +export const definitionTriggerSpec = z.object({ + type: z.string().min(1), + params: z.record(z.string(), z.any()).default({}), +}); +export type DefinitionTriggerSpec = z.infer<typeof definitionTriggerSpec>; + +export const execution = z.object({ + timeout_seconds: z.number().int().positive().default(600), + max_retries: z.number().int().min(0).default(2), + retry_backoff: z.enum(["exponential", "linear", "none"]).default("exponential"), + concurrency: z.enum(["drop_if_running", "queue", "always"]).default("drop_if_running"), + on_failure: z.array(planStep).default([]), +}); +export type Execution = z.infer<typeof execution>; + +// Backend ``Metadata`` is ``extra="allow"`` — keep ``tags`` typed, accept arbitrary keys. +export const metadata = z.object({ tags: z.array(z.string()).default([]) }).catchall(z.any()); +export type Metadata = z.infer<typeof metadata>; + +// Backend ``Inputs`` serializes its ``schema_`` field as ``schema`` (alias). +export const inputs = z.object({ + schema: z.record(z.string(), z.any()), +}); +export type Inputs = z.infer<typeof inputs>; + +export const automationDefinition = z.object({ + schema_version: z.string().default("1.0"), + name: z.string().min(1).max(200), + goal: z.string().nullable().optional(), + inputs: inputs.nullable().optional(), + triggers: z.array(definitionTriggerSpec).default([]), + plan: z.array(planStep).min(1), + execution: execution.default(execution.parse({})), + metadata: metadata.default(metadata.parse({})), +}); +export type AutomationDefinition = z.infer<typeof automationDefinition>; + +// ============================================================================= +// Triggers (sub-resource) — mirror app/automations/schemas/api/trigger.py +// ============================================================================= + +export const triggerCreateRequest = z.object({ + type: triggerType, + params: z.record(z.string(), z.any()).default({}), + static_inputs: z.record(z.string(), z.any()).default({}), + enabled: z.boolean().default(true), +}); +export type TriggerCreateRequest = z.infer<typeof triggerCreateRequest>; + +export const triggerUpdateRequest = z.object({ + enabled: z.boolean().nullable().optional(), + params: z.record(z.string(), z.any()).nullable().optional(), + static_inputs: z.record(z.string(), z.any()).nullable().optional(), +}); +export type TriggerUpdateRequest = z.infer<typeof triggerUpdateRequest>; + +export const trigger = z.object({ + id: z.number(), + type: triggerType, + params: z.record(z.string(), z.any()), + static_inputs: z.record(z.string(), z.any()), + enabled: z.boolean(), + last_fired_at: z.string().nullable().optional(), + next_fire_at: z.string().nullable().optional(), + created_at: z.string(), +}); +export type Trigger = z.infer<typeof trigger>; + +// ============================================================================= +// Automations — mirror app/automations/schemas/api/automation.py +// ============================================================================= + +export const automationCreateRequest = z.object({ + search_space_id: z.number(), + name: z.string().min(1).max(200), + description: z.string().nullable().optional(), + definition: automationDefinition, + triggers: z.array(triggerCreateRequest).default([]), +}); +export type AutomationCreateRequest = z.infer<typeof automationCreateRequest>; + +export const automationUpdateRequest = z.object({ + name: z.string().min(1).max(200).nullable().optional(), + description: z.string().nullable().optional(), + status: automationStatus.nullable().optional(), + definition: automationDefinition.nullable().optional(), +}); +export type AutomationUpdateRequest = z.infer<typeof automationUpdateRequest>; + +export const automationSummary = z.object({ + id: z.number(), + search_space_id: z.number(), + name: z.string(), + description: z.string().nullable().optional(), + status: automationStatus, + version: z.number(), + created_at: z.string(), + updated_at: z.string(), +}); +export type AutomationSummary = z.infer<typeof automationSummary>; + +export const automation = automationSummary.extend({ + definition: automationDefinition, + triggers: z.array(trigger).default([]), +}); +export type Automation = z.infer<typeof automation>; + +export const automationListResponse = z.object({ + items: z.array(automationSummary), + total: z.number(), +}); +export type AutomationListResponse = z.infer<typeof automationListResponse>; + +export const automationListParams = z.object({ + search_space_id: z.number(), + limit: z.number().int().min(1).max(200).default(50), + offset: z.number().int().min(0).default(0), +}); +export type AutomationListParams = z.infer<typeof automationListParams>; + +// ============================================================================= +// Runs (sub-resource) — mirror app/automations/schemas/api/run.py +// ============================================================================= + +export const runSummary = z.object({ + id: z.number(), + automation_id: z.number(), + trigger_id: z.number().nullable().optional(), + status: runStatus, + started_at: z.string().nullable().optional(), + finished_at: z.string().nullable().optional(), + created_at: z.string(), +}); +export type RunSummary = z.infer<typeof runSummary>; + +export const run = runSummary.extend({ + definition_snapshot: z.record(z.string(), z.any()), + inputs: z.record(z.string(), z.any()), + step_results: z.array(z.record(z.string(), z.any())), + output: z.record(z.string(), z.any()).nullable().optional(), + artifacts: z.array(z.record(z.string(), z.any())), + error: z.record(z.string(), z.any()).nullable().optional(), +}); +export type Run = z.infer<typeof run>; + +export const runListResponse = z.object({ + items: z.array(runSummary), + total: z.number(), +}); +export type RunListResponse = z.infer<typeof runListResponse>; + +export const runListParams = z.object({ + limit: z.number().int().min(1).max(200).default(50), + offset: z.number().int().min(0).default(0), +}); +export type RunListParams = z.infer<typeof runListParams>; diff --git a/surfsense_web/hooks/use-automation-runs.ts b/surfsense_web/hooks/use-automation-runs.ts new file mode 100644 index 000000000..c91c7bd6e --- /dev/null +++ b/surfsense_web/hooks/use-automation-runs.ts @@ -0,0 +1,42 @@ +"use client"; +import { useQuery } from "@tanstack/react-query"; +import type { Run, RunListResponse } from "@/contracts/types/automation.types"; +import { automationsApiService } from "@/lib/apis/automations-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; + +const DEFAULT_LIMIT = 50; +const DEFAULT_OFFSET = 0; + +export interface UseAutomationRunsOptions { + limit?: number; + offset?: number; + enabled?: boolean; +} + +/** Paginated run history for one automation. Newest-first per backend. */ +export function useAutomationRuns( + automationId: number | undefined, + { limit = DEFAULT_LIMIT, offset = DEFAULT_OFFSET, enabled = true }: UseAutomationRunsOptions = {} +) { + return useQuery<RunListResponse, Error>({ + queryKey: cacheKeys.automations.runs(automationId ?? 0, limit, offset), + queryFn: () => automationsApiService.listRuns(automationId as number, { limit, offset }), + enabled: enabled && !!automationId, + staleTime: 30_000, + }); +} + +/** Single run with the full snapshot, step results, output and artifacts. */ +export function useAutomationRun( + automationId: number | undefined, + runId: number | undefined, + options: { enabled?: boolean } = {} +) { + const { enabled = true } = options; + return useQuery<Run, Error>({ + queryKey: cacheKeys.automations.run(automationId ?? 0, runId ?? 0), + queryFn: () => automationsApiService.getRun(automationId as number, runId as number), + enabled: enabled && !!automationId && !!runId, + staleTime: 30_000, + }); +} diff --git a/surfsense_web/hooks/use-automation.ts b/surfsense_web/hooks/use-automation.ts new file mode 100644 index 000000000..d49ec03a1 --- /dev/null +++ b/surfsense_web/hooks/use-automation.ts @@ -0,0 +1,19 @@ +"use client"; +import { useQuery } from "@tanstack/react-query"; +import type { Automation } from "@/contracts/types/automation.types"; +import { automationsApiService } from "@/lib/apis/automations-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; + +/** + * Fetch a single automation with its definition and triggers. + * Lives outside the jotai atom layer because it's keyed by id, not by the + * "current scope" the atom layer assumes. + */ +export function useAutomation(automationId: number | undefined) { + return useQuery<Automation, Error>({ + queryKey: cacheKeys.automations.detail(automationId ?? 0), + queryFn: () => automationsApiService.getAutomation(automationId as number), + enabled: !!automationId, + staleTime: 60_000, + }); +} diff --git a/surfsense_web/hooks/use-automations.ts b/surfsense_web/hooks/use-automations.ts new file mode 100644 index 000000000..945e91866 --- /dev/null +++ b/surfsense_web/hooks/use-automations.ts @@ -0,0 +1,24 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { automationsListAtom } from "@/atoms/automations/automations-query.atoms"; + +/** + * List automations in the active search space (first page). + * Pagination knobs live in detail/list hooks below; v1 surfaces only the + * first page since automation counts are expected to be small. + */ +export function useAutomations() { + const { data, isLoading, error, refetch } = useAutomationsRaw(); + return { + automations: data?.items ?? [], + total: data?.total ?? 0, + loading: isLoading, + error, + refresh: refetch, + }; +} + +// Exposed for callers that prefer the raw react-query result shape. +export function useAutomationsRaw() { + return useAtomValue(automationsListAtom); +} diff --git a/surfsense_web/lib/apis/automations-api.service.ts b/surfsense_web/lib/apis/automations-api.service.ts new file mode 100644 index 000000000..ebe72bea5 --- /dev/null +++ b/surfsense_web/lib/apis/automations-api.service.ts @@ -0,0 +1,102 @@ +import { + type AutomationCreateRequest, + type AutomationListParams, + type AutomationUpdateRequest, + automation, + automationCreateRequest, + automationListResponse, + automationUpdateRequest, + type RunListParams, + run, + runListResponse, + type TriggerCreateRequest, + type TriggerUpdateRequest, + trigger, + triggerCreateRequest, + triggerUpdateRequest, +} from "@/contracts/types/automation.types"; +import { ValidationError } from "../error"; +import { baseApiService } from "./base-api.service"; + +const BASE = "/api/v1/automations"; + +function rejectIfInvalid<T>( + parsed: { success: true; data: T } | { success: false; error: { issues: { message: string }[] } } +): T { + if (!parsed.success) { + throw new ValidationError( + `Invalid request: ${parsed.error.issues.map((i) => i.message).join(", ")}` + ); + } + return parsed.data; +} + +class AutomationsApiService { + // ---- Automations --------------------------------------------------------- + + listAutomations = async (params: AutomationListParams) => { + const qs = new URLSearchParams({ + search_space_id: String(params.search_space_id), + limit: String(params.limit), + offset: String(params.offset), + }); + return baseApiService.get(`${BASE}?${qs.toString()}`, automationListResponse); + }; + + getAutomation = async (automationId: number) => { + return baseApiService.get(`${BASE}/${automationId}`, automation); + }; + + createAutomation = async (request: AutomationCreateRequest) => { + const data = rejectIfInvalid(automationCreateRequest.safeParse(request)); + return baseApiService.post(BASE, automation, { body: data }); + }; + + updateAutomation = async (automationId: number, request: AutomationUpdateRequest) => { + const data = rejectIfInvalid(automationUpdateRequest.safeParse(request)); + return baseApiService.patch(`${BASE}/${automationId}`, automation, { body: data }); + }; + + // Server returns 204; baseApiService now resolves to null and skips schema validation. + deleteAutomation = async (automationId: number) => { + return baseApiService.delete(`${BASE}/${automationId}`); + }; + + // ---- Triggers (sub-resource) -------------------------------------------- + + addTrigger = async (automationId: number, request: TriggerCreateRequest) => { + const data = rejectIfInvalid(triggerCreateRequest.safeParse(request)); + return baseApiService.post(`${BASE}/${automationId}/triggers`, trigger, { body: data }); + }; + + updateTrigger = async ( + automationId: number, + triggerId: number, + request: TriggerUpdateRequest + ) => { + const data = rejectIfInvalid(triggerUpdateRequest.safeParse(request)); + return baseApiService.patch(`${BASE}/${automationId}/triggers/${triggerId}`, trigger, { + body: data, + }); + }; + + removeTrigger = async (automationId: number, triggerId: number) => { + return baseApiService.delete(`${BASE}/${automationId}/triggers/${triggerId}`); + }; + + // ---- Runs (sub-resource, read-only) ------------------------------------- + + listRuns = async (automationId: number, params: RunListParams) => { + const qs = new URLSearchParams({ + limit: String(params.limit), + offset: String(params.offset), + }); + return baseApiService.get(`${BASE}/${automationId}/runs?${qs.toString()}`, runListResponse); + }; + + getRun = async (automationId: number, runId: number) => { + return baseApiService.get(`${BASE}/${automationId}/runs/${runId}`, run); + }; +} + +export const automationsApiService = new AutomationsApiService(); diff --git a/surfsense_web/lib/query-client/cache-keys.ts b/surfsense_web/lib/query-client/cache-keys.ts index ce45ee143..8943d6842 100644 --- a/surfsense_web/lib/query-client/cache-keys.ts +++ b/surfsense_web/lib/query-client/cache-keys.ts @@ -126,4 +126,14 @@ export const cacheKeys = { batchUnreadCounts: (searchSpaceId: number | null) => ["notifications", "unread-counts-batch", searchSpaceId] as const, }, + automations: { + // list endpoint is keyed by pagination too so distinct pages don't collide + list: (searchSpaceId: number, limit: number, offset: number) => + ["automations", "list", searchSpaceId, limit, offset] as const, + detail: (automationId: number) => ["automations", "detail", automationId] as const, + runs: (automationId: number, limit: number, offset: number) => + ["automations", "runs", automationId, limit, offset] as const, + run: (automationId: number, runId: number) => + ["automations", "runs", automationId, runId] as const, + }, }; From fe28833ad47bcff944a44ecdafde394d3247de96 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 01:02:48 +0200 Subject: [PATCH 134/198] feat(web): automations list page with status, pause/resume and delete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vertical slice at /dashboard/[id]/automations. The page is read-only by default; every action gates on backend automations:* permissions via a co-located permissions hook so adding/removing surfaces stays a one-file change. Route: - page.tsx — server boundary; extracts search_space_id. - automations-content.tsx — client orchestrator (loading / no-access / error / empty / table branches). Components (one concern per file): - automations-header.tsx — title + count + "Create via chat" CTA. - automations-table.tsx + automation-row.tsx — name/status/updated columns; row name links to detail (PR4). - automation-status-badge.tsx — active / paused / archived pill. - automation-row-actions.tsx — ⋯ menu with pause/resume + delete, gated on canUpdate / canDelete. Archived rows hide the toggle. - delete-automation-dialog.tsx — destructive confirm; mentions FK cascade explicitly so users know triggers/runs go too. - automations-empty-state.tsx — zero-state pointing to chat (creation is intent-driven via the create_automation HITL tool, not a form). - automations-loading.tsx — skeleton rows in the same shell so the layout doesn't shift on data arrival. - automation-triggers-summary.tsx — small cron-describer (daily, weekdays, weekly, monthly, hourly) + timezone for the detail page. Kept inline since v1 only registers schedule. Hooks: - use-automation-permissions.ts — single source of truth for the slice's canCreate/canRead/canUpdate/canDelete/canExecute gates, backed by myAccessAtom. Pause/resume and delete reuse the PR2 mutation atoms, so list + detail caches stay coherent without bespoke invalidation. Out of scope (later PRs): - detail route (definition viewer + triggers manager) — PR4 - raw JSON editor — PR5 - nav entry / sidebar wiring — small follow-up PR --- .../automations/automations-content.tsx | 101 +++++++++++++++ .../components/automation-row-actions.tsx | 98 ++++++++++++++ .../automations/components/automation-row.tsx | 61 +++++++++ .../components/automation-status-badge.tsx | 49 +++++++ .../automation-triggers-summary.tsx | 120 ++++++++++++++++++ .../components/automations-empty-state.tsx | 42 ++++++ .../components/automations-header.tsx | 44 +++++++ .../components/automations-loading.tsx | 36 ++++++ .../components/automations-table.tsx | 73 +++++++++++ .../components/delete-automation-dialog.tsx | 80 ++++++++++++ .../hooks/use-automation-permissions.ts | 37 ++++++ .../[search_space_id]/automations/page.tsx | 15 +++ 12 files changed, 756 insertions(+) create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row-actions.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-status-badge.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-loading.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-table.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/hooks/use-automation-permissions.ts create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/page.tsx diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx new file mode 100644 index 000000000..fa1caff96 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx @@ -0,0 +1,101 @@ +"use client"; +import { ShieldAlert } from "lucide-react"; +import { useAutomations } from "@/hooks/use-automations"; +import { AutomationsEmptyState } from "./components/automations-empty-state"; +import { AutomationsHeader } from "./components/automations-header"; +import { AutomationsTable } from "./components/automations-table"; +import { useAutomationPermissions } from "./hooks/use-automation-permissions"; + +interface AutomationsContentProps { + searchSpaceId: number; +} + +/** + * Client orchestrator for the automations list page. Pulls the active + * search space's first page (via ``useAutomations`` → ``automationsListAtom``) + * and the user's permissions, then decides between empty / loading / table. + * + * Read access is mandatory; anything else is hidden behind RBAC. The + * permissions hook is co-located in this slice so adding/removing + * surfaces is a one-file change. + */ +export function AutomationsContent({ searchSpaceId }: AutomationsContentProps) { + const { automations, total, loading, error } = useAutomations(); + const perms = useAutomationPermissions(); + + if (perms.loading) { + // Permissions gate the entire page; defer everything until we know. + return ( + <> + <AutomationsHeader searchSpaceId={searchSpaceId} total={0} loading canCreate={false} /> + <AutomationsTable + automations={[]} + searchSpaceId={searchSpaceId} + loading + canUpdate={false} + canDelete={false} + /> + </> + ); + } + + if (!perms.canRead) { + return ( + <div className="rounded-lg border border-border/60 bg-muted/20 px-6 py-12 text-center"> + <ShieldAlert className="mx-auto h-10 w-10 text-muted-foreground" aria-hidden /> + <h2 className="mt-3 text-base font-semibold text-foreground">Access denied</h2> + <p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto"> + You don't have permission to view automations in this search space. + </p> + </div> + ); + } + + if (error) { + return ( + <> + <AutomationsHeader + searchSpaceId={searchSpaceId} + total={0} + loading={false} + canCreate={perms.canCreate} + /> + <div className="rounded-lg border border-destructive/40 bg-destructive/5 px-6 py-8 text-center"> + <p className="text-sm text-destructive">Couldn't load automations. {error.message}</p> + </div> + </> + ); + } + + if (!loading && automations.length === 0) { + return ( + <> + <AutomationsHeader + searchSpaceId={searchSpaceId} + total={0} + loading={false} + canCreate={perms.canCreate} + /> + <AutomationsEmptyState searchSpaceId={searchSpaceId} canCreate={perms.canCreate} /> + </> + ); + } + + return ( + <> + <AutomationsHeader + searchSpaceId={searchSpaceId} + total={total} + loading={loading} + canCreate={perms.canCreate} + /> + <AutomationsTable + automations={automations} + searchSpaceId={searchSpaceId} + loading={loading} + canUpdate={perms.canUpdate} + canDelete={perms.canDelete} + /> + </> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row-actions.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row-actions.tsx new file mode 100644 index 000000000..229a417dc --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row-actions.tsx @@ -0,0 +1,98 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { MoreHorizontal, Pause, Play, Trash2 } from "lucide-react"; +import { useState } from "react"; +import { updateAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import type { AutomationSummary } from "@/contracts/types/automation.types"; +import { DeleteAutomationDialog } from "./delete-automation-dialog"; + +interface AutomationRowActionsProps { + automation: AutomationSummary; + searchSpaceId: number; + canUpdate: boolean; + canDelete: boolean; +} + +/** + * Three-dot menu on each row: pause/resume (if updatable) and delete + * (if deletable). The menu itself is hidden when the user has neither + * permission so we don't render an empty trigger. + */ +export function AutomationRowActions({ + automation, + searchSpaceId, + canUpdate, + canDelete, +}: AutomationRowActionsProps) { + const { mutateAsync: updateAutomation, isPending: updating } = useAtomValue( + updateAutomationMutationAtom + ); + const [deleteOpen, setDeleteOpen] = useState(false); + + if (!canUpdate && !canDelete) return null; + + const nextStatus = automation.status === "active" ? "paused" : "active"; + const pauseLabel = automation.status === "active" ? "Pause" : "Resume"; + const PauseIcon = automation.status === "active" ? Pause : Play; + const canToggle = canUpdate && automation.status !== "archived"; + + async function handleTogglePause() { + await updateAutomation({ + automationId: automation.id, + patch: { status: nextStatus }, + }); + } + + return ( + <> + <DropdownMenu> + <DropdownMenuTrigger asChild> + <Button + variant="ghost" + size="icon" + className="h-8 w-8" + aria-label={`Actions for ${automation.name}`} + > + <MoreHorizontal className="h-4 w-4" /> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end" className="w-40"> + {canToggle && ( + <DropdownMenuItem onSelect={handleTogglePause} disabled={updating}> + <PauseIcon className="mr-2 h-4 w-4" /> + {pauseLabel} + </DropdownMenuItem> + )} + {canToggle && canDelete && <DropdownMenuSeparator />} + {canDelete && ( + <DropdownMenuItem + onSelect={() => setDeleteOpen(true)} + className="text-destructive focus:text-destructive" + > + <Trash2 className="mr-2 h-4 w-4" /> + Delete + </DropdownMenuItem> + )} + </DropdownMenuContent> + </DropdownMenu> + + {canDelete && ( + <DeleteAutomationDialog + open={deleteOpen} + onOpenChange={setDeleteOpen} + automationId={automation.id} + automationName={automation.name} + searchSpaceId={searchSpaceId} + /> + )} + </> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row.tsx new file mode 100644 index 000000000..a59fb4527 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-row.tsx @@ -0,0 +1,61 @@ +"use client"; +import Link from "next/link"; +import { TableCell, TableRow } from "@/components/ui/table"; +import type { AutomationSummary } from "@/contracts/types/automation.types"; +import { formatRelativeDate } from "@/lib/format-date"; +import { AutomationRowActions } from "./automation-row-actions"; +import { AutomationStatusBadge } from "./automation-status-badge"; + +interface AutomationRowProps { + automation: AutomationSummary; + searchSpaceId: number; + canUpdate: boolean; + canDelete: boolean; +} + +/** + * One row in the automations table. The name links to the detail page; + * actions are gated by ``canUpdate`` / ``canDelete``. Trigger summary + * is intentionally left to the detail page — list responses don't + * include triggers and we want to avoid N+1 detail fetches. + */ +export function AutomationRow({ + automation, + searchSpaceId, + canUpdate, + canDelete, +}: AutomationRowProps) { + return ( + <TableRow className="border-b border-border/60 hover:bg-muted/40"> + <TableCell className="px-4 md:px-6 py-3 border-r border-border/60"> + <div className="flex flex-col gap-0.5 min-w-0"> + <Link + href={`/dashboard/${searchSpaceId}/automations/${automation.id}`} + className="text-sm font-medium text-foreground hover:underline truncate" + > + {automation.name} + </Link> + {automation.description && ( + <span className="text-xs text-muted-foreground line-clamp-1"> + {automation.description} + </span> + )} + </div> + </TableCell> + <TableCell className="px-4 py-3 border-r border-border/60 w-32"> + <AutomationStatusBadge status={automation.status} /> + </TableCell> + <TableCell className="hidden md:table-cell px-4 py-3 border-r border-border/60 w-40 text-xs text-muted-foreground"> + {formatRelativeDate(automation.updated_at)} + </TableCell> + <TableCell className="px-4 md:px-6 py-3 w-16 text-right"> + <AutomationRowActions + automation={automation} + searchSpaceId={searchSpaceId} + canUpdate={canUpdate} + canDelete={canDelete} + /> + </TableCell> + </TableRow> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-status-badge.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-status-badge.tsx new file mode 100644 index 000000000..ecf171e78 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-status-badge.tsx @@ -0,0 +1,49 @@ +"use client"; +import { Archive, CircleDot, Pause } from "lucide-react"; +import type { AutomationStatus } from "@/contracts/types/automation.types"; +import { cn } from "@/lib/utils"; + +interface AutomationStatusBadgeProps { + status: AutomationStatus; + className?: string; +} + +// Color + icon per status. Active = green, paused = amber, archived = muted. +const STATUS_STYLES: Record< + AutomationStatus, + { label: string; icon: typeof CircleDot; classes: string } +> = { + active: { + label: "Active", + icon: CircleDot, + classes: + "bg-emerald-50 text-emerald-700 border border-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-900/50", + }, + paused: { + label: "Paused", + icon: Pause, + classes: + "bg-amber-50 text-amber-700 border border-amber-200 dark:bg-amber-950/40 dark:text-amber-300 dark:border-amber-900/50", + }, + archived: { + label: "Archived", + icon: Archive, + classes: "bg-muted text-muted-foreground border border-border/60", + }, +}; + +export function AutomationStatusBadge({ status, className }: AutomationStatusBadgeProps) { + const { label, icon: Icon, classes } = STATUS_STYLES[status]; + return ( + <span + className={cn( + "inline-flex items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium", + classes, + className + )} + > + <Icon className="h-3 w-3" aria-hidden /> + {label} + </span> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx new file mode 100644 index 000000000..ac27b01e2 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx @@ -0,0 +1,120 @@ +"use client"; +import { CalendarClock, Pause } from "lucide-react"; +import type { Trigger } from "@/contracts/types/automation.types"; + +interface AutomationTriggersSummaryProps { + triggers: Trigger[]; +} + +/** + * One-line summary of an automation's triggers for the list view. + * + * v1 only registers ``schedule`` so this stays compact: + * - 0 triggers → "No triggers" + * - 1 schedule trigger → "Mon–Fri at 09:00 · UTC" + disabled badge if off + * - >1 → "N triggers" + * + * The detail page renders the full per-trigger editor. + */ +export function AutomationTriggersSummary({ triggers }: AutomationTriggersSummaryProps) { + if (triggers.length === 0) { + return <span className="text-xs text-muted-foreground">No triggers</span>; + } + + if (triggers.length > 1) { + return <span className="text-xs text-muted-foreground">{triggers.length} triggers</span>; + } + + const [trigger] = triggers; + + if (trigger.type === "schedule") { + const cron = typeof trigger.params.cron === "string" ? trigger.params.cron : undefined; + const tz = typeof trigger.params.timezone === "string" ? trigger.params.timezone : "UTC"; + const human = cron ? describeCron(cron) : "Schedule"; + + return ( + <span className="inline-flex items-center gap-1.5 text-xs"> + <CalendarClock className="h-3.5 w-3.5 text-muted-foreground" aria-hidden /> + <span className="text-foreground">{human}</span> + <span className="text-muted-foreground">· {tz}</span> + {!trigger.enabled && ( + <span className="inline-flex items-center gap-1 rounded-md border border-border/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground"> + <Pause className="h-2.5 w-2.5" aria-hidden /> + Off + </span> + )} + </span> + ); + } + + return <span className="text-xs text-muted-foreground capitalize">{trigger.type}</span>; +} + +// ---------------------------------------------------------------------------- +// Minimal cron describer for the common 5-field patterns SurfSense automations +// surface today. Falls back to the raw expression when unrecognized so the user +// still sees something honest instead of a guess. +// +// Kept inline (not a library) because: +// - v1 only needs to recognize a small set of patterns produced by the +// drafter LLM (hourly/daily/weekdays/weekly/monthly). +// - All current consumers live in this slice. If reuse grows, lift to +// ``lib/cron-describe.ts``. +// ---------------------------------------------------------------------------- + +const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + +function describeCron(cron: string): string { + const parts = cron.trim().split(/\s+/); + if (parts.length !== 5) return cron; + + const [minute, hour, dom, month, dow] = parts; + + // Daily at H:MM (matches the very common "0 9 * * *") + if (month === "*" && dom === "*" && dow === "*" && /^\d+$/.test(minute) && /^\d+$/.test(hour)) { + return `Daily at ${formatTime(hour, minute)}`; + } + + // Weekdays at H:MM ("0 9 * * 1-5") + if (month === "*" && dom === "*" && dow === "1-5" && /^\d+$/.test(minute) && /^\d+$/.test(hour)) { + return `Mon–Fri at ${formatTime(hour, minute)}`; + } + + // Specific weekday(s) ("0 9 * * 1" or "0 9 * * 1,3,5") + if ( + month === "*" && + dom === "*" && + /^\d+$/.test(minute) && + /^\d+$/.test(hour) && + /^[\d,]+$/.test(dow) + ) { + const days = dow + .split(",") + .map((d) => DAY_NAMES[Number(d) % 7]) + .filter(Boolean) + .join(", "); + if (days) return `${days} at ${formatTime(hour, minute)}`; + } + + // Monthly on day N ("0 9 1 * *") + if ( + month === "*" && + dow === "*" && + /^\d+$/.test(dom) && + /^\d+$/.test(hour) && + /^\d+$/.test(minute) + ) { + return `Day ${dom} of each month at ${formatTime(hour, minute)}`; + } + + // Hourly ("0 * * * *") + if (month === "*" && dom === "*" && dow === "*" && hour === "*" && /^\d+$/.test(minute)) { + return minute === "0" ? "Every hour" : `Every hour at :${minute.padStart(2, "0")}`; + } + + return cron; +} + +function formatTime(hour: string, minute: string): string { + return `${hour.padStart(2, "0")}:${minute.padStart(2, "0")}`; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx new file mode 100644 index 000000000..4004cce9b --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx @@ -0,0 +1,42 @@ +"use client"; +import { MessageSquarePlus, Workflow } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; + +interface AutomationsEmptyStateProps { + searchSpaceId: number; + canCreate: boolean; +} + +/** + * Zero-state for the automations list. The primary CTA points to a new + * chat — creation happens via the ``create_automation`` HITL tool, not a + * "new automation" form. We surface the chat path explicitly so users + * don't go hunting for an "add" button that doesn't exist. + */ +export function AutomationsEmptyState({ searchSpaceId, canCreate }: AutomationsEmptyStateProps) { + return ( + <div className="rounded-lg border border-dashed border-border/60 bg-muted/20 px-6 py-12 text-center"> + <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-muted text-muted-foreground"> + <Workflow className="h-6 w-6" aria-hidden /> + </div> + <h3 className="mt-4 text-base font-semibold text-foreground">No automations yet</h3> + <p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto"> + Automations let SurfSense run agent tasks on a schedule. Describe what you want in chat and + SurfSense drafts the automation for your approval. + </p> + {canCreate ? ( + <Button asChild className="mt-6"> + <Link href={`/dashboard/${searchSpaceId}/new-chat`}> + <MessageSquarePlus className="mr-2 h-4 w-4" /> + Create via chat + </Link> + </Button> + ) : ( + <p className="mt-6 text-xs text-muted-foreground"> + You don't have permission to create automations in this search space. + </p> + )} + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx new file mode 100644 index 000000000..b938825a6 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx @@ -0,0 +1,44 @@ +"use client"; +import { MessageSquarePlus } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; + +interface AutomationsHeaderProps { + searchSpaceId: number; + total: number; + loading: boolean; + canCreate: boolean; +} + +/** + * Page header: title + count + "Create via chat" CTA. Creation is intent-driven + * (the create_automation tool runs inside chat with a HITL approval card), so + * the CTA links to a new chat rather than opening a form. + */ +export function AutomationsHeader({ + searchSpaceId, + total, + loading, + canCreate, +}: AutomationsHeaderProps) { + return ( + <div className="flex items-center justify-between gap-4 flex-wrap"> + <div className="flex items-baseline gap-3"> + <h1 className="text-xl md:text-2xl font-semibold text-foreground">Automations</h1> + {!loading && ( + <span className="text-sm text-muted-foreground"> + {total} {total === 1 ? "automation" : "automations"} + </span> + )} + </div> + {canCreate && ( + <Button asChild size="sm"> + <Link href={`/dashboard/${searchSpaceId}/new-chat`}> + <MessageSquarePlus className="mr-2 h-4 w-4" /> + Create via chat + </Link> + </Button> + )} + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-loading.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-loading.tsx new file mode 100644 index 000000000..1156be3f6 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-loading.tsx @@ -0,0 +1,36 @@ +"use client"; +import { Skeleton } from "@/components/ui/skeleton"; +import { TableCell, TableRow } from "@/components/ui/table"; + +const ROW_KEYS = ["sk-1", "sk-2", "sk-3"]; + +/** + * Skeleton rows for the automations table. Number of rows is fixed since + * we don't know the count ahead of time and three placeholders is enough + * to communicate "loading" without flashing too much chrome. + */ +export function AutomationsLoadingRows() { + return ( + <> + {ROW_KEYS.map((key) => ( + <TableRow key={key} className="border-b border-border/60 hover:bg-transparent"> + <TableCell className="px-4 md:px-6 py-3 border-r border-border/60"> + <div className="flex flex-col gap-1.5"> + <Skeleton className="h-4 w-40" /> + <Skeleton className="h-3 w-56" /> + </div> + </TableCell> + <TableCell className="px-4 py-3 border-r border-border/60 w-32"> + <Skeleton className="h-5 w-16 rounded-md" /> + </TableCell> + <TableCell className="hidden md:table-cell px-4 py-3 border-r border-border/60 w-40"> + <Skeleton className="h-3 w-20" /> + </TableCell> + <TableCell className="px-4 md:px-6 py-3 w-16"> + <Skeleton className="h-8 w-8 rounded-md ml-auto" /> + </TableCell> + </TableRow> + ))} + </> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-table.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-table.tsx new file mode 100644 index 000000000..ec3aeeef5 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-table.tsx @@ -0,0 +1,73 @@ +"use client"; +import { Activity, CalendarDays, Workflow } from "lucide-react"; +import { Table, TableBody, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import type { AutomationSummary } from "@/contracts/types/automation.types"; +import { AutomationRow } from "./automation-row"; +import { AutomationsLoadingRows } from "./automations-loading"; + +interface AutomationsTableProps { + automations: AutomationSummary[]; + searchSpaceId: number; + loading: boolean; + canUpdate: boolean; + canDelete: boolean; +} + +/** + * Table shell + header. Rows render below — loading state renders skeleton + * rows in the same shell so the layout doesn't shift on data arrival. + */ +export function AutomationsTable({ + automations, + searchSpaceId, + loading, + canUpdate, + canDelete, +}: AutomationsTableProps) { + return ( + <div className="rounded-lg border border-border/60 bg-accent overflow-hidden"> + <Table className="table-fixed w-full"> + <TableHeader> + <TableRow className="hover:bg-transparent border-b border-border/60"> + <TableHead className="px-4 md:px-6 border-r border-border/60"> + <span className="flex items-center gap-1.5 text-sm font-medium text-muted-foreground/70"> + <Workflow size={14} className="opacity-60 text-muted-foreground" /> + Name + </span> + </TableHead> + <TableHead className="border-r border-border/60 w-32"> + <span className="flex items-center gap-1.5 text-sm font-medium text-muted-foreground/70"> + <Activity size={14} className="opacity-60 text-muted-foreground" /> + Status + </span> + </TableHead> + <TableHead className="hidden md:table-cell border-r border-border/60 w-40"> + <span className="flex items-center gap-1.5 text-sm font-medium text-muted-foreground/70"> + <CalendarDays size={14} className="opacity-60 text-muted-foreground" /> + Updated + </span> + </TableHead> + <TableHead className="px-4 md:px-6 w-16"> + <span className="sr-only">Actions</span> + </TableHead> + </TableRow> + </TableHeader> + <TableBody> + {loading ? ( + <AutomationsLoadingRows /> + ) : ( + automations.map((automation) => ( + <AutomationRow + key={automation.id} + automation={automation} + searchSpaceId={searchSpaceId} + canUpdate={canUpdate} + canDelete={canDelete} + /> + )) + )} + </TableBody> + </Table> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx new file mode 100644 index 000000000..db73ddad5 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx @@ -0,0 +1,80 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { useState } from "react"; +import { deleteAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { Spinner } from "@/components/ui/spinner"; + +interface DeleteAutomationDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + automationId: number; + automationName: string; + searchSpaceId: number; +} + +/** + * Confirm + delete one automation. FK cascade on the backend wipes attached + * triggers and runs, so we mention it explicitly. List re-fetch is handled + * by the mutation atom's onSuccess. + */ +export function DeleteAutomationDialog({ + open, + onOpenChange, + automationId, + automationName, + searchSpaceId, +}: DeleteAutomationDialogProps) { + const { mutateAsync: deleteAutomation } = useAtomValue(deleteAutomationMutationAtom); + const [submitting, setSubmitting] = useState(false); + + async function handleConfirm() { + setSubmitting(true); + try { + await deleteAutomation({ automationId, searchSpaceId }); + onOpenChange(false); + } finally { + setSubmitting(false); + } + } + + return ( + <AlertDialog open={open} onOpenChange={onOpenChange}> + <AlertDialogContent> + <AlertDialogHeader> + <AlertDialogTitle>Delete this automation?</AlertDialogTitle> + <AlertDialogDescription> + <span className="font-medium text-foreground">{automationName}</span> and all of its + triggers and run history will be removed. This cannot be undone. + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel disabled={submitting}>Cancel</AlertDialogCancel> + <AlertDialogAction + onClick={handleConfirm} + disabled={submitting} + className="bg-destructive text-white hover:bg-destructive/90" + > + {submitting ? ( + <span className="inline-flex items-center gap-2"> + <Spinner size="xs" /> + Deleting… + </span> + ) : ( + "Delete" + )} + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/hooks/use-automation-permissions.ts b/surfsense_web/app/dashboard/[search_space_id]/automations/hooks/use-automation-permissions.ts new file mode 100644 index 000000000..293688710 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/hooks/use-automation-permissions.ts @@ -0,0 +1,37 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { useMemo } from "react"; +import { canPerform, myAccessAtom } from "@/atoms/members/members-query.atoms"; + +/** + * Centralized RBAC gates for the automations slice. Co-located with the + * route so adding/removing surfaces stays a one-file change. Backed by + * the same ``myAccessAtom`` the rest of the app uses; owners short-circuit + * to ``true`` for every action. + * + * Mirrors backend permissions in ``app.db.permissions`` (automations:*). + */ +export interface AutomationPermissions { + loading: boolean; + canCreate: boolean; + canRead: boolean; + canUpdate: boolean; + canDelete: boolean; + canExecute: boolean; +} + +export function useAutomationPermissions(): AutomationPermissions { + const { data: access, isLoading } = useAtomValue(myAccessAtom); + + return useMemo( + () => ({ + loading: isLoading, + canCreate: canPerform(access, "automations:create"), + canRead: canPerform(access, "automations:read"), + canUpdate: canPerform(access, "automations:update"), + canDelete: canPerform(access, "automations:delete"), + canExecute: canPerform(access, "automations:execute"), + }), + [access, isLoading] + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/page.tsx new file mode 100644 index 000000000..b77cb20f4 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/page.tsx @@ -0,0 +1,15 @@ +import { AutomationsContent } from "./automations-content"; + +export default async function AutomationsPage({ + params, +}: { + params: Promise<{ search_space_id: string }>; +}) { + const { search_space_id } = await params; + + return ( + <div className="w-full space-y-6"> + <AutomationsContent searchSpaceId={Number(search_space_id)} /> + </div> + ); +} From 7bc52dcdc04124dfff9b598eee5c33dfed488044 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 01:11:20 +0200 Subject: [PATCH 135/198] feat(web): surface Automations in the sidebar under Inbox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an "Automations" nav entry rendered explicitly between Inbox and (on mobile) Documents, mirroring how those two are pulled out of the nav list and rendered above the chat sections. The icon is Workflow to match settings/RBAC labelling. LayoutDataProvider: - Adds the entry to navItems pointing at /dashboard/[id]/automations. - Marks isActive via pathname so the row highlights on the route. - Tags /automations as a workspace-panel page so it renders in the centered settings-style viewport (same chrome as Team / settings). Sidebar: - Pulls out automationsItem alongside inboxItem and documentsItem. - Renders it between them. - Excludes its URL from footerNavItems so it doesn't double-render. Page-level RBAC still gates the actual view; the sidebar entry is always visible (consistent with Inbox/Documents which are also not gated at the nav layer). Anonymous (FreeLayoutDataProvider) intentionally not touched — automations is an authenticated feature. --- .../layout/providers/LayoutDataProvider.tsx | 34 ++++++++++++++----- .../components/layout/ui/sidebar/Sidebar.tsx | 28 ++++++++++++--- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index 917d1c6e1..67971e435 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -2,7 +2,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useAtom, useAtomValue, useSetAtom } from "jotai"; -import { AlertTriangle, Inbox, LibraryBig } from "lucide-react"; +import { AlertTriangle, Inbox, LibraryBig, Workflow } from "lucide-react"; import { useParams, usePathname, useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useTheme } from "next-themes"; @@ -335,9 +335,10 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid }, [threadsData, searchSpaceId]); // Navigation items - // Inbox is rendered explicitly below "New chat" in the sidebar (it is also - // surfaced in the icon rail's collapsed mode via this list). Announcements - // has been moved to the avatar dropdown and is no longer a nav item. + // Inbox, Automations, and Documents are rendered explicitly below "New chat" + // in the sidebar (also surfaced in the icon rail's collapsed mode via this + // list). Announcements has been moved to the avatar dropdown. + const isAutomationsActive = pathname?.includes("/automations") === true; const navItems: NavItem[] = useMemo( () => ( @@ -349,6 +350,12 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid isActive: isInboxSidebarOpen, badge: totalUnreadCount > 0 ? formatInboxCount(totalUnreadCount) : undefined, }, + { + title: "Automations", + url: `/dashboard/${searchSpaceId}/automations`, + icon: Workflow, + isActive: isAutomationsActive, + }, isMobile ? { title: "Documents", @@ -359,7 +366,14 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid : null, ] as (NavItem | null)[] ).filter((item): item is NavItem => item !== null), - [isMobile, isInboxSidebarOpen, isDocumentsSidebarOpen, totalUnreadCount] + [ + isMobile, + isInboxSidebarOpen, + isDocumentsSidebarOpen, + totalUnreadCount, + searchSpaceId, + isAutomationsActive, + ] ); // Handlers @@ -660,12 +674,14 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid const isUserSettingsPage = pathname?.includes("/user-settings") === true; const isSearchSpaceSettingsPage = pathname?.includes("/search-space-settings") === true; const isTeamPage = pathname?.endsWith("/team") === true; + const isAutomationsPage = pathname?.includes("/automations") === true; const useWorkspacePanel = pathname?.endsWith("/buy-more") === true || pathname?.endsWith("/more-pages") === true || isUserSettingsPage || isSearchSpaceSettingsPage || - isTeamPage; + isTeamPage || + isAutomationsPage; return ( <> @@ -705,12 +721,14 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid isChatPage={isChatPage} useWorkspacePanel={useWorkspacePanel} workspacePanelViewportClassName={ - isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage + isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage || isAutomationsPage ? "items-start justify-center px-6 py-8 md:px-10 md:py-10" : undefined } workspacePanelContentClassName={ - isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage ? "max-w-5xl" : undefined + isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage || isAutomationsPage + ? "max-w-5xl" + : undefined } isLoadingChats={isLoadingThreads} activeSlideoutPanel={activeSlideoutPanel} diff --git a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx index e0cb3072a..805f8bfd3 100644 --- a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx @@ -140,16 +140,26 @@ export function Sidebar({ const t = useTranslations("sidebar"); const [openDropdownChatId, setOpenDropdownChatId] = useState<number | null>(null); - // Inbox and Documents are rendered explicitly right below New Chat. Pull - // them out of the nav items list so they don't also appear in the bottom - // NavSection. Documents is only present in navItems on mobile. + // Inbox, Automations, and Documents are rendered explicitly right below + // New Chat. Pull them out of the nav items list so they don't also appear + // in the bottom NavSection. Documents is only present in navItems on + // mobile; Automations is identified by URL suffix so the same code path + // works across search spaces. const inboxItem = useMemo(() => navItems.find((item) => item.url === "#inbox"), [navItems]); + const automationsItem = useMemo( + () => navItems.find((item) => item.url.endsWith("/automations")), + [navItems] + ); const documentsItem = useMemo( () => navItems.find((item) => item.url === "#documents"), [navItems] ); const footerNavItems = useMemo( - () => navItems.filter((item) => item.url !== "#inbox" && item.url !== "#documents"), + () => + navItems.filter( + (item) => + item.url !== "#inbox" && item.url !== "#documents" && !item.url.endsWith("/automations") + ), [navItems] ); @@ -227,6 +237,16 @@ export function Sidebar({ } /> )} + {automationsItem && ( + <SidebarButton + icon={automationsItem.icon} + label={automationsItem.title} + onClick={() => onNavItemClick?.(automationsItem)} + isCollapsed={isCollapsed} + isActive={automationsItem.isActive} + tooltipContent={isCollapsed ? automationsItem.title : undefined} + /> + )} {documentsItem && ( <SidebarButton icon={documentsItem.icon} From bc3c2fd5152628c0e12a36fccac56a43adc131ed Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 01:14:10 +0200 Subject: [PATCH 136/198] fix(web): hide header Create CTA on the automations empty state The empty-state card already hosts the primary "Create via chat" CTA; keeping the header button on the same screen showed two identical buttons. Adds an optional ``showCreateCta`` prop to AutomationsHeader (default true) and turns it off only in the empty branch so the card stays the focal point. --- .../automations/automations-content.tsx | 1 + .../automations/components/automations-header.tsx | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx index fa1caff96..756221d38 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx @@ -75,6 +75,7 @@ export function AutomationsContent({ searchSpaceId }: AutomationsContentProps) { total={0} loading={false} canCreate={perms.canCreate} + showCreateCta={false} /> <AutomationsEmptyState searchSpaceId={searchSpaceId} canCreate={perms.canCreate} /> </> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx index b938825a6..22ea60664 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx @@ -8,6 +8,12 @@ interface AutomationsHeaderProps { total: number; loading: boolean; canCreate: boolean; + /** + * Render the header's Create CTA. Defaults to true; the empty state owns + * the primary CTA on its own card, so the orchestrator turns this off + * there to avoid a duplicate button. + */ + showCreateCta?: boolean; } /** @@ -20,6 +26,7 @@ export function AutomationsHeader({ total, loading, canCreate, + showCreateCta = true, }: AutomationsHeaderProps) { return ( <div className="flex items-center justify-between gap-4 flex-wrap"> @@ -31,7 +38,7 @@ export function AutomationsHeader({ </span> )} </div> - {canCreate && ( + {canCreate && showCreateCta && ( <Button asChild size="sm"> <Link href={`/dashboard/${searchSpaceId}/new-chat`}> <MessageSquarePlus className="mr-2 h-4 w-4" /> From c0a9ea368f43af57e12342a49d17d102c2642b85 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 01:21:54 +0200 Subject: [PATCH 137/198] feat(web): automations detail page (definition viewer + trigger manager) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vertical slice at /dashboard/[id]/automations/[automation_id]. Branches in the orchestrator are: perms loading → skeleton, no-access → access denied panel, bad id → not-found, fetch loading → skeleton, fetch error → not-found, loaded → header + definition + triggers. Route: - page.tsx — server boundary; extracts both ids. - automation-detail-content.tsx — client orchestrator. Header: - automation-detail-header.tsx — back link, name, status badge, description, pause/resume + delete actions. Delete navigates back to the list via a new onDeleted hook on DeleteAutomationDialog so the list page (where the row just vanishes) stays unaffected. - automation-not-found.tsx — 404/403/NaN-id panel. We don't distinguish missing vs. forbidden in the UI. Definition (read-only in v1): - automation-definition-section.tsx — wrapper Card; renders goal + tags + execution defaults + inputs schema (if present) + plan. - plan-step-card.tsx — one step (when, output_as, retries, timeout, params JSON). - execution-summary.tsx — timeout / max_retries / backoff / concurrency + on_failure step count. - inputs-schema-preview.tsx — formatted JSON of inputs.schema; only rendered when the definition declares inputs. Triggers: - automation-triggers-section.tsx — wrapper Card, "Add via chat" CTA (creation is intent-driven, same philosophy as automations). - trigger-card.tsx — schedule + timezone + cron, last/next fire hints, static_inputs JSON, enable Switch and remove button. - delete-trigger-dialog.tsx — confirm + mutation atom. Shared: - lib/describe-cron.ts — moved out of automation-triggers-summary.tsx so both list and detail can describe schedules consistently (daily/weekdays/weekly/monthly/hourly, raw cron fallback). Loading: - automation-detail-loading.tsx — same shell as the loaded view so the layout doesn't jump on data arrival. RBAC: each interactive surface is independently gated (canUpdate/canDelete/canCreate) so the orchestrator stays thin and the component tree is self-documenting about what each action requires. Out of scope (later PRs): - Editing definition / trigger params (raw-JSON path) — PR5 - Run history — PR6 --- .../automation-detail-content.tsx | 86 ++++++++++ .../automation-definition-section.tsx | 99 ++++++++++++ .../components/automation-detail-header.tsx | 129 +++++++++++++++ .../components/automation-detail-loading.tsx | 42 +++++ .../components/automation-not-found.tsx | 34 ++++ .../automation-triggers-section.tsx | 75 +++++++++ .../components/delete-trigger-dialog.tsx | 80 +++++++++ .../components/execution-summary.tsx | 37 +++++ .../components/inputs-schema-preview.tsx | 20 +++ .../components/plan-step-card.tsx | 73 +++++++++ .../components/trigger-card.tsx | 152 ++++++++++++++++++ .../automations/[automation_id]/page.tsx | 18 +++ .../automation-triggers-summary.tsx | 70 +------- .../components/delete-automation-dialog.tsx | 8 + .../automations/lib/describe-cron.ts | 66 ++++++++ 15 files changed, 920 insertions(+), 69 deletions(-) create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-not-found.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/delete-trigger-dialog.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/execution-summary.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/page.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/lib/describe-cron.ts diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx new file mode 100644 index 000000000..a82887721 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx @@ -0,0 +1,86 @@ +"use client"; +import { ShieldAlert } from "lucide-react"; +import { useAutomation } from "@/hooks/use-automation"; +import { useAutomationPermissions } from "../hooks/use-automation-permissions"; +import { AutomationDefinitionSection } from "./components/automation-definition-section"; +import { AutomationDetailHeader } from "./components/automation-detail-header"; +import { AutomationDetailLoading } from "./components/automation-detail-loading"; +import { AutomationNotFound } from "./components/automation-not-found"; +import { AutomationTriggersSection } from "./components/automation-triggers-section"; + +interface AutomationDetailContentProps { + searchSpaceId: number; + automationId: number; +} + +/** + * Client orchestrator for one automation's detail view. Branches: + * - permissions loading → skeleton + * - no read permission → access denied panel + * - bad id (NaN) → not-found panel + * - detail fetching → skeleton + * - detail error / null → not-found panel (we don't distinguish 404 + * from 403 in the UI) + * - detail loaded → header + definition + triggers + * + * Each child component is gated independently on the relevant permission + * so the orchestrator stays thin. + */ +export function AutomationDetailContent({ + searchSpaceId, + automationId, +}: AutomationDetailContentProps) { + const perms = useAutomationPermissions(); + const validId = Number.isInteger(automationId) && automationId > 0; + const { data: automation, isLoading, error } = useAutomation(validId ? automationId : undefined); + + if (perms.loading) { + return <AutomationDetailLoading />; + } + + if (!perms.canRead) { + return ( + <div className="rounded-lg border border-border/60 bg-muted/20 px-6 py-12 text-center"> + <ShieldAlert className="mx-auto h-10 w-10 text-muted-foreground" aria-hidden /> + <h2 className="mt-3 text-base font-semibold text-foreground">Access denied</h2> + <p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto"> + You don't have permission to view automations in this search space. + </p> + </div> + ); + } + + if (!validId) { + return <AutomationNotFound searchSpaceId={searchSpaceId} />; + } + + if (isLoading) { + return <AutomationDetailLoading />; + } + + if (error || !automation) { + return <AutomationNotFound searchSpaceId={searchSpaceId} error={error} />; + } + + return ( + <> + <AutomationDetailHeader + automation={automation} + searchSpaceId={searchSpaceId} + canUpdate={perms.canUpdate} + canDelete={perms.canDelete} + /> + + <AutomationDefinitionSection definition={automation.definition} /> + + <AutomationTriggersSection + triggers={automation.triggers} + automationId={automation.id} + searchSpaceId={searchSpaceId} + canUpdate={perms.canUpdate} + canDelete={perms.canDelete} + canCreate={perms.canCreate} + /> + </> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx new file mode 100644 index 000000000..9545f363b --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx @@ -0,0 +1,99 @@ +"use client"; +import { ListOrdered, Settings2, Tag, Target } from "lucide-react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import type { AutomationDefinition } from "@/contracts/types/automation.types"; +import { ExecutionSummary } from "./execution-summary"; +import { InputsSchemaPreview } from "./inputs-schema-preview"; +import { PlanStepCard } from "./plan-step-card"; + +interface AutomationDefinitionSectionProps { + definition: AutomationDefinition; +} + +/** + * The Definition card. Read-only in v1 — editing definitions happens via + * chat (re-run create_automation with a refined intent) or, later, via + * the raw-JSON path. Layout is top-down: + * goal → tags → execution defaults → inputs schema (if any) → plan + * + * The schema_version is rendered as a small badge next to the section + * title so it's discoverable but doesn't fight for attention. + */ +export function AutomationDefinitionSection({ definition }: AutomationDefinitionSectionProps) { + const hasTags = definition.metadata.tags.length > 0; + const hasInputs = !!definition.inputs; + + return ( + <Card> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> + <CardTitle className="text-base font-semibold">Definition</CardTitle> + <span className="text-xs font-mono text-muted-foreground border border-border/60 rounded px-1.5 py-0.5"> + v{definition.schema_version} + </span> + </CardHeader> + <CardContent className="space-y-6"> + {definition.goal && ( + <Field icon={Target} label="Goal"> + <p className="text-sm text-foreground">{definition.goal}</p> + </Field> + )} + + {hasTags && ( + <Field icon={Tag} label="Tags"> + <div className="flex flex-wrap gap-1.5"> + {definition.metadata.tags.map((tag) => ( + <span + key={tag} + className="inline-flex items-center rounded-md bg-muted px-2 py-0.5 text-xs text-muted-foreground" + > + {tag} + </span> + ))} + </div> + </Field> + )} + + <Field icon={Settings2} label="Execution defaults"> + <ExecutionSummary execution={definition.execution} /> + </Field> + + {hasInputs && ( + <Field icon={Settings2} label="Inputs schema"> + {definition.inputs && <InputsSchemaPreview inputs={definition.inputs} />} + </Field> + )} + + <Field + icon={ListOrdered} + label={`Plan · ${definition.plan.length} step${definition.plan.length === 1 ? "" : "s"}`} + > + <div className="space-y-2"> + {definition.plan.map((step, idx) => ( + <PlanStepCard key={step.step_id} step={step} index={idx} /> + ))} + </div> + </Field> + </CardContent> + </Card> + ); +} + +function Field({ + icon: Icon, + label, + children, +}: { + icon: typeof Target; + label: string; + children: React.ReactNode; +}) { + return ( + <div className="space-y-2"> + <div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground uppercase tracking-wider"> + <Icon className="h-3.5 w-3.5" aria-hidden /> + {label} + </div> + {children} + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx new file mode 100644 index 000000000..4cf3efcc1 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx @@ -0,0 +1,129 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { ArrowLeft, Pause, Play, Trash2 } from "lucide-react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useCallback, useState } from "react"; +import { updateAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { Button } from "@/components/ui/button"; +import { Spinner } from "@/components/ui/spinner"; +import type { Automation } from "@/contracts/types/automation.types"; +import { AutomationStatusBadge } from "../../components/automation-status-badge"; +import { DeleteAutomationDialog } from "../../components/delete-automation-dialog"; + +interface AutomationDetailHeaderProps { + automation: Automation; + searchSpaceId: number; + canUpdate: boolean; + canDelete: boolean; +} + +/** + * Title bar for the detail page: back link, name, status badge, + * description, and the two destructive-ish primary actions (pause / + * resume + delete). Same mutation atoms as the list-row actions to + * keep caches coherent. + * + * Archived automations hide the pause/resume toggle (we don't unarchive + * here — that flow comes later if we need it). + */ +export function AutomationDetailHeader({ + automation, + searchSpaceId, + canUpdate, + canDelete, +}: AutomationDetailHeaderProps) { + const router = useRouter(); + const { mutateAsync: updateAutomation, isPending: updating } = useAtomValue( + updateAutomationMutationAtom + ); + const [deleteOpen, setDeleteOpen] = useState(false); + + const canToggle = canUpdate && automation.status !== "archived"; + const nextStatus = automation.status === "active" ? "paused" : "active"; + const pauseLabel = automation.status === "active" ? "Pause" : "Resume"; + const PauseIcon = automation.status === "active" ? Pause : Play; + + const handleDeleted = useCallback(() => { + router.push(`/dashboard/${searchSpaceId}/automations`); + }, [router, searchSpaceId]); + + async function handleTogglePause() { + await updateAutomation({ + automationId: automation.id, + patch: { status: nextStatus }, + }); + } + + return ( + <> + <div className="space-y-3"> + <Button asChild variant="ghost" size="sm" className="-ml-2 h-auto px-2 py-1"> + <Link + href={`/dashboard/${searchSpaceId}/automations`} + className="text-xs text-muted-foreground" + > + <ArrowLeft className="mr-1.5 h-3.5 w-3.5" /> + Back to automations + </Link> + </Button> + + <div className="flex items-start justify-between gap-4 flex-wrap"> + <div className="space-y-2 min-w-0 flex-1"> + <div className="flex items-center gap-3 flex-wrap"> + <h1 className="text-xl md:text-2xl font-semibold text-foreground break-words"> + {automation.name} + </h1> + <AutomationStatusBadge status={automation.status} /> + </div> + {automation.description && ( + <p className="text-sm text-muted-foreground max-w-3xl">{automation.description}</p> + )} + </div> + + <div className="flex items-center gap-2 shrink-0"> + {canToggle && ( + <Button + type="button" + variant="outline" + size="sm" + onClick={handleTogglePause} + disabled={updating} + > + {updating ? ( + <Spinner size="xs" className="mr-2" /> + ) : ( + <PauseIcon className="mr-2 h-4 w-4" /> + )} + {pauseLabel} + </Button> + )} + {canDelete && ( + <Button + type="button" + variant="outline" + size="sm" + onClick={() => setDeleteOpen(true)} + className="text-destructive hover:text-destructive hover:bg-destructive/10" + > + <Trash2 className="mr-2 h-4 w-4" /> + Delete + </Button> + )} + </div> + </div> + </div> + + {canDelete && ( + <DeleteAutomationDialog + open={deleteOpen} + onOpenChange={setDeleteOpen} + automationId={automation.id} + automationName={automation.name} + searchSpaceId={searchSpaceId} + onDeleted={handleDeleted} + /> + )} + </> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx new file mode 100644 index 000000000..1d01305ee --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx @@ -0,0 +1,42 @@ +"use client"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; +import { Skeleton } from "@/components/ui/skeleton"; + +/** + * Skeleton for the detail page. Same shell as the loaded view (header + + * two stacked cards) so the layout doesn't jump on data arrival. + */ +export function AutomationDetailLoading() { + return ( + <div className="space-y-6"> + <div className="space-y-3"> + <Skeleton className="h-4 w-32" /> + <div className="flex items-center gap-3"> + <Skeleton className="h-7 w-64" /> + <Skeleton className="h-5 w-16 rounded-md" /> + </div> + <Skeleton className="h-4 w-96" /> + </div> + + <Card> + <CardHeader> + <Skeleton className="h-5 w-32" /> + </CardHeader> + <CardContent className="space-y-4"> + <Skeleton className="h-4 w-3/4" /> + <Skeleton className="h-4 w-1/2" /> + <Skeleton className="h-24 w-full" /> + </CardContent> + </Card> + + <Card> + <CardHeader> + <Skeleton className="h-5 w-24" /> + </CardHeader> + <CardContent> + <Skeleton className="h-20 w-full" /> + </CardContent> + </Card> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-not-found.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-not-found.tsx new file mode 100644 index 000000000..1681caf25 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-not-found.tsx @@ -0,0 +1,34 @@ +"use client"; +import { ArrowLeft, FileWarning } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; + +interface AutomationNotFoundProps { + searchSpaceId: number; + error?: Error | null; +} + +/** + * Rendered when the detail fetch fails (404 / 403 / network) or the id + * is not a number. We don't distinguish "missing" from "forbidden" in the + * UI on purpose — leaking that an id exists you can't read is worse than + * a vague message. + */ +export function AutomationNotFound({ searchSpaceId, error }: AutomationNotFoundProps) { + return ( + <div className="rounded-lg border border-border/60 bg-muted/20 px-6 py-12 text-center"> + <FileWarning className="mx-auto h-10 w-10 text-muted-foreground" aria-hidden /> + <h2 className="mt-3 text-base font-semibold text-foreground">Automation not found</h2> + <p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto"> + This automation doesn't exist or you don't have access to it. + {error?.message ? ` (${error.message})` : null} + </p> + <Button asChild variant="outline" size="sm" className="mt-6"> + <Link href={`/dashboard/${searchSpaceId}/automations`}> + <ArrowLeft className="mr-2 h-4 w-4" /> + Back to automations + </Link> + </Button> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx new file mode 100644 index 000000000..8cc62f5c8 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx @@ -0,0 +1,75 @@ +"use client"; +import { CalendarClock, MessageSquarePlus } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import type { Trigger } from "@/contracts/types/automation.types"; +import { TriggerCard } from "./trigger-card"; + +interface AutomationTriggersSectionProps { + triggers: Trigger[]; + automationId: number; + searchSpaceId: number; + canUpdate: boolean; + canDelete: boolean; + canCreate: boolean; +} + +/** + * The Triggers card. Lists each attached trigger with its own enable + * toggle and remove button. Adding a new trigger is intent-driven (via + * chat) for v1 — same philosophy as creating an automation, so the + * empty/add CTA links to a new chat rather than opening a form. + */ +export function AutomationTriggersSection({ + triggers, + automationId, + searchSpaceId, + canUpdate, + canDelete, + canCreate, +}: AutomationTriggersSectionProps) { + return ( + <Card> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> + <div className="space-y-1"> + <CardTitle className="text-base font-semibold">Triggers</CardTitle> + <p className="text-xs text-muted-foreground"> + When this automation fires. v1 supports scheduled triggers only. + </p> + </div> + {canCreate && ( + <Button asChild variant="outline" size="sm"> + <Link href={`/dashboard/${searchSpaceId}/new-chat`}> + <MessageSquarePlus className="mr-2 h-4 w-4" /> + Add via chat + </Link> + </Button> + )} + </CardHeader> + <CardContent> + {triggers.length === 0 ? ( + <div className="rounded-md border border-dashed border-border/60 bg-muted/20 px-4 py-8 text-center"> + <CalendarClock className="mx-auto h-8 w-8 text-muted-foreground" aria-hidden /> + <p className="mt-2 text-sm font-medium text-foreground">No triggers attached</p> + <p className="mt-1 text-xs text-muted-foreground"> + This automation can still be invoked, but nothing will fire it on its own. + </p> + </div> + ) : ( + <div className="space-y-3"> + {triggers.map((trigger) => ( + <TriggerCard + key={trigger.id} + trigger={trigger} + automationId={automationId} + canUpdate={canUpdate} + canDelete={canDelete} + /> + ))} + </div> + )} + </CardContent> + </Card> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/delete-trigger-dialog.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/delete-trigger-dialog.tsx new file mode 100644 index 000000000..71e905724 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/delete-trigger-dialog.tsx @@ -0,0 +1,80 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { useState } from "react"; +import { removeTriggerMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { Spinner } from "@/components/ui/spinner"; + +interface DeleteTriggerDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + automationId: number; + triggerId: number; + triggerLabel: string; +} + +/** + * Confirm + detach one trigger from its automation. The automation itself + * is untouched; only this trigger row is removed. The mutation atom + * invalidates the parent automation detail so the page rerenders. + */ +export function DeleteTriggerDialog({ + open, + onOpenChange, + automationId, + triggerId, + triggerLabel, +}: DeleteTriggerDialogProps) { + const { mutateAsync: removeTrigger } = useAtomValue(removeTriggerMutationAtom); + const [submitting, setSubmitting] = useState(false); + + async function handleConfirm() { + setSubmitting(true); + try { + await removeTrigger({ automationId, triggerId }); + onOpenChange(false); + } finally { + setSubmitting(false); + } + } + + return ( + <AlertDialog open={open} onOpenChange={onOpenChange}> + <AlertDialogContent> + <AlertDialogHeader> + <AlertDialogTitle>Remove this trigger?</AlertDialogTitle> + <AlertDialogDescription> + <span className="font-medium text-foreground">{triggerLabel}</span> will be detached. + The automation itself stays, but it won't fire on this trigger anymore. + </AlertDialogDescription> + </AlertDialogHeader> + <AlertDialogFooter> + <AlertDialogCancel disabled={submitting}>Cancel</AlertDialogCancel> + <AlertDialogAction + onClick={handleConfirm} + disabled={submitting} + className="bg-destructive text-white hover:bg-destructive/90" + > + {submitting ? ( + <span className="inline-flex items-center gap-2"> + <Spinner size="xs" /> + Removing… + </span> + ) : ( + "Remove" + )} + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/execution-summary.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/execution-summary.tsx new file mode 100644 index 000000000..5c4dc381c --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/execution-summary.tsx @@ -0,0 +1,37 @@ +"use client"; +import type { Execution } from "@/contracts/types/automation.types"; + +interface ExecutionSummaryProps { + execution: Execution; +} + +/** + * Compact view of an automation's execution defaults (wall-clock cap, + * retries, backoff, concurrency, on_failure presence). Per-step overrides + * are shown inside each PlanStepCard, not here. + */ +export function ExecutionSummary({ execution }: ExecutionSummaryProps) { + return ( + <dl className="grid grid-cols-2 md:grid-cols-4 gap-x-6 gap-y-2 text-xs"> + <Item label="Timeout" value={`${execution.timeout_seconds}s`} /> + <Item label="Max retries" value={String(execution.max_retries)} /> + <Item label="Retry backoff" value={execution.retry_backoff} /> + <Item label="Concurrency" value={execution.concurrency} /> + {execution.on_failure.length > 0 && ( + <Item + label="On failure" + value={`${execution.on_failure.length} step${execution.on_failure.length === 1 ? "" : "s"}`} + /> + )} + </dl> + ); +} + +function Item({ label, value }: { label: string; value: string }) { + return ( + <div className="flex flex-col gap-0.5 min-w-0"> + <dt className="text-muted-foreground">{label}</dt> + <dd className="text-foreground font-medium truncate">{value}</dd> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx new file mode 100644 index 000000000..bf2db8986 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx @@ -0,0 +1,20 @@ +"use client"; +import type { Inputs } from "@/contracts/types/automation.types"; + +interface InputsSchemaPreviewProps { + inputs: Inputs; +} + +/** + * Read-only JSON preview of an automation's accepted-inputs schema. + * Most automations don't define inputs (defaults are baked into the + * trigger's static_inputs), so the parent skips rendering this card + * when ``inputs`` is null. + */ +export function InputsSchemaPreview({ inputs }: InputsSchemaPreviewProps) { + return ( + <pre className="rounded-md bg-muted/40 px-3 py-2 text-xs font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words max-h-72"> + {JSON.stringify(inputs.schema, null, 2)} + </pre> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx new file mode 100644 index 000000000..3feb77712 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx @@ -0,0 +1,73 @@ +"use client"; +import { ArrowRightCircle, GitCommitHorizontal } from "lucide-react"; +import type { PlanStep } from "@/contracts/types/automation.types"; + +interface PlanStepCardProps { + step: PlanStep; + index: number; +} + +/** + * Read-only view of one plan step. Renders the step_id + action prominently, + * then a definition list of the per-step knobs, and finally the params as + * formatted JSON. Editable mode is out of scope here — definition edits live + * on the (future) raw-JSON path. + */ +export function PlanStepCard({ step, index }: PlanStepCardProps) { + return ( + <div className="rounded-md border border-border/60 bg-background overflow-hidden"> + <div className="flex items-center gap-2 px-4 py-2 border-b border-border/60 bg-muted/30"> + <span className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground"> + {index + 1} + </span> + <span className="text-sm font-medium text-foreground">{step.step_id}</span> + <ArrowRightCircle className="h-3.5 w-3.5 text-muted-foreground" aria-hidden /> + <span className="text-xs font-mono text-muted-foreground">{step.action}</span> + </div> + + <div className="px-4 py-3 space-y-3"> + {(step.when || + step.output_as || + step.max_retries != null || + step.timeout_seconds != null) && ( + <dl className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-1.5 text-xs"> + {step.when && ( + <DefRow label="When" value={<code className="font-mono">{step.when}</code>} /> + )} + {step.output_as && ( + <DefRow + label="Output as" + value={<code className="font-mono">{step.output_as}</code>} + /> + )} + {step.max_retries != null && ( + <DefRow label="Max retries" value={String(step.max_retries)} /> + )} + {step.timeout_seconds != null && ( + <DefRow label="Timeout" value={`${step.timeout_seconds}s`} /> + )} + </dl> + )} + + <div> + <div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground mb-1.5"> + <GitCommitHorizontal className="h-3.5 w-3.5" aria-hidden /> + Params + </div> + <pre className="rounded-md bg-muted/40 px-3 py-2 text-xs font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words"> + {JSON.stringify(step.params, null, 2)} + </pre> + </div> + </div> + </div> + ); +} + +function DefRow({ label, value }: { label: string; value: React.ReactNode }) { + return ( + <div className="flex items-baseline gap-2 min-w-0"> + <dt className="text-muted-foreground shrink-0">{label}:</dt> + <dd className="text-foreground min-w-0 truncate">{value}</dd> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx new file mode 100644 index 000000000..0caaf968f --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx @@ -0,0 +1,152 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { CalendarClock, Clock, Trash2 } from "lucide-react"; +import { useState } from "react"; +import { updateTriggerMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { Button } from "@/components/ui/button"; +import { Switch } from "@/components/ui/switch"; +import type { Trigger } from "@/contracts/types/automation.types"; +import { formatRelativeDate } from "@/lib/format-date"; +import { describeCron } from "../../lib/describe-cron"; +import { DeleteTriggerDialog } from "./delete-trigger-dialog"; + +interface TriggerCardProps { + trigger: Trigger; + automationId: number; + canUpdate: boolean; + canDelete: boolean; +} + +/** + * One trigger row in the Triggers section of the detail page. Renders: + * - type icon + human-readable schedule + timezone + * - last_fired_at / next_fire_at hints + * - static_inputs as formatted JSON (when present) + * - enable toggle + remove button (each gated independently) + * + * Editing params (cron, timezone, static_inputs) lives behind the future + * raw-JSON path; this card stays read-only-except-for-toggle for v1. + */ +export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: TriggerCardProps) { + const { mutateAsync: updateTrigger, isPending: updating } = + useAtomValue(updateTriggerMutationAtom); + const [deleteOpen, setDeleteOpen] = useState(false); + + const cron = typeof trigger.params.cron === "string" ? trigger.params.cron : undefined; + const tz = typeof trigger.params.timezone === "string" ? trigger.params.timezone : "UTC"; + const human = cron ? describeCron(cron) : trigger.type; + const triggerLabel = cron ? `${human} · ${tz}` : trigger.type; + const hasStaticInputs = Object.keys(trigger.static_inputs ?? {}).length > 0; + + async function handleToggle(checked: boolean) { + await updateTrigger({ + automationId, + triggerId: trigger.id, + patch: { enabled: checked }, + }); + } + + return ( + <> + <div className="rounded-md border border-border/60 bg-background overflow-hidden"> + <div className="flex items-center justify-between gap-4 px-4 py-3 border-b border-border/60"> + <div className="flex items-center gap-3 min-w-0"> + <CalendarClock className="h-4 w-4 text-muted-foreground shrink-0" aria-hidden /> + <div className="min-w-0"> + <div className="flex items-center gap-2 text-sm"> + <span className="font-medium text-foreground">{human}</span> + <span className="text-muted-foreground">· {tz}</span> + </div> + {cron && <code className="text-xs font-mono text-muted-foreground">{cron}</code>} + </div> + </div> + + <div className="flex items-center gap-2 shrink-0"> + {canUpdate && ( + <div className="flex items-center gap-2"> + <span className="text-xs text-muted-foreground"> + {trigger.enabled ? "Enabled" : "Off"} + </span> + <Switch + checked={trigger.enabled} + onCheckedChange={handleToggle} + disabled={updating} + aria-label={trigger.enabled ? "Disable trigger" : "Enable trigger"} + /> + </div> + )} + {canDelete && ( + <Button + variant="ghost" + size="icon" + className="h-8 w-8 text-muted-foreground hover:text-destructive" + onClick={() => setDeleteOpen(true)} + aria-label="Remove trigger" + > + <Trash2 className="h-4 w-4" /> + </Button> + )} + </div> + </div> + + <div className="px-4 py-3 space-y-3 text-xs"> + {(trigger.last_fired_at || trigger.next_fire_at) && ( + <dl className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-1.5"> + {trigger.next_fire_at && ( + <TimeRow label="Next fire" iso={trigger.next_fire_at} highlight={trigger.enabled} /> + )} + {trigger.last_fired_at && <TimeRow label="Last fired" iso={trigger.last_fired_at} />} + </dl> + )} + + {hasStaticInputs && ( + <div> + <div className="text-muted-foreground mb-1">Static inputs</div> + <pre className="rounded-md bg-muted/40 px-3 py-2 font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words"> + {JSON.stringify(trigger.static_inputs, null, 2)} + </pre> + </div> + )} + </div> + </div> + + {canDelete && ( + <DeleteTriggerDialog + open={deleteOpen} + onOpenChange={setDeleteOpen} + automationId={automationId} + triggerId={trigger.id} + triggerLabel={triggerLabel} + /> + )} + </> + ); +} + +function TimeRow({ + label, + iso, + highlight = false, +}: { + label: string; + iso: string; + highlight?: boolean; +}) { + return ( + <div className="flex items-baseline gap-2 min-w-0"> + <dt className="text-muted-foreground shrink-0 inline-flex items-center gap-1"> + <Clock className="h-3 w-3" aria-hidden /> + {label}: + </dt> + <dd + className={ + highlight + ? "text-foreground font-medium min-w-0 truncate" + : "text-muted-foreground min-w-0 truncate" + } + > + {formatRelativeDate(iso)} + </dd> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/page.tsx new file mode 100644 index 000000000..dbaceecdd --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/page.tsx @@ -0,0 +1,18 @@ +import { AutomationDetailContent } from "./automation-detail-content"; + +export default async function AutomationDetailPage({ + params, +}: { + params: Promise<{ search_space_id: string; automation_id: string }>; +}) { + const { search_space_id, automation_id } = await params; + + return ( + <div className="w-full space-y-6"> + <AutomationDetailContent + searchSpaceId={Number(search_space_id)} + automationId={Number(automation_id)} + /> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx index ac27b01e2..8b61a1e02 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx @@ -1,6 +1,7 @@ "use client"; import { CalendarClock, Pause } from "lucide-react"; import type { Trigger } from "@/contracts/types/automation.types"; +import { describeCron } from "../lib/describe-cron"; interface AutomationTriggersSummaryProps { triggers: Trigger[]; @@ -49,72 +50,3 @@ export function AutomationTriggersSummary({ triggers }: AutomationTriggersSummar return <span className="text-xs text-muted-foreground capitalize">{trigger.type}</span>; } - -// ---------------------------------------------------------------------------- -// Minimal cron describer for the common 5-field patterns SurfSense automations -// surface today. Falls back to the raw expression when unrecognized so the user -// still sees something honest instead of a guess. -// -// Kept inline (not a library) because: -// - v1 only needs to recognize a small set of patterns produced by the -// drafter LLM (hourly/daily/weekdays/weekly/monthly). -// - All current consumers live in this slice. If reuse grows, lift to -// ``lib/cron-describe.ts``. -// ---------------------------------------------------------------------------- - -const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - -function describeCron(cron: string): string { - const parts = cron.trim().split(/\s+/); - if (parts.length !== 5) return cron; - - const [minute, hour, dom, month, dow] = parts; - - // Daily at H:MM (matches the very common "0 9 * * *") - if (month === "*" && dom === "*" && dow === "*" && /^\d+$/.test(minute) && /^\d+$/.test(hour)) { - return `Daily at ${formatTime(hour, minute)}`; - } - - // Weekdays at H:MM ("0 9 * * 1-5") - if (month === "*" && dom === "*" && dow === "1-5" && /^\d+$/.test(minute) && /^\d+$/.test(hour)) { - return `Mon–Fri at ${formatTime(hour, minute)}`; - } - - // Specific weekday(s) ("0 9 * * 1" or "0 9 * * 1,3,5") - if ( - month === "*" && - dom === "*" && - /^\d+$/.test(minute) && - /^\d+$/.test(hour) && - /^[\d,]+$/.test(dow) - ) { - const days = dow - .split(",") - .map((d) => DAY_NAMES[Number(d) % 7]) - .filter(Boolean) - .join(", "); - if (days) return `${days} at ${formatTime(hour, minute)}`; - } - - // Monthly on day N ("0 9 1 * *") - if ( - month === "*" && - dow === "*" && - /^\d+$/.test(dom) && - /^\d+$/.test(hour) && - /^\d+$/.test(minute) - ) { - return `Day ${dom} of each month at ${formatTime(hour, minute)}`; - } - - // Hourly ("0 * * * *") - if (month === "*" && dom === "*" && dow === "*" && hour === "*" && /^\d+$/.test(minute)) { - return minute === "0" ? "Every hour" : `Every hour at :${minute.padStart(2, "0")}`; - } - - return cron; -} - -function formatTime(hour: string, minute: string): string { - return `${hour.padStart(2, "0")}:${minute.padStart(2, "0")}`; -} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx index db73ddad5..23fc522ca 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/delete-automation-dialog.tsx @@ -20,6 +20,12 @@ interface DeleteAutomationDialogProps { automationId: number; automationName: string; searchSpaceId: number; + /** + * Fired after a successful delete, before the dialog closes. The detail + * page uses this to navigate back to the list (the row simply vanishes + * on the list page so no callback is needed there). + */ + onDeleted?: () => void; } /** @@ -33,6 +39,7 @@ export function DeleteAutomationDialog({ automationId, automationName, searchSpaceId, + onDeleted, }: DeleteAutomationDialogProps) { const { mutateAsync: deleteAutomation } = useAtomValue(deleteAutomationMutationAtom); const [submitting, setSubmitting] = useState(false); @@ -41,6 +48,7 @@ export function DeleteAutomationDialog({ setSubmitting(true); try { await deleteAutomation({ automationId, searchSpaceId }); + onDeleted?.(); onOpenChange(false); } finally { setSubmitting(false); diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/lib/describe-cron.ts b/surfsense_web/app/dashboard/[search_space_id]/automations/lib/describe-cron.ts new file mode 100644 index 000000000..e10a99a44 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/lib/describe-cron.ts @@ -0,0 +1,66 @@ +/** + * Minimal cron describer for the 5-field patterns the SurfSense drafter LLM + * actually produces (daily, weekdays, weekly, monthly, hourly). Falls back + * to the raw expression when unrecognized so the user still sees something + * honest instead of a guess. + * + * Lives in the automations slice because it's a UI display concern with no + * consumers outside it. If reuse grows, lift to ``lib/cron-describe.ts``. + */ + +const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + +export function describeCron(cron: string): string { + const parts = cron.trim().split(/\s+/); + if (parts.length !== 5) return cron; + + const [minute, hour, dom, month, dow] = parts; + + // Daily at H:MM ("0 9 * * *") + if (month === "*" && dom === "*" && dow === "*" && /^\d+$/.test(minute) && /^\d+$/.test(hour)) { + return `Daily at ${formatTime(hour, minute)}`; + } + + // Weekdays at H:MM ("0 9 * * 1-5") + if (month === "*" && dom === "*" && dow === "1-5" && /^\d+$/.test(minute) && /^\d+$/.test(hour)) { + return `Mon–Fri at ${formatTime(hour, minute)}`; + } + + // Specific weekday(s) ("0 9 * * 1" or "0 9 * * 1,3,5") + if ( + month === "*" && + dom === "*" && + /^\d+$/.test(minute) && + /^\d+$/.test(hour) && + /^[\d,]+$/.test(dow) + ) { + const days = dow + .split(",") + .map((d) => DAY_NAMES[Number(d) % 7]) + .filter(Boolean) + .join(", "); + if (days) return `${days} at ${formatTime(hour, minute)}`; + } + + // Monthly on day N ("0 9 1 * *") + if ( + month === "*" && + dow === "*" && + /^\d+$/.test(dom) && + /^\d+$/.test(hour) && + /^\d+$/.test(minute) + ) { + return `Day ${dom} of each month at ${formatTime(hour, minute)}`; + } + + // Hourly ("0 * * * *") + if (month === "*" && dom === "*" && dow === "*" && hour === "*" && /^\d+$/.test(minute)) { + return minute === "0" ? "Every hour" : `Every hour at :${minute.padStart(2, "0")}`; + } + + return cron; +} + +function formatTime(hour: string, minute: string): string { + return `${hour.padStart(2, "0")}:${minute.padStart(2, "0")}`; +} From 2e572d781855292aa58981417ade345bb4f6fe21 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 01:32:04 +0200 Subject: [PATCH 138/198] feat(web): create_automation HITL approval card in chat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the create loop in chat: the agent describes user intent → the drafter sub-LLM produces an AutomationCreate JSON → this card surfaces a structured preview → approve persists; reject cancels. Edits flow through chat refinement (re-call with a refined intent), not in-card, so the card stays simple and the multi-turn checkpointer carries the context. Tool UI (components/tool-ui/automation/): - create-automation.tsx — entry dispatcher + ApprovalCard chrome (pending/processing/complete/rejected via useHitlPhase) + SavedCard (links to the detail page) + InvalidCard (lists drafter validation issues) + ErrorCard (verbatim message). Rejection result is hidden because the approval card itself shows the rejected phase inline. - automation-draft-preview.tsx — structured preview body: name + description + goal, triggers (humanised cron + tz + static-input keys), plan steps (step_id → action), and a collapsible raw JSON for power users. Wiring: - components/tool-ui/index.ts — re-export. - features/chat-messages/timeline/tool-registry/registry.ts — register create_automation → CreateAutomationToolUI (dynamic import, same pattern as other connector tools). - contracts/enums/toolIcons.tsx — Workflow icon + "Create automation" display name so fallback chrome (and timeline headers) are honest. Shared util: - lib/automations/describe-cron.ts — lifted from the route slice's lib/ folder since both the dashboard slice and the new approval card now render schedule descriptions. Slice imports updated; the now- empty slice lib/ folder is gone. Backend prompt fragments: - main_agent/system_prompt/.../create_automation/description.md and the tool's docstring no longer promise in-card edits. They make the refinement path explicit: if the user wants changes after seeing the draft, they reply in chat and the agent calls the tool again with a refined intent. v1 deliberately excludes: - In-card edit form / right-side edit panel — defer until we see real demand. The chat refinement loop covers the common case. - approve_always / persistent allow rules — automations are a single artifact, not a repeated mutation, so the "trust this kind of call" affordance doesn't apply. --- .../tools/create_automation/description.md | 11 +- .../main_agent/tools/automation/create.py | 8 +- .../components/trigger-card.tsx | 2 +- .../automation-triggers-summary.tsx | 2 +- .../automation/automation-draft-preview.tsx | 183 ++++++++++ .../tool-ui/automation/create-automation.tsx | 328 ++++++++++++++++++ .../components/tool-ui/automation/index.ts | 1 + surfsense_web/components/tool-ui/index.ts | 1 + surfsense_web/contracts/enums/toolIcons.tsx | 5 + .../timeline/tool-registry/registry.ts | 6 + .../lib => lib/automations}/describe-cron.ts | 5 +- 11 files changed, 541 insertions(+), 11 deletions(-) create mode 100644 surfsense_web/components/tool-ui/automation/automation-draft-preview.tsx create mode 100644 surfsense_web/components/tool-ui/automation/create-automation.tsx create mode 100644 surfsense_web/components/tool-ui/automation/index.ts rename surfsense_web/{app/dashboard/[search_space_id]/automations/lib => lib/automations}/describe-cron.ts (88%) diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md index 25b4eec47..ce6562c97 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/create_automation/description.md @@ -1,7 +1,7 @@ - `create_automation` — Draft and author a new automation. You describe the user's intent; a focused drafter inside the tool turns it into the full - automation JSON; the user reviews and edits it on an approval card; on - approval it's saved. All three phases happen in a single tool call. + automation JSON; the user sees a preview on an approval card and chooses + approve or reject. All three phases happen in a single tool call. - Call when the user wants SurfSense to do something on its own: anything recurring or scheduled ("every morning…", "each Monday…", "weekly recap…"). @@ -17,13 +17,16 @@ explicitly ("the Notion parent page id was not specified") so the drafter leaves a placeholder. - Do NOT prompt the user to confirm before calling — the approval card - IS the confirmation. The user can edit any field on the card. + IS the confirmation. The card shows a structured preview plus the raw + JSON; it offers approve/reject only. If the user wants changes after + seeing the draft, they reply in chat and you call this tool again with + a refined `intent` — that's the edit path. - Returns: - `{status: "saved", automation_id, name}` — confirm briefly to the user ("Saved as automation #N — runs <when>."). Don't dump JSON back. - `{status: "rejected", message}` — the user declined on the card. Acknowledge once ("Understood, I didn't create it.") and stop. Do - NOT retry or pitch variants. + NOT retry or pitch variants without a fresh user request. - `{status: "invalid", issues, raw?}` — drafting/validation failed before the card was shown. Read the issues, refine your `intent` with the missing details, call again. diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py index 78fedde22..07b579f3b 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py @@ -66,9 +66,11 @@ def create_create_automation_tool( names, …) it needs. The tool drafts the full automation JSON internally, shows the user - an approval card for review, and persists on approval. Do NOT - prompt the user to confirm before calling — the card IS the - confirmation. The user can edit any field there. + a structured preview on an approval card, and persists on approval. + The card supports approve/reject only — if the user wants edits + after seeing the draft, they say so in chat and you call this tool + again with a refined intent. Do NOT prompt the user to confirm + before calling — the card IS the confirmation. Args: intent: Concrete restatement of the user's request. Include diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx index 0caaf968f..afadf589a 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx @@ -6,8 +6,8 @@ import { updateTriggerMutationAtom } from "@/atoms/automations/automations-mutat import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import type { Trigger } from "@/contracts/types/automation.types"; +import { describeCron } from "@/lib/automations/describe-cron"; import { formatRelativeDate } from "@/lib/format-date"; -import { describeCron } from "../../lib/describe-cron"; import { DeleteTriggerDialog } from "./delete-trigger-dialog"; interface TriggerCardProps { diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx index 8b61a1e02..270a1f844 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-triggers-summary.tsx @@ -1,7 +1,7 @@ "use client"; import { CalendarClock, Pause } from "lucide-react"; import type { Trigger } from "@/contracts/types/automation.types"; -import { describeCron } from "../lib/describe-cron"; +import { describeCron } from "@/lib/automations/describe-cron"; interface AutomationTriggersSummaryProps { triggers: Trigger[]; diff --git a/surfsense_web/components/tool-ui/automation/automation-draft-preview.tsx b/surfsense_web/components/tool-ui/automation/automation-draft-preview.tsx new file mode 100644 index 000000000..b0b5c8f78 --- /dev/null +++ b/surfsense_web/components/tool-ui/automation/automation-draft-preview.tsx @@ -0,0 +1,183 @@ +"use client"; +import { CalendarClock, ChevronDown, ChevronRight, ListOrdered, Target } from "lucide-react"; +import { useState } from "react"; +import { describeCron } from "@/lib/automations/describe-cron"; + +interface DraftTrigger { + type: string; + params: Record<string, unknown>; + static_inputs: Record<string, unknown>; + enabled: boolean; +} + +interface DraftPlanStep { + step_id: string; + action: string; + when?: string | null; +} + +interface AutomationDraft { + name: string; + description?: string | null; + definition: { + goal?: string | null; + plan: DraftPlanStep[]; + }; + triggers: DraftTrigger[]; +} + +interface AutomationDraftPreviewProps { + draft: AutomationDraft; + /** Full unmodified args dict — surfaced as the "raw JSON" escape hatch. */ + raw: Record<string, unknown>; +} + +/** + * Structured preview of a drafted automation rendered inside the chat + * approval card. + * + * Three layers, top to bottom: + * 1. Name + description (and goal when present). + * 2. Triggers — humanised cron string + timezone + static_inputs hint. + * 3. Plan steps — ordered list of ``step_id → action``. + * + * A "View raw JSON" toggle reveals the full payload for power users who + * want to inspect every field; it's collapsed by default so the card + * stays scannable for the common case. + */ +export function AutomationDraftPreview({ draft, raw }: AutomationDraftPreviewProps) { + const [showRaw, setShowRaw] = useState(false); + + return ( + <div className="space-y-4 text-sm"> + <div className="space-y-1"> + <p className="font-medium text-foreground">{draft.name}</p> + {draft.description && <p className="text-xs text-muted-foreground">{draft.description}</p>} + </div> + + {draft.definition.goal && ( + <Section icon={Target} label="Goal"> + <p className="text-xs text-foreground">{draft.definition.goal}</p> + </Section> + )} + + <Section icon={CalendarClock} label={`Triggers · ${draft.triggers.length}`}> + {draft.triggers.length === 0 ? ( + <p className="text-xs text-muted-foreground"> + No triggers — automation will need one before it can run. + </p> + ) : ( + <ul className="space-y-1.5"> + {draft.triggers.map((trigger) => ( + <li + key={triggerKey(trigger)} + className="rounded-md border border-border/60 bg-background/50 px-3 py-2 text-xs" + > + <TriggerLine trigger={trigger} /> + </li> + ))} + </ul> + )} + </Section> + + <Section + icon={ListOrdered} + label={`Plan · ${draft.definition.plan.length} step${draft.definition.plan.length === 1 ? "" : "s"}`} + > + <ol className="space-y-1 text-xs"> + {draft.definition.plan.map((step, idx) => ( + <li key={step.step_id} className="flex items-start gap-2"> + <span className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-muted text-[10px] font-medium text-muted-foreground shrink-0 mt-0.5"> + {idx + 1} + </span> + <div className="min-w-0"> + <span className="font-medium text-foreground">{step.step_id}</span> + <span className="text-muted-foreground"> → </span> + <code className="font-mono text-muted-foreground">{step.action}</code> + {step.when && <span className="ml-2 text-muted-foreground">when {step.when}</span>} + </div> + </li> + ))} + </ol> + </Section> + + <button + type="button" + onClick={() => setShowRaw((value) => !value)} + className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground" + > + {showRaw ? ( + <ChevronDown className="h-3 w-3" aria-hidden /> + ) : ( + <ChevronRight className="h-3 w-3" aria-hidden /> + )} + {showRaw ? "Hide raw JSON" : "View raw JSON"} + </button> + {showRaw && ( + <pre className="rounded-md bg-muted/40 px-3 py-2 text-[11px] font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words max-h-72"> + {JSON.stringify(raw, null, 2)} + </pre> + )} + </div> + ); +} + +/** + * Stable key derived from the trigger's identifying fields. Drafts are + * static snapshots so collisions only happen if the LLM emits two literally + * identical triggers — harmless in practice. + */ +function triggerKey(trigger: DraftTrigger): string { + const cron = typeof trigger.params.cron === "string" ? trigger.params.cron : ""; + const tz = typeof trigger.params.timezone === "string" ? trigger.params.timezone : ""; + return `${trigger.type}|${cron}|${tz}`; +} + +function TriggerLine({ trigger }: { trigger: DraftTrigger }) { + if (trigger.type === "schedule") { + const cron = typeof trigger.params.cron === "string" ? trigger.params.cron : undefined; + const tz = typeof trigger.params.timezone === "string" ? trigger.params.timezone : "UTC"; + const human = cron ? describeCron(cron) : "Schedule"; + const staticKeys = Object.keys(trigger.static_inputs ?? {}); + return ( + <div className="space-y-1"> + <div className="flex items-center gap-2 flex-wrap"> + <span className="font-medium text-foreground">{human}</span> + <span className="text-muted-foreground">· {tz}</span> + {!trigger.enabled && ( + <span className="rounded-md border border-border/60 px-1.5 py-0.5 text-[10px] text-muted-foreground"> + Disabled + </span> + )} + </div> + {cron && <code className="font-mono text-muted-foreground">{cron}</code>} + {staticKeys.length > 0 && ( + <p className="text-muted-foreground"> + Static inputs: <span className="text-foreground">{staticKeys.join(", ")}</span> + </p> + )} + </div> + ); + } + return <span className="capitalize text-foreground">{trigger.type}</span>; +} + +function Section({ + icon: Icon, + label, + children, +}: { + icon: typeof Target; + label: string; + children: React.ReactNode; +}) { + return ( + <div className="space-y-1.5"> + <div className="flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground uppercase tracking-wider"> + <Icon className="h-3 w-3" aria-hidden /> + {label} + </div> + {children} + </div> + ); +} diff --git a/surfsense_web/components/tool-ui/automation/create-automation.tsx b/surfsense_web/components/tool-ui/automation/create-automation.tsx new file mode 100644 index 000000000..713c5fd46 --- /dev/null +++ b/surfsense_web/components/tool-ui/automation/create-automation.tsx @@ -0,0 +1,328 @@ +"use client"; + +import type { ToolCallMessagePartProps } from "@assistant-ui/react"; +import { useAtomValue } from "jotai"; +import { CornerDownLeftIcon, ExternalLink, Workflow } from "lucide-react"; +import Link from "next/link"; +import { useCallback, useEffect, useMemo } from "react"; +import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; +import { TextShimmerLoader } from "@/components/prompt-kit/loader"; +import { Button } from "@/components/ui/button"; +import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl"; +import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl"; +import { AutomationDraftPreview } from "./automation-draft-preview"; + +// ---------------------------------------------------------------------------- +// Result discrimination — mirrors the backend return shapes in +// app/agents/multi_agent_chat/main_agent/tools/automation/create.py. +// ---------------------------------------------------------------------------- + +type AutomationCreateContext = { + search_space_id?: number; +}; + +interface SavedResult { + status: "saved"; + automation_id: number; + name: string; +} + +interface RejectedResult { + status: "rejected"; + message?: string; +} + +interface InvalidResult { + status: "invalid"; + issues: string[]; + raw?: unknown; +} + +interface ErrorResult { + status: "error"; + message: string; +} + +type CreateAutomationResult = + | InterruptResult<AutomationCreateContext> + | SavedResult + | RejectedResult + | InvalidResult + | ErrorResult; + +function hasStatus(value: unknown, status: string): boolean { + return ( + typeof value === "object" && + value !== null && + "status" in value && + (value as { status: unknown }).status === status + ); +} + +// ---------------------------------------------------------------------------- +// Approval card — pending → processing → complete / rejected. +// +// v1 deliberately supports only approve/reject. The drafted JSON is complex +// (full plan + triggers) and we already have a multi-turn refinement path via +// chat ("make it run at 10am instead" → the agent re-calls the tool with a +// refined intent). An in-card edit form would duplicate that flow and add UX +// surface area we don't need yet — leave it for the raw-JSON path on the +// detail page. +// ---------------------------------------------------------------------------- + +interface ApprovalCardProps { + args: Record<string, unknown>; + interruptData: InterruptResult<AutomationCreateContext>; + onDecision: (decision: HitlDecision) => void; +} + +function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { + const { phase, setProcessing, setRejected } = useHitlPhase(interruptData); + + const reviewConfig = interruptData.review_configs[0]; + const allowedDecisions = reviewConfig?.allowed_decisions ?? ["approve", "reject"]; + const canApprove = allowedDecisions.includes("approve"); + const canReject = allowedDecisions.includes("reject"); + + const draft = useMemo(() => extractDraft(args), [args]); + + const handleApprove = useCallback(() => { + if (phase !== "pending" || !canApprove) return; + setProcessing(); + onDecision({ + type: "approve", + edited_action: { + name: interruptData.action_requests[0]?.name ?? "create_automation", + args, + }, + }); + }, [phase, canApprove, setProcessing, onDecision, interruptData, args]); + + const handleReject = useCallback(() => { + if (phase !== "pending" || !canReject) return; + setRejected(); + onDecision({ type: "reject", message: "User rejected the automation draft." }); + }, [phase, canReject, setRejected, onDecision]); + + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.metaKey) { + handleApprove(); + } + }; + window.addEventListener("keydown", handler); + return () => window.removeEventListener("keydown", handler); + }, [handleApprove]); + + return ( + <div className="my-4 max-w-lg overflow-hidden rounded-2xl border bg-muted/30 transition-[box-shadow] duration-300"> + <div className="flex items-start gap-3 px-5 pt-5 pb-4 select-none"> + <Workflow className="h-5 w-5 text-muted-foreground mt-0.5 shrink-0" aria-hidden /> + <div className="min-w-0"> + <p className="text-sm font-semibold text-foreground"> + {phase === "rejected" + ? "Automation cancelled" + : phase === "processing" + ? "Saving automation" + : phase === "complete" + ? "Automation saved" + : "Create automation"} + </p> + {phase === "processing" ? ( + <TextShimmerLoader text="Saving automation" size="sm" /> + ) : phase === "complete" ? ( + <p className="text-xs text-muted-foreground mt-0.5"> + Automation created from this draft + </p> + ) : phase === "rejected" ? ( + <p className="text-xs text-muted-foreground mt-0.5"> + No automation was saved — ask in chat to refine and try again. + </p> + ) : ( + <p className="text-xs text-muted-foreground mt-0.5"> + Review and approve to save. To change anything, reply in chat — I'll redraft. + </p> + )} + </div> + </div> + + <div className="mx-5 h-px bg-border/50" /> + <div className="px-5 py-4"> + <AutomationDraftPreview draft={draft} raw={args} /> + </div> + + {phase === "pending" && ( + <> + <div className="mx-5 h-px bg-border/50" /> + <div className="px-5 py-4 flex items-center gap-2 select-none"> + {canApprove && ( + <Button size="sm" className="rounded-lg gap-1.5" onClick={handleApprove}> + Approve + <CornerDownLeftIcon className="size-3 opacity-60" /> + </Button> + )} + {canReject && ( + <Button + size="sm" + variant="ghost" + className="rounded-lg text-muted-foreground" + onClick={handleReject} + > + Reject + </Button> + )} + </div> + </> + )} + </div> + ); +} + +// ---------------------------------------------------------------------------- +// Terminal result cards. +// ---------------------------------------------------------------------------- + +function SavedCard({ result }: { result: SavedResult }) { + const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); + const detailHref = searchSpaceId + ? `/dashboard/${searchSpaceId}/automations/${result.automation_id}` + : null; + + return ( + <div className="my-4 max-w-lg overflow-hidden rounded-2xl border bg-muted/30 select-none"> + <div className="flex items-start gap-3 px-5 pt-5 pb-4"> + <Workflow className="h-5 w-5 text-muted-foreground mt-0.5 shrink-0" aria-hidden /> + <div className="min-w-0"> + <p className="text-sm font-semibold text-foreground">Automation saved</p> + <p className="text-xs text-muted-foreground mt-0.5">{result.name}</p> + </div> + </div> + {detailHref && ( + <> + <div className="mx-5 h-px bg-border/50" /> + <div className="px-5 py-3"> + <Link + href={detailHref} + className="inline-flex items-center gap-1.5 text-xs text-primary hover:underline" + > + <ExternalLink className="h-3.5 w-3.5" aria-hidden /> + Open automation #{result.automation_id} + </Link> + </div> + </> + )} + </div> + ); +} + +function InvalidCard({ result }: { result: InvalidResult }) { + return ( + <div className="my-4 max-w-lg overflow-hidden rounded-2xl border bg-muted/30 select-none"> + <div className="px-5 pt-5 pb-4"> + <p className="text-sm font-semibold text-destructive">Couldn't draft this automation</p> + <p className="text-xs text-muted-foreground mt-0.5"> + The drafter produced output that didn't validate. I'll refine and retry. + </p> + </div> + {result.issues.length > 0 && ( + <> + <div className="mx-5 h-px bg-border/50" /> + <ul className="px-5 py-3 space-y-1 text-xs text-muted-foreground list-disc list-inside"> + {result.issues.map((issue) => ( + <li key={issue}>{issue}</li> + ))} + </ul> + </> + )} + </div> + ); +} + +function ErrorCard({ result }: { result: ErrorResult }) { + return ( + <div className="my-4 max-w-lg overflow-hidden rounded-2xl border bg-muted/30 select-none"> + <div className="px-5 pt-5 pb-4"> + <p className="text-sm font-semibold text-destructive">Failed to create automation</p> + </div> + <div className="mx-5 h-px bg-border/50" /> + <div className="px-5 py-4"> + <p className="text-sm text-muted-foreground">{result.message}</p> + </div> + </div> + ); +} + +// ---------------------------------------------------------------------------- +// Entry — dispatches between the approval card and terminal result cards. +// +// Rejection is special: we hide the standalone "rejected" card because the +// approval card itself already transitions to a "rejected" phase inline. A +// second message in the timeline would be noisy. +// ---------------------------------------------------------------------------- + +export const CreateAutomationToolUI = ({ + args, + result, +}: ToolCallMessagePartProps<{ intent: string }, CreateAutomationResult>) => { + const { dispatch } = useHitlDecision(); + + if (!result) return null; + + if (isInterruptResult(result)) { + return ( + <ApprovalCard + args={args as unknown as Record<string, unknown>} + interruptData={result as InterruptResult<AutomationCreateContext>} + onDecision={(decision) => dispatch([decision])} + /> + ); + } + + if (hasStatus(result, "rejected")) return null; + if (hasStatus(result, "saved")) return <SavedCard result={result as SavedResult} />; + if (hasStatus(result, "invalid")) return <InvalidCard result={result as InvalidResult} />; + if (hasStatus(result, "error")) return <ErrorCard result={result as ErrorResult} />; + + return null; +}; + +// ---------------------------------------------------------------------------- +// Helpers. +// ---------------------------------------------------------------------------- + +/** + * Project raw args into the shape ``AutomationDraftPreview`` expects. + * + * The args dict is the full ``AutomationCreate`` payload (minus + * ``search_space_id`` which is injected server-side), so we trust the + * top-level fields but defend against missing nested defaults. + */ +function extractDraft(args: Record<string, unknown>) { + const definition = (args.definition ?? {}) as Record<string, unknown>; + const planSteps = Array.isArray(definition.plan) + ? (definition.plan as Array<Record<string, unknown>>).map((step) => ({ + step_id: String(step.step_id ?? "(unnamed)"), + action: String(step.action ?? ""), + when: typeof step.when === "string" ? step.when : null, + })) + : []; + + const triggers = Array.isArray(args.triggers) + ? (args.triggers as Array<Record<string, unknown>>).map((trigger) => ({ + type: String(trigger.type ?? "schedule"), + params: (trigger.params ?? {}) as Record<string, unknown>, + static_inputs: (trigger.static_inputs ?? {}) as Record<string, unknown>, + enabled: trigger.enabled !== false, + })) + : []; + + return { + name: String(args.name ?? "(unnamed automation)"), + description: typeof args.description === "string" ? args.description : null, + definition: { + goal: typeof definition.goal === "string" ? definition.goal : null, + plan: planSteps, + }, + triggers, + }; +} diff --git a/surfsense_web/components/tool-ui/automation/index.ts b/surfsense_web/components/tool-ui/automation/index.ts new file mode 100644 index 000000000..50cf1a478 --- /dev/null +++ b/surfsense_web/components/tool-ui/automation/index.ts @@ -0,0 +1 @@ +export { CreateAutomationToolUI } from "./create-automation"; diff --git a/surfsense_web/components/tool-ui/index.ts b/surfsense_web/components/tool-ui/index.ts index 4d885a38c..ee5072dad 100644 --- a/surfsense_web/components/tool-ui/index.ts +++ b/surfsense_web/components/tool-ui/index.ts @@ -7,6 +7,7 @@ */ export { Audio } from "./audio"; +export { CreateAutomationToolUI } from "./automation"; export { CreateDropboxFileToolUI, DeleteDropboxFileToolUI } from "./dropbox"; export { type GenerateImageArgs, diff --git a/surfsense_web/contracts/enums/toolIcons.tsx b/surfsense_web/contracts/enums/toolIcons.tsx index bb87be0ba..668cb51cd 100644 --- a/surfsense_web/contracts/enums/toolIcons.tsx +++ b/surfsense_web/contracts/enums/toolIcons.tsx @@ -25,6 +25,7 @@ import { SearchCheck, Send, Trash2, + Workflow, Wrench, } from "lucide-react"; @@ -47,6 +48,8 @@ const TOOL_ICONS: Record<string, LucideIcon> = { scrape_webpage: ScanLine, web_search: Globe, search_surfsense_docs: BookOpen, + // Automations + create_automation: Workflow, // Memory update_memory: Brain, // Filesystem (built-in deepagent + middleware) @@ -150,6 +153,8 @@ const TOOL_DISPLAY_NAMES: Record<string, string> = { scrape_webpage: "Read webpage", web_search: "Search the web", search_surfsense_docs: "Search knowledge base", + // Automations + create_automation: "Create automation", // Memory update_memory: "Update memory", // Calendar diff --git a/surfsense_web/features/chat-messages/timeline/tool-registry/registry.ts b/surfsense_web/features/chat-messages/timeline/tool-registry/registry.ts index 8acc6b4fa..c4cfe7cd3 100644 --- a/surfsense_web/features/chat-messages/timeline/tool-registry/registry.ts +++ b/surfsense_web/features/chat-messages/timeline/tool-registry/registry.ts @@ -17,6 +17,11 @@ const UpdateMemoryToolUI = dynamic( () => import("@/components/tool-ui/user-memory").then((m) => ({ default: m.UpdateMemoryToolUI })), { ssr: false } ); +const CreateAutomationToolUI = dynamic( + () => + import("@/components/tool-ui/automation").then((m) => ({ default: m.CreateAutomationToolUI })), + { ssr: false } +); const SandboxExecuteToolUI = dynamic( () => import("@/components/tool-ui/sandbox-execute").then((m) => ({ @@ -184,6 +189,7 @@ const NullTimelineBody: TimelineToolComponent = () => null; */ const TOOLS_BY_NAME = { task: NullTimelineBody, + create_automation: CreateAutomationToolUI, update_memory: UpdateMemoryToolUI, execute: SandboxExecuteToolUI, execute_code: SandboxExecuteToolUI, diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/lib/describe-cron.ts b/surfsense_web/lib/automations/describe-cron.ts similarity index 88% rename from surfsense_web/app/dashboard/[search_space_id]/automations/lib/describe-cron.ts rename to surfsense_web/lib/automations/describe-cron.ts index e10a99a44..19f7ff991 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/lib/describe-cron.ts +++ b/surfsense_web/lib/automations/describe-cron.ts @@ -4,8 +4,9 @@ * to the raw expression when unrecognized so the user still sees something * honest instead of a guess. * - * Lives in the automations slice because it's a UI display concern with no - * consumers outside it. If reuse grows, lift to ``lib/cron-describe.ts``. + * Lives under ``lib/automations/`` because both the dashboard slice and the + * chat ``create_automation`` approval card render schedule descriptions — + * keeping the helper outside either feature avoids a layering violation. */ const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; From 4625bd937e02d5f1f6eb7b62fe25fa7a0b657894 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 01:35:48 +0200 Subject: [PATCH 139/198] feat(web): run history section on automations detail page Recent runs card under triggers. Each row expands lazily to fetch the full run (step results, output, artifacts, error). 20-row cap for now; real pagination lands if usage demands it. --- .../automation-detail-content.tsx | 3 + .../components/automation-runs-section.tsx | 67 ++++++++++ .../components/run-details-panel.tsx | 116 ++++++++++++++++++ .../[automation_id]/components/run-row.tsx | 75 +++++++++++ .../components/run-status-badge.tsx | 57 +++++++++ .../components/runs-loading.tsx | 23 ++++ 6 files changed, 341 insertions(+) create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-status-badge.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx index a82887721..253d6ae67 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx @@ -6,6 +6,7 @@ import { AutomationDefinitionSection } from "./components/automation-definition- import { AutomationDetailHeader } from "./components/automation-detail-header"; import { AutomationDetailLoading } from "./components/automation-detail-loading"; import { AutomationNotFound } from "./components/automation-not-found"; +import { AutomationRunsSection } from "./components/automation-runs-section"; import { AutomationTriggersSection } from "./components/automation-triggers-section"; interface AutomationDetailContentProps { @@ -81,6 +82,8 @@ export function AutomationDetailContent({ canDelete={perms.canDelete} canCreate={perms.canCreate} /> + + <AutomationRunsSection automationId={automation.id} /> </> ); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx new file mode 100644 index 000000000..b6158cab2 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx @@ -0,0 +1,67 @@ +"use client"; +import { History } from "lucide-react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useAutomationRuns } from "@/hooks/use-automation-runs"; +import { RunRow } from "./run-row"; +import { RunsLoading } from "./runs-loading"; + +interface AutomationRunsSectionProps { + automationId: number; +} + +const LIMIT = 20; + +/** + * Run history card. Shows the most recent ``LIMIT`` runs; pagination is + * intentionally deferred — for the foreseeable v1 surface (one-trigger + * automations firing daily), 20 covers ~3 weeks of history which is + * enough to tell whether things are working. Real "load more" lands if + * we see usage spike past that. + */ +export function AutomationRunsSection({ automationId }: AutomationRunsSectionProps) { + const { data, isLoading, error } = useAutomationRuns(automationId, { limit: LIMIT }); + const runs = data?.items ?? []; + + return ( + <Card> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> + <div className="space-y-1"> + <CardTitle className="text-base font-semibold inline-flex items-center gap-2"> + <History className="h-4 w-4 text-muted-foreground" aria-hidden /> + Recent runs + </CardTitle> + <p className="text-xs text-muted-foreground"> + Most recent first. Click a row to inspect step results, output and artifacts. + </p> + </div> + {!isLoading && !error && data && ( + <span className="text-xs text-muted-foreground">{data.total} total</span> + )} + </CardHeader> + <CardContent> + {isLoading ? ( + <RunsLoading /> + ) : error ? ( + <p className="text-sm text-muted-foreground"> + Couldn't load runs{error.message ? `: ${error.message}` : "."} + </p> + ) : runs.length === 0 ? ( + <div className="rounded-md border border-dashed border-border/60 bg-muted/20 px-4 py-8 text-center"> + <History className="mx-auto h-8 w-8 text-muted-foreground" aria-hidden /> + <p className="mt-2 text-sm font-medium text-foreground">No runs yet</p> + <p className="mt-1 text-xs text-muted-foreground"> + This automation hasn't fired. Once a trigger fires (or you invoke it manually), runs + will appear here. + </p> + </div> + ) : ( + <div className="space-y-2"> + {runs.map((run) => ( + <RunRow key={run.id} run={run} automationId={automationId} /> + ))} + </div> + )} + </CardContent> + </Card> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx new file mode 100644 index 000000000..d1d46900a --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx @@ -0,0 +1,116 @@ +"use client"; +import { AlertCircle, FileOutput, GitCommitHorizontal, Package, Settings2 } from "lucide-react"; +import { Skeleton } from "@/components/ui/skeleton"; +import { useAutomationRun } from "@/hooks/use-automation-runs"; + +interface RunDetailsPanelProps { + automationId: number; + runId: number; +} + +/** + * Expanded view of a single run. Fetches lazily — the parent only renders + * this once the row is opened, so the list view stays cheap. + * + * We surface the four most actionable sections (error first when present, + * then output, step results, artifacts, inputs). The full + * ``definition_snapshot`` is omitted because it usually mirrors the live + * definition — surfacing it would dominate the panel without informing + * what the user is trying to learn ("did this work? what did it do?"). + */ +export function RunDetailsPanel({ automationId, runId }: RunDetailsPanelProps) { + const { data: run, isLoading, error } = useAutomationRun(automationId, runId); + + if (isLoading) { + return ( + <div className="space-y-3 p-4 bg-muted/20 border-t border-border/60"> + <Skeleton className="h-3 w-32" /> + <Skeleton className="h-24 w-full" /> + </div> + ); + } + + if (error || !run) { + return ( + <div className="p-4 bg-muted/20 border-t border-border/60 text-xs text-muted-foreground"> + Couldn't load run details{error?.message ? `: ${error.message}` : "."} + </div> + ); + } + + const hasError = run.error && Object.keys(run.error).length > 0; + const hasOutput = run.output && Object.keys(run.output).length > 0; + const hasInputs = Object.keys(run.inputs ?? {}).length > 0; + + return ( + <div className="space-y-4 p-4 bg-muted/20 border-t border-border/60"> + {hasError && ( + <Section icon={AlertCircle} label="Error" tone="destructive"> + <JsonBlock value={run.error} /> + </Section> + )} + + {hasOutput && ( + <Section icon={FileOutput} label="Output"> + <JsonBlock value={run.output} /> + </Section> + )} + + <Section icon={GitCommitHorizontal} label={`Step results · ${run.step_results.length}`}> + {run.step_results.length === 0 ? ( + <p className="text-xs text-muted-foreground">No steps recorded.</p> + ) : ( + <JsonBlock value={run.step_results} /> + )} + </Section> + + {run.artifacts.length > 0 && ( + <Section icon={Package} label={`Artifacts · ${run.artifacts.length}`}> + <JsonBlock value={run.artifacts} /> + </Section> + )} + + {hasInputs && ( + <Section icon={Settings2} label="Resolved inputs"> + <JsonBlock value={run.inputs} /> + </Section> + )} + </div> + ); +} + +function Section({ + icon: Icon, + label, + tone = "default", + children, +}: { + icon: typeof AlertCircle; + label: string; + tone?: "default" | "destructive"; + children: React.ReactNode; +}) { + return ( + <div className="space-y-1.5"> + <div + className={ + tone === "destructive" + ? "flex items-center gap-1.5 text-[11px] font-medium text-destructive uppercase tracking-wider" + : "flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground uppercase tracking-wider" + } + > + <Icon className="h-3 w-3" aria-hidden /> + {label} + </div> + {children} + </div> + ); +} + +function JsonBlock({ value }: { value: unknown }) { + return ( + <pre className="rounded-md bg-background/60 px-3 py-2 text-[11px] font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words max-h-64"> + {JSON.stringify(value, null, 2)} + </pre> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx new file mode 100644 index 000000000..b8d2bcc8b --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx @@ -0,0 +1,75 @@ +"use client"; +import { ChevronDown, ChevronRight, Hand } from "lucide-react"; +import { useState } from "react"; +import type { RunSummary } from "@/contracts/types/automation.types"; +import { formatRelativeDate } from "@/lib/format-date"; +import { RunDetailsPanel } from "./run-details-panel"; +import { RunStatusBadge } from "./run-status-badge"; + +interface RunRowProps { + run: RunSummary; + automationId: number; +} + +/** + * One run row. Click to expand → fetches the full run and shows the + * details panel inline. State is local to each row so multiple panels + * can be open at once (or none). + */ +export function RunRow({ run, automationId }: RunRowProps) { + const [open, setOpen] = useState(false); + const duration = computeDuration(run.started_at, run.finished_at); + const startedLabel = run.started_at + ? formatRelativeDate(run.started_at) + : formatRelativeDate(run.created_at); + + return ( + <div className="rounded-md border border-border/60 bg-background overflow-hidden"> + <button + type="button" + onClick={() => setOpen((value) => !value)} + className="flex w-full items-center justify-between gap-4 px-4 py-3 text-left hover:bg-muted/30 transition-colors" + aria-expanded={open} + > + <div className="flex items-center gap-3 min-w-0"> + {open ? ( + <ChevronDown className="h-4 w-4 text-muted-foreground shrink-0" aria-hidden /> + ) : ( + <ChevronRight className="h-4 w-4 text-muted-foreground shrink-0" aria-hidden /> + )} + <RunStatusBadge status={run.status} /> + <span className="text-xs text-muted-foreground truncate">{startedLabel}</span> + </div> + <div className="flex items-center gap-3 shrink-0 text-xs text-muted-foreground"> + {duration && <span className="font-mono">{duration}</span>} + <TriggerSource triggerId={run.trigger_id ?? null} /> + </div> + </button> + + {open && <RunDetailsPanel automationId={automationId} runId={run.id} />} + </div> + ); +} + +function TriggerSource({ triggerId }: { triggerId: number | null }) { + if (triggerId == null) { + return ( + <span className="inline-flex items-center gap-1"> + <Hand className="h-3 w-3" aria-hidden /> + Manual + </span> + ); + } + return <span>via trigger #{triggerId}</span>; +} + +function computeDuration(started: string | null | undefined, finished: string | null | undefined) { + if (!started || !finished) return null; + const ms = new Date(finished).getTime() - new Date(started).getTime(); + if (!Number.isFinite(ms) || ms < 0) return null; + if (ms < 1000) return `${ms}ms`; + if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`; + const minutes = Math.floor(ms / 60_000); + const seconds = Math.floor((ms % 60_000) / 1000); + return `${minutes}m ${seconds}s`; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-status-badge.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-status-badge.tsx new file mode 100644 index 000000000..e5532a500 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-status-badge.tsx @@ -0,0 +1,57 @@ +"use client"; +import { AlertCircle, CheckCircle2, Clock, Loader2, TimerOff, XCircle } from "lucide-react"; +import type { RunStatus } from "@/contracts/types/automation.types"; +import { cn } from "@/lib/utils"; + +const STATUS_STYLES: Record< + RunStatus, + { label: string; icon: typeof CheckCircle2; classes: string; spin?: boolean } +> = { + pending: { + label: "Pending", + icon: Clock, + classes: "bg-muted text-muted-foreground border-border/60", + }, + running: { + label: "Running", + icon: Loader2, + classes: "bg-blue-500/10 text-blue-600 border-blue-500/20", + spin: true, + }, + succeeded: { + label: "Succeeded", + icon: CheckCircle2, + classes: "bg-emerald-500/10 text-emerald-600 border-emerald-500/20", + }, + failed: { + label: "Failed", + icon: XCircle, + classes: "bg-destructive/10 text-destructive border-destructive/20", + }, + cancelled: { + label: "Cancelled", + icon: AlertCircle, + classes: "bg-muted text-muted-foreground border-border/60", + }, + timed_out: { + label: "Timed out", + icon: TimerOff, + classes: "bg-amber-500/10 text-amber-600 border-amber-500/20", + }, +}; + +export function RunStatusBadge({ status, className }: { status: RunStatus; className?: string }) { + const { label, icon: Icon, classes, spin } = STATUS_STYLES[status]; + return ( + <span + className={cn( + "inline-flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs font-medium", + classes, + className + )} + > + <Icon className={cn("h-3 w-3", spin && "animate-spin")} aria-hidden /> + {label} + </span> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx new file mode 100644 index 000000000..5cab18f4c --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx @@ -0,0 +1,23 @@ +"use client"; +import { Skeleton } from "@/components/ui/skeleton"; + +const ROW_KEYS = ["a", "b", "c"] as const; + +export function RunsLoading() { + return ( + <div className="space-y-2"> + {ROW_KEYS.map((key) => ( + <div + key={key} + className="flex items-center justify-between gap-4 rounded-md border border-border/60 bg-background/50 px-4 py-3" + > + <div className="flex items-center gap-3"> + <Skeleton className="h-5 w-20 rounded-md" /> + <Skeleton className="h-3 w-32" /> + </div> + <Skeleton className="h-3 w-16" /> + </div> + ))} + </div> + ); +} From ed8d56aa16dc1beba7dfee1e5a93c18a49b29725 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 01:44:13 +0200 Subject: [PATCH 140/198] feat(web): create automation via raw JSON --- .../components/automations-empty-state.tsx | 22 +++- .../components/automations-header.tsx | 22 +++- .../new/automation-new-content.tsx | 42 ++++++ .../new/components/automation-json-form.tsx | 122 ++++++++++++++++++ .../new/components/automation-new-header.tsx | 42 ++++++ .../automations/new/page.tsx | 15 +++ .../lib/automations/default-template.ts | 44 +++++++ 7 files changed, 295 insertions(+), 14 deletions(-) create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/new/page.tsx create mode 100644 surfsense_web/lib/automations/default-template.ts diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx index 4004cce9b..83fa52fa8 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx @@ -1,5 +1,5 @@ "use client"; -import { MessageSquarePlus, Workflow } from "lucide-react"; +import { FileJson, MessageSquarePlus, Workflow } from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; @@ -26,12 +26,20 @@ export function AutomationsEmptyState({ searchSpaceId, canCreate }: AutomationsE SurfSense drafts the automation for your approval. </p> {canCreate ? ( - <Button asChild className="mt-6"> - <Link href={`/dashboard/${searchSpaceId}/new-chat`}> - <MessageSquarePlus className="mr-2 h-4 w-4" /> - Create via chat - </Link> - </Button> + <div className="mt-6 flex items-center justify-center gap-2 flex-wrap"> + <Button asChild> + <Link href={`/dashboard/${searchSpaceId}/new-chat`}> + <MessageSquarePlus className="mr-2 h-4 w-4" /> + Create via chat + </Link> + </Button> + <Button asChild variant="outline"> + <Link href={`/dashboard/${searchSpaceId}/automations/new`}> + <FileJson className="mr-2 h-4 w-4" /> + Create via JSON + </Link> + </Button> + </div> ) : ( <p className="mt-6 text-xs text-muted-foreground"> You don't have permission to create automations in this search space. diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx index 22ea60664..544c6b7ac 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx @@ -1,5 +1,5 @@ "use client"; -import { MessageSquarePlus } from "lucide-react"; +import { FileJson, MessageSquarePlus } from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; @@ -39,12 +39,20 @@ export function AutomationsHeader({ )} </div> {canCreate && showCreateCta && ( - <Button asChild size="sm"> - <Link href={`/dashboard/${searchSpaceId}/new-chat`}> - <MessageSquarePlus className="mr-2 h-4 w-4" /> - Create via chat - </Link> - </Button> + <div className="flex items-center gap-2"> + <Button asChild size="sm" variant="outline"> + <Link href={`/dashboard/${searchSpaceId}/automations/new`}> + <FileJson className="mr-2 h-4 w-4" /> + Create via JSON + </Link> + </Button> + <Button asChild size="sm"> + <Link href={`/dashboard/${searchSpaceId}/new-chat`}> + <MessageSquarePlus className="mr-2 h-4 w-4" /> + Create via chat + </Link> + </Button> + </div> )} </div> ); diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx new file mode 100644 index 000000000..f03b3f4c8 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx @@ -0,0 +1,42 @@ +"use client"; +import { ShieldAlert } from "lucide-react"; +import { useAutomationPermissions } from "../hooks/use-automation-permissions"; +import { AutomationJsonForm } from "./components/automation-json-form"; +import { AutomationNewHeader } from "./components/automation-new-header"; + +interface AutomationNewContentProps { + searchSpaceId: number; +} + +/** + * Orchestrator for the raw-JSON create route. Gates on + * ``automations:create`` so users who can't create don't even see the + * form; same panel as the detail page's access-denied state for + * consistency. + */ +export function AutomationNewContent({ searchSpaceId }: AutomationNewContentProps) { + const perms = useAutomationPermissions(); + + if (perms.loading) { + return <div className="h-32 rounded-md border border-border/60 bg-muted/10 animate-pulse" />; + } + + if (!perms.canCreate) { + return ( + <div className="rounded-lg border border-border/60 bg-muted/20 px-6 py-12 text-center"> + <ShieldAlert className="mx-auto h-10 w-10 text-muted-foreground" aria-hidden /> + <h2 className="mt-3 text-base font-semibold text-foreground">Access denied</h2> + <p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto"> + You don't have permission to create automations in this search space. + </p> + </div> + ); + } + + return ( + <> + <AutomationNewHeader searchSpaceId={searchSpaceId} /> + <AutomationJsonForm searchSpaceId={searchSpaceId} /> + </> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx new file mode 100644 index 000000000..845d95166 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx @@ -0,0 +1,122 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { AlertCircle, Code, FileJson, Save } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { createAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Spinner } from "@/components/ui/spinner"; +import { automationCreateRequest } from "@/contracts/types/automation.types"; +import { DEFAULT_AUTOMATION_TEMPLATE } from "@/lib/automations/default-template"; + +interface AutomationJsonFormProps { + searchSpaceId: number; +} + +/** + * Raw-JSON create form. Lets power users skip the chat drafter when they + * already know the shape they want. Flow: + * parse JSON → inject search_space_id → Zod validate → POST → navigate + * + * ``search_space_id`` is injected here rather than required in the pasted + * payload — the user shouldn't have to know their numeric id, and it + * keeps the template copy-paste-friendly across search spaces. + */ +export function AutomationJsonForm({ searchSpaceId }: AutomationJsonFormProps) { + const router = useRouter(); + const { mutateAsync: createAutomation, isPending } = useAtomValue(createAutomationMutationAtom); + const [text, setText] = useState(() => JSON.stringify(DEFAULT_AUTOMATION_TEMPLATE, null, 2)); + const [issues, setIssues] = useState<string[]>([]); + + function handleFormat() { + try { + const parsed = JSON.parse(text); + setText(JSON.stringify(parsed, null, 2)); + setIssues([]); + } catch (err) { + setIssues([`Cannot format — not valid JSON: ${(err as Error).message}`]); + } + } + + async function handleSubmit() { + setIssues([]); + + let parsed: unknown; + try { + parsed = JSON.parse(text); + } catch (err) { + setIssues([`Invalid JSON: ${(err as Error).message}`]); + return; + } + + if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { + setIssues(["Root must be a JSON object."]); + return; + } + + const payload = { ...(parsed as Record<string, unknown>), search_space_id: searchSpaceId }; + const result = automationCreateRequest.safeParse(payload); + if (!result.success) { + setIssues( + result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`) + ); + return; + } + + try { + const created = await createAutomation(result.data); + router.push(`/dashboard/${searchSpaceId}/automations/${created.id}`); + } catch (err) { + setIssues([(err as Error).message ?? "Submit failed"]); + } + } + + const hasIssues = issues.length > 0; + + return ( + <Card> + <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> + <CardTitle className="text-base font-semibold inline-flex items-center gap-2"> + <FileJson className="h-4 w-4 text-muted-foreground" aria-hidden /> + Definition + triggers + </CardTitle> + <Button type="button" variant="outline" size="sm" onClick={handleFormat}> + <Code className="mr-2 h-3.5 w-3.5" /> + Format + </Button> + </CardHeader> + <CardContent className="space-y-4"> + <textarea + value={text} + onChange={(e) => setText(e.target.value)} + spellCheck={false} + rows={24} + className="w-full rounded-md border border-input bg-background px-3 py-2 text-xs font-mono text-foreground shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring resize-y min-h-[16rem]" + aria-label="Automation JSON" + /> + + {hasIssues && ( + <div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2"> + <div className="flex items-center gap-1.5 text-xs font-medium text-destructive mb-1.5"> + <AlertCircle className="h-3.5 w-3.5" aria-hidden /> + {issues.length === 1 ? "1 issue" : `${issues.length} issues`} + </div> + <ul className="space-y-0.5 text-xs text-destructive list-disc list-inside"> + {issues.map((issue) => ( + <li key={issue}>{issue}</li> + ))} + </ul> + </div> + )} + + <div className="flex items-center justify-end gap-2"> + <Button type="button" onClick={handleSubmit} disabled={isPending} size="sm"> + {isPending ? <Spinner size="xs" className="mr-2" /> : <Save className="mr-2 h-4 w-4" />} + Create automation + </Button> + </div> + </CardContent> + </Card> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx new file mode 100644 index 000000000..aef2744d5 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx @@ -0,0 +1,42 @@ +"use client"; +import { ArrowLeft, MessageSquarePlus } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; + +interface AutomationNewHeaderProps { + searchSpaceId: number; +} + +export function AutomationNewHeader({ searchSpaceId }: AutomationNewHeaderProps) { + return ( + <div className="space-y-3"> + <Button asChild variant="ghost" size="sm" className="-ml-2 h-auto px-2 py-1"> + <Link + href={`/dashboard/${searchSpaceId}/automations`} + className="text-xs text-muted-foreground" + > + <ArrowLeft className="mr-1.5 h-3.5 w-3.5" /> + Back to automations + </Link> + </Button> + + <div className="flex items-start justify-between gap-4 flex-wrap"> + <div className="space-y-1"> + <h1 className="text-xl md:text-2xl font-semibold text-foreground"> + New automation · raw JSON + </h1> + <p className="text-sm text-muted-foreground max-w-2xl"> + Paste an ``AutomationCreate`` payload and submit. Validated against the schema before + save. Prefer natural language? Use chat instead. + </p> + </div> + <Button asChild variant="outline" size="sm"> + <Link href={`/dashboard/${searchSpaceId}/new-chat`}> + <MessageSquarePlus className="mr-2 h-4 w-4" /> + Switch to chat + </Link> + </Button> + </div> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/page.tsx new file mode 100644 index 000000000..f6e8e0008 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/page.tsx @@ -0,0 +1,15 @@ +import { AutomationNewContent } from "./automation-new-content"; + +export default async function NewAutomationPage({ + params, +}: { + params: Promise<{ search_space_id: string }>; +}) { + const { search_space_id } = await params; + + return ( + <div className="w-full space-y-6"> + <AutomationNewContent searchSpaceId={Number(search_space_id)} /> + </div> + ); +} diff --git a/surfsense_web/lib/automations/default-template.ts b/surfsense_web/lib/automations/default-template.ts new file mode 100644 index 000000000..8963992cb --- /dev/null +++ b/surfsense_web/lib/automations/default-template.ts @@ -0,0 +1,44 @@ +/** + * Minimal valid ``AutomationCreate`` skeleton used to seed the raw-JSON + * create form. ``search_space_id`` is omitted on purpose — the form + * injects it from the route so users never have to know their id. + * + * The shape matches the Pydantic ``AutomationCreate`` model less the + * search_space_id field; Zod validates the merged payload before submit. + */ +export const DEFAULT_AUTOMATION_TEMPLATE = { + name: "My automation", + description: null, + definition: { + name: "My automation", + goal: null, + plan: [ + { + step_id: "step_1", + action: "agent_task", + params: { + query: "Summarize new docs added to folder 12 since the last run.", + }, + }, + ], + execution: { + timeout_seconds: 600, + max_retries: 2, + retry_backoff: "exponential", + concurrency: "drop_if_running", + on_failure: [], + }, + metadata: { tags: [] }, + }, + triggers: [ + { + type: "schedule", + params: { + cron: "0 9 * * 1-5", + timezone: "UTC", + }, + static_inputs: {}, + enabled: true, + }, + ], +} as const; From 91962ba879a677e0fcf43bc2e1dc7abe0732b462 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 02:48:47 +0200 Subject: [PATCH 141/198] fix automation run inputs, hitl routing, and detail UI polish --- .../main_agent/tools/automation/create.py | 11 +- .../hitl/approvals/self_gated/request.py | 7 + .../app/automations/dispatch/run.py | 9 +- .../automation-detail-content.tsx | 2 - .../automation-triggers-section.tsx | 33 +-- .../components/trigger-card.tsx | 29 ++- .../tool-ui/automation/create-automation.tsx | 213 ++++++++++++++---- surfsense_web/lib/format-date.ts | 40 +++- 8 files changed, 258 insertions(+), 86 deletions(-) diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py index 07b579f3b..173d302e5 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py @@ -22,11 +22,14 @@ from typing import Any from uuid import UUID from fastapi import HTTPException +from langchain.tools import ToolRuntime from langchain_core.messages import HumanMessage from langchain_core.tools import tool from pydantic import ValidationError -from app.agents.new_chat.tools.hitl import request_approval +from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated import ( + request_approval, +) from app.automations.schemas.api import AutomationCreate from app.automations.services.automation import AutomationService from app.db import User, async_session_maker @@ -56,7 +59,7 @@ def create_create_automation_tool( uid = UUID(user_id) if isinstance(user_id, str) else user_id @tool - async def create_automation(intent: str) -> dict[str, Any]: + async def create_automation(intent: str, runtime: ToolRuntime) -> dict[str, Any]: """Draft + save an automation from a natural-language intent. Use this when the user wants SurfSense to do something on its own @@ -137,6 +140,7 @@ def create_create_automation_tool( tool_name="create_automation", params=card_params, context={"search_space_id": search_space_id}, + tool_call_id=runtime.tool_call_id, ) if result.rejected: @@ -200,6 +204,5 @@ def _extract_json(text: str) -> dict[str, Any] | None: def _format_validation_issues(exc: ValidationError) -> list[str]: return [ - f"{'.'.join(str(p) for p in err['loc'])}: {err['msg']}" - for err in exc.errors() + f"{'.'.join(str(p) for p in err['loc'])}: {err['msg']}" for err in exc.errors() ] diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/request.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/request.py index 8729ea85b..2f7e3cd35 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/request.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/shared/hitl/approvals/self_gated/request.py @@ -49,6 +49,7 @@ def request_approval( params: dict[str, Any], context: dict[str, Any] | None = None, trusted_tools: list[str] | None = None, + tool_call_id: str | None = None, ) -> HITLResult: """Pause the graph for user approval and return the user's decision. @@ -64,6 +65,10 @@ def request_approval( forwarded verbatim to the FE for richer card chrome. trusted_tools: Per-session allowlist; when ``tool_name`` is in it the interrupt is skipped and the tool runs immediately. + tool_call_id: Caller's LangChain tool-call id. Required for tools + running directly on the main agent; subagent-mounted tools omit + it (the ``task`` chokepoint stamps it on re-raise — see + :mod:`...checkpointed_subagent_middleware.propagation`). Returns: :class:`HITLResult` with ``rejected=True`` if the user declined or @@ -90,6 +95,8 @@ def request_approval( interrupt_type=action_type, context=context, ) + if tool_call_id: + payload["tool_call_id"] = tool_call_id approval = interrupt(payload) parsed = parse_lc_envelope(approval) diff --git a/surfsense_backend/app/automations/dispatch/run.py b/surfsense_backend/app/automations/dispatch/run.py index e317a13b9..02d0b0356 100644 --- a/surfsense_backend/app/automations/dispatch/run.py +++ b/surfsense_backend/app/automations/dispatch/run.py @@ -67,8 +67,15 @@ async def dispatch_run( def _validate_inputs( definition: AutomationDefinition, inputs: dict[str, Any] ) -> dict[str, Any]: + """Validate merged inputs against the optional declared schema. + + No declared schema → pass through (runtime inputs like ``fired_at`` / + ``last_fired_at`` and trigger ``static_inputs`` must still reach the + template context). Returning ``{}`` here strips them and makes Jinja + blow up on any ``{{ inputs.* }}`` reference. + """ if definition.inputs is None or not definition.inputs.schema_: - return {} + return inputs try: jsonschema.validate(instance=inputs, schema=definition.inputs.schema_) except jsonschema.ValidationError as exc: diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx index 253d6ae67..49df3633e 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx @@ -77,10 +77,8 @@ export function AutomationDetailContent({ <AutomationTriggersSection triggers={automation.triggers} automationId={automation.id} - searchSpaceId={searchSpaceId} canUpdate={perms.canUpdate} canDelete={perms.canDelete} - canCreate={perms.canCreate} /> <AutomationRunsSection automationId={automation.id} /> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx index 8cc62f5c8..33c8373a1 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx @@ -1,7 +1,5 @@ "use client"; -import { CalendarClock, MessageSquarePlus } from "lucide-react"; -import Link from "next/link"; -import { Button } from "@/components/ui/button"; +import { CalendarClock } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import type { Trigger } from "@/contracts/types/automation.types"; import { TriggerCard } from "./trigger-card"; @@ -9,43 +7,28 @@ import { TriggerCard } from "./trigger-card"; interface AutomationTriggersSectionProps { triggers: Trigger[]; automationId: number; - searchSpaceId: number; canUpdate: boolean; canDelete: boolean; - canCreate: boolean; } /** * The Triggers card. Lists each attached trigger with its own enable - * toggle and remove button. Adding a new trigger is intent-driven (via - * chat) for v1 — same philosophy as creating an automation, so the - * empty/add CTA links to a new chat rather than opening a form. + * toggle and remove button. v1 attaches triggers at automation-creation + * time only; there is no in-place "add trigger" affordance here. */ export function AutomationTriggersSection({ triggers, automationId, - searchSpaceId, canUpdate, canDelete, - canCreate, }: AutomationTriggersSectionProps) { return ( <Card> - <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> - <div className="space-y-1"> - <CardTitle className="text-base font-semibold">Triggers</CardTitle> - <p className="text-xs text-muted-foreground"> - When this automation fires. v1 supports scheduled triggers only. - </p> - </div> - {canCreate && ( - <Button asChild variant="outline" size="sm"> - <Link href={`/dashboard/${searchSpaceId}/new-chat`}> - <MessageSquarePlus className="mr-2 h-4 w-4" /> - Add via chat - </Link> - </Button> - )} + <CardHeader className="pb-4"> + <CardTitle className="text-base font-semibold">Triggers</CardTitle> + <p className="text-xs text-muted-foreground"> + When this automation fires. v1 supports scheduled triggers only. + </p> </CardHeader> <CardContent> {triggers.length === 0 ? ( diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx index afadf589a..ec0246e49 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx @@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import type { Trigger } from "@/contracts/types/automation.types"; import { describeCron } from "@/lib/automations/describe-cron"; -import { formatRelativeDate } from "@/lib/format-date"; +import { formatRelativeDate, formatRelativeFutureDate } from "@/lib/format-date"; import { DeleteTriggerDialog } from "./delete-trigger-dialog"; interface TriggerCardProps { @@ -91,11 +91,18 @@ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: Tri <div className="px-4 py-3 space-y-3 text-xs"> {(trigger.last_fired_at || trigger.next_fire_at) && ( - <dl className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-1.5"> + <dl className="grid grid-cols-[auto_minmax(0,1fr)] items-baseline gap-x-3 gap-y-1"> {trigger.next_fire_at && ( - <TimeRow label="Next fire" iso={trigger.next_fire_at} highlight={trigger.enabled} /> + <TimeRow + label="Next fire" + iso={trigger.next_fire_at} + tense="future" + highlight={trigger.enabled} + /> + )} + {trigger.last_fired_at && ( + <TimeRow label="Last fired" iso={trigger.last_fired_at} tense="past" /> )} - {trigger.last_fired_at && <TimeRow label="Last fired" iso={trigger.last_fired_at} />} </dl> )} @@ -126,17 +133,20 @@ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: Tri function TimeRow({ label, iso, + tense, highlight = false, }: { label: string; iso: string; + tense: "past" | "future"; highlight?: boolean; }) { + const formatted = tense === "future" ? formatRelativeFutureDate(iso) : formatRelativeDate(iso); return ( - <div className="flex items-baseline gap-2 min-w-0"> - <dt className="text-muted-foreground shrink-0 inline-flex items-center gap-1"> + <> + <dt className="text-muted-foreground inline-flex items-center gap-1.5 whitespace-nowrap"> <Clock className="h-3 w-3" aria-hidden /> - {label}: + {label} </dt> <dd className={ @@ -144,9 +154,10 @@ function TimeRow({ ? "text-foreground font-medium min-w-0 truncate" : "text-muted-foreground min-w-0 truncate" } + title={new Date(iso).toLocaleString()} > - {formatRelativeDate(iso)} + {formatted} </dd> - </div> + </> ); } diff --git a/surfsense_web/components/tool-ui/automation/create-automation.tsx b/surfsense_web/components/tool-ui/automation/create-automation.tsx index 713c5fd46..00b120d38 100644 --- a/surfsense_web/components/tool-ui/automation/create-automation.tsx +++ b/surfsense_web/components/tool-ui/automation/create-automation.tsx @@ -2,16 +2,26 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react"; import { useAtomValue } from "jotai"; -import { CornerDownLeftIcon, ExternalLink, Workflow } from "lucide-react"; +import { + AlertCircle, + Code, + CornerDownLeftIcon, + ExternalLink, + Pencil, + Workflow, +} from "lucide-react"; import Link from "next/link"; -import { useCallback, useEffect, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { Button } from "@/components/ui/button"; +import { automationCreateRequest } from "@/contracts/types/automation.types"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl"; import { AutomationDraftPreview } from "./automation-draft-preview"; +const editArgsSchema = automationCreateRequest.omit({ search_space_id: true }); + // ---------------------------------------------------------------------------- // Result discrimination — mirrors the backend return shapes in // app/agents/multi_agent_chat/main_agent/tools/automation/create.py. @@ -62,12 +72,11 @@ function hasStatus(value: unknown, status: string): boolean { // ---------------------------------------------------------------------------- // Approval card — pending → processing → complete / rejected. // -// v1 deliberately supports only approve/reject. The drafted JSON is complex -// (full plan + triggers) and we already have a multi-turn refinement path via -// chat ("make it run at 10am instead" → the agent re-calls the tool with a -// refined intent). An in-card edit form would duplicate that flow and add UX -// surface area we don't need yet — leave it for the raw-JSON path on the -// detail page. +// Edit toggle reuses the same primitives as the Create-via-JSON page: raw +// textarea, Format, Zod validation against ``AutomationCreate`` (minus the +// ``search_space_id`` field, which the backend injects). Approve dispatches +// an ``edit`` decision with the parsed args when edits are pending, otherwise +// a plain ``approve``. Multi-turn chat refinement still works as a fallback. // ---------------------------------------------------------------------------- interface ApprovalCardProps { @@ -83,28 +92,34 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { const allowedDecisions = reviewConfig?.allowed_decisions ?? ["approve", "reject"]; const canApprove = allowedDecisions.includes("approve"); const canReject = allowedDecisions.includes("reject"); + const canEdit = allowedDecisions.includes("edit"); - const draft = useMemo(() => extractDraft(args), [args]); + const [pendingEdits, setPendingEdits] = useState<Record<string, unknown> | null>(null); + const [isEditing, setIsEditing] = useState(false); + + const effectiveArgs = pendingEdits ?? args; + const draft = useMemo(() => extractDraft(effectiveArgs), [effectiveArgs]); const handleApprove = useCallback(() => { - if (phase !== "pending" || !canApprove) return; + if (phase !== "pending" || !canApprove || isEditing) return; setProcessing(); onDecision({ - type: "approve", + type: pendingEdits ? "edit" : "approve", edited_action: { name: interruptData.action_requests[0]?.name ?? "create_automation", - args, + args: pendingEdits ?? args, }, }); - }, [phase, canApprove, setProcessing, onDecision, interruptData, args]); + }, [phase, canApprove, isEditing, setProcessing, onDecision, interruptData, args, pendingEdits]); const handleReject = useCallback(() => { - if (phase !== "pending" || !canReject) return; + if (phase !== "pending" || !canReject || isEditing) return; setRejected(); onDecision({ type: "reject", message: "User rejected the automation draft." }); - }, [phase, canReject, setRejected, onDecision]); + }, [phase, canReject, isEditing, setRejected, onDecision]); useEffect(() => { + if (isEditing) return; const handler = (e: KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.metaKey) { handleApprove(); @@ -112,46 +127,77 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); - }, [handleApprove]); + }, [handleApprove, isEditing]); return ( <div className="my-4 max-w-lg overflow-hidden rounded-2xl border bg-muted/30 transition-[box-shadow] duration-300"> - <div className="flex items-start gap-3 px-5 pt-5 pb-4 select-none"> - <Workflow className="h-5 w-5 text-muted-foreground mt-0.5 shrink-0" aria-hidden /> - <div className="min-w-0"> - <p className="text-sm font-semibold text-foreground"> - {phase === "rejected" - ? "Automation cancelled" - : phase === "processing" - ? "Saving automation" - : phase === "complete" - ? "Automation saved" - : "Create automation"} - </p> - {phase === "processing" ? ( - <TextShimmerLoader text="Saving automation" size="sm" /> - ) : phase === "complete" ? ( - <p className="text-xs text-muted-foreground mt-0.5"> - Automation created from this draft + <div className="flex items-start justify-between gap-3 px-5 pt-5 pb-4 select-none"> + <div className="flex items-start gap-3 min-w-0"> + <Workflow className="h-5 w-5 text-muted-foreground mt-0.5 shrink-0" aria-hidden /> + <div className="min-w-0"> + <p className="text-sm font-semibold text-foreground"> + {phase === "rejected" + ? "Automation cancelled" + : phase === "processing" + ? "Saving automation" + : phase === "complete" + ? "Automation saved" + : "Create automation"} </p> - ) : phase === "rejected" ? ( - <p className="text-xs text-muted-foreground mt-0.5"> - No automation was saved — ask in chat to refine and try again. - </p> - ) : ( - <p className="text-xs text-muted-foreground mt-0.5"> - Review and approve to save. To change anything, reply in chat — I'll redraft. - </p> - )} + {phase === "processing" ? ( + <TextShimmerLoader + text={pendingEdits ? "Saving with your edits" : "Saving automation"} + size="sm" + /> + ) : phase === "complete" ? ( + <p className="text-xs text-muted-foreground mt-0.5"> + {pendingEdits + ? "Automation saved with your edits" + : "Automation created from this draft"} + </p> + ) : phase === "rejected" ? ( + <p className="text-xs text-muted-foreground mt-0.5"> + No automation was saved — ask in chat to refine and try again. + </p> + ) : ( + <p className="text-xs text-muted-foreground mt-0.5"> + {pendingEdits + ? "Showing your edits. Approve to save, or edit again." + : "Review and approve to save. Edit for fine-tuning, or reply in chat for a redraft."} + </p> + )} + </div> </div> + {phase === "pending" && canEdit && !isEditing && ( + <Button + size="sm" + variant="ghost" + className="rounded-lg text-muted-foreground -mt-1 -mr-2 shrink-0" + onClick={() => setIsEditing(true)} + > + <Pencil className="size-3.5" /> + Edit + </Button> + )} </div> <div className="mx-5 h-px bg-border/50" /> <div className="px-5 py-4"> - <AutomationDraftPreview draft={draft} raw={args} /> + {isEditing ? ( + <JsonEditor + initialValue={effectiveArgs} + onSave={(parsed) => { + setPendingEdits(parsed); + setIsEditing(false); + }} + onCancel={() => setIsEditing(false)} + /> + ) : ( + <AutomationDraftPreview draft={draft} raw={effectiveArgs} /> + )} </div> - {phase === "pending" && ( + {phase === "pending" && !isEditing && ( <> <div className="mx-5 h-px bg-border/50" /> <div className="px-5 py-4 flex items-center gap-2 select-none"> @@ -178,6 +224,85 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { ); } +interface JsonEditorProps { + initialValue: Record<string, unknown>; + onSave: (parsed: Record<string, unknown>) => void; + onCancel: () => void; +} + +function JsonEditor({ initialValue, onSave, onCancel }: JsonEditorProps) { + const [text, setText] = useState(() => JSON.stringify(initialValue, null, 2)); + const [issues, setIssues] = useState<string[]>([]); + + function handleFormat() { + try { + setText(JSON.stringify(JSON.parse(text), null, 2)); + setIssues([]); + } catch (err) { + setIssues([`Cannot format — not valid JSON: ${(err as Error).message}`]); + } + } + + function handleSave() { + setIssues([]); + let parsed: unknown; + try { + parsed = JSON.parse(text); + } catch (err) { + setIssues([`Invalid JSON: ${(err as Error).message}`]); + return; + } + const result = editArgsSchema.safeParse(parsed); + if (!result.success) { + setIssues( + result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`) + ); + return; + } + onSave(result.data as unknown as Record<string, unknown>); + } + + return ( + <div className="space-y-3"> + <textarea + value={text} + onChange={(e) => setText(e.target.value)} + spellCheck={false} + rows={16} + className="w-full rounded-md border border-input bg-background px-3 py-2 text-xs font-mono text-foreground shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring resize-y min-h-[12rem]" + aria-label="Automation JSON" + /> + {issues.length > 0 && ( + <div className="rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2"> + <div className="flex items-center gap-1.5 text-xs font-medium text-destructive"> + <AlertCircle className="h-3.5 w-3.5" aria-hidden /> + {issues.length} issue{issues.length === 1 ? "" : "s"} + </div> + <ul className="mt-1.5 space-y-0.5 text-xs text-destructive/90 list-disc list-inside"> + {issues.map((issue) => ( + <li key={issue} className="font-mono"> + {issue} + </li> + ))} + </ul> + </div> + )} + <div className="flex items-center justify-end gap-2"> + <Button type="button" variant="ghost" size="sm" onClick={onCancel}> + Cancel + </Button> + <Button type="button" variant="outline" size="sm" onClick={handleFormat}> + <Code className="mr-1.5 h-3.5 w-3.5" /> + Format + </Button> + <Button type="button" size="sm" onClick={handleSave}> + Save edits + </Button> + </div> + </div> + ); +} + // ---------------------------------------------------------------------------- // Terminal result cards. // ---------------------------------------------------------------------------- diff --git a/surfsense_web/lib/format-date.ts b/surfsense_web/lib/format-date.ts index 9decd3402..c2f445537 100644 --- a/surfsense_web/lib/format-date.ts +++ b/surfsense_web/lib/format-date.ts @@ -1,4 +1,12 @@ -import { differenceInDays, differenceInMinutes, format, isToday, isYesterday } from "date-fns"; +import { + differenceInDays, + differenceInMinutes, + format, + isThisYear, + isToday, + isTomorrow, + isYesterday, +} from "date-fns"; /** * Format a date string as a human-readable relative time @@ -23,6 +31,36 @@ export function formatRelativeDate(dateString: string): string { return format(date, "MMM d, yyyy"); } +/** + * Format a future date string as a human-readable countdown. + * - < 1 min: "Any moment" + * - < 60 min: "in 15m" + * - Today: "Today, 2:30 PM" + * - Tomorrow: "Tomorrow, 2:30 PM" + * - < 7 days: "in 3d" + * - This year: "May 30, 2:30 PM" + * - Older: "Jan 15, 2027" + * + * Mirrors {@link formatRelativeDate} but for moments strictly after now. + * Falls back to the past-relative formatter if the timestamp is not in + * the future (defensive — guards against stale "next_fire_at" values). + */ +export function formatRelativeFutureDate(dateString: string): string { + const date = new Date(dateString); + const now = new Date(); + const minutesAhead = differenceInMinutes(date, now); + const daysAhead = differenceInDays(date, now); + + if (minutesAhead <= 0) return formatRelativeDate(dateString); + if (minutesAhead < 1) return "Any moment"; + if (minutesAhead < 60) return `in ${minutesAhead}m`; + if (isToday(date)) return `Today, ${format(date, "h:mm a")}`; + if (isTomorrow(date)) return `Tomorrow, ${format(date, "h:mm a")}`; + if (daysAhead < 7) return `in ${daysAhead}d`; + if (isThisYear(date)) return format(date, "MMM d, h:mm a"); + return format(date, "MMM d, yyyy"); +} + /** * Format a thread's last-updated timestamp for the chats sidebars. * Example: "Mar 23, 2026 at 4:30 PM" From b90bed2dbdfe7941f1a886481c45121309076501 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 15:38:57 +0200 Subject: [PATCH 142/198] chore: drop local design plan --- automation-design-plan.md | 1240 ------------------------------------- 1 file changed, 1240 deletions(-) delete mode 100644 automation-design-plan.md diff --git a/automation-design-plan.md b/automation-design-plan.md deleted file mode 100644 index db5f7a23c..000000000 --- a/automation-design-plan.md +++ /dev/null @@ -1,1240 +0,0 @@ -# SurfSense Automation Feature — Design Plan (v2) - -A generic, extensible automation system for SurfSense that lets users (and -future SurfSense features) trigger agent work on a schedule, on an external -event, or on demand — with the ability to author automations either by hand -or from a natural-language description that yields an editable, structured -definition. - -This document supersedes the v1 draft. It folds in the design audit pass and -the corrections from working through worked examples (notably: removing the -connector bias, clarifying the executor's role, integrating MCP cleanly, and -committing to JSON Schema as the single declarative language). - ---- - -## 1. The load-bearing principle - -> **The JSON definition is the program. Everything else is interpreter.** - -Every decision in this document serves that principle. If we ever face a -design choice and one option lets some behavior leak out of the definition -into the engine, we pick the other option. - -Three properties follow from this principle, and they're the reason the -system will survive feature growth: - -- **Reproducibility** — same definition + same inputs → same observable - behavior, regardless of which version of the engine runs it. -- **Portability** — definitions can be exported, imported, version- - controlled, code-reviewed, and shared across SurfSense instances. -- **LLM tractability** — the NL authoring flow works because the LLM only - needs to produce a self-contained JSON document that validates against a - schema. It doesn't need to understand the engine. - ---- - -## 2. The three-layer contract - -The system is structured as three layers. Layers 1 and 3 are defined by -SurfSense developers (at registration time). Layer 2 is what users write -(or the NL generator produces). The runtime reads all three to do its job. - -| Layer | What it is | Defined by | -| ----- | ---------- | ---------- | -| **1. Action contract** | Per-action params and output schema | Developers, at startup | -| **2. Automation definition** | One concrete saved automation | Users (or NL generator) | -| **3. Trigger contract** | Per-trigger params and payload schemas | Developers, at startup | - -Each layer constrains the next. The runtime reads all three but doesn't -know what's in them ahead of time. That's how a new action or trigger -type becomes available across the engine without code changes outside its -registration. - -A unification layer below Layer 1 — one catalog of "things this SurfSense -instance can do," shared by automations, agents, and future surfaces — was -considered and deferred (§3). v1 actions are stand-alone. - -### Schema language - -Every shape in every layer is described in **JSON Schema (draft 2020-12).** -No exceptions, no parallel languages, no inline shorthand. Two documented -extensions on top: - -- `default: "$some_token"` — runtime-resolved defaults. The vocabulary is - fixed: `$last_fired_at`, `$creator`, `$space_default`. The engine resolves - these to values before validation. -- `x-surfsense-*` annotations — editor hints (widget type, autocomplete - source). The validator ignores them; the form editor reads them. - ---- - -## 3. Capability unification layer — deferred to post-v1 - -Earlier drafts introduced a `Capability` registry as Layer 1: one catalog -of "things this SurfSense instance can do," shared by the automation -engine (as actions), the agent (as tools), and any future HTTP surface. -The motivation is real — one source of truth beats N parallel registries — -but v1 has a single action (`agent_task`) and a single consumer (the -automation engine). The five-field shape sketched earlier (`id`, -`description`, `input_schema`, `output_schema`, `handler`) cannot safely -host any non-trivial capability: it carries no caller identity, no -search-space scoping, and no authorization gate on tool delegation. -Building the abstraction with one consumer would lock in a shape that -doesn't survive the second consumer. - -The unification layer returns when the second consumer lands (Phase 2 -tight actions or Phase 4 MCP), redesigned from the start with: - -- A `CallContext` carrying caller user id, search space id, and run id, - passed to every handler invocation. -- Explicit scope declarations per capability (e.g. `reads:documents`, - `writes:slack`, `destructive`) for the authorization layer to read. -- A per-user, per-search-space filter consulted at both definition save - time (validating `agent_task.tools`) and run time (scoping the agent's - tool list to what the automation creator can delegate). - -Until then: - -- v1 actions are stand-alone units (Layer 1 below); the automation engine - reads its own action registry, nothing else. -- `agent_task.params.tools` is a forward-looking allowlist field with no - v1 semantics beyond "list of string identifiers." The handler's tool - resolution is opaque to the automation contract. - -### Credentials — deferred to Phase 2 - -External-credential handlers (Slack, email, etc.) require per-user or -per-connection auth. v1 actions run server-side with app-level -configuration. When tight actions ship in Phase 2, the credential design -lands as part of the unification redesign: connection IDs in the -definition (never tokens); credentials loaded per-call by the handler -context (never pre-loaded into worker memory); credentials never enter -LLM context. - -### MCP — deferred to Phase 4 - -External tool servers feeding tools into a shared registry land with the -rest of the integration tooling in Phase 4, after the unification layer -is in place. The two-tier registry, `mcp_connections` and `mcp_tools` -tables, and the harvester arrive as a single coherent step then. - ---- - -## 4. Action contract - -An `Action` is what a user references in a plan step. Some actions are -deterministic single-purpose handlers (`slack_post`, `send_email`); one -action (`agent_task`) hosts an LLM and a tool allowlist for cases where -judgment is needed. The contract is the same in both cases — only the -handler differs. - -```python -@dataclass(frozen=True, slots=True) -class ActionDefinition: - type: str # "agent_task", "slack_post" - name: str # short UI label - description: str # for the NL generator and the UI - params_schema: dict # JSON Schema for step.params - handler: ActionHandler -``` - -This is the v1 shape: five fields, no handler context, no output -contract, no artifact declaration. The deferrals are intentional: - -- **`output_contract`** — Phase 2. Deterministic handlers will return - a fixed shape; v1's only action (`agent_task`) takes an - `output_schema` inside `params` and validates against that instead. -- **`produces_artifacts`** — Phase 5. Artifact lifecycle (storage, - signed URLs, retention) is its own design step; v1 handlers - persist their own outputs. -- **Handler context** — paired with the unification redesign (§3). - v1 handlers receive `(args)` only; per-user / per-search-space - behavior is not yet a v1 concern. - -### Tight vs loose actions - -Two patterns coexist by design: - -- **Tight actions** (`slack_post`, `linear_create_issue`, - `send_email`) — deterministic single-purpose handlers. ~20 LOC - each. **Phase 2.** -- **Loose actions** (`agent_task`) — params_schema accepts a `prompt`, - a `tools` allowlist, and an optional `output_schema` declaring what - the agent must return; the handler validates the agent's output - against it. **v1.** - -The agent's `tools` allowlist resolves opaquely in v1; the redesigned -unification layer (§3) will give both invocation modes access to the -same vocabulary, with per-user authorization gating both. - -### How names in the definition become function calls - -The definition contains strings like `"action": "agent_task"`. The -string is just a name — it does not point to a function. At runtime, -the executor performs a **name-based lookup** against the action -registry: - -```python -action_def = action_registry.get(step.action) # dict lookup -handler = action_def.handler # Python callable -result = await handler(resolved_params) # invocation -``` - -The registry is a Python dict populated at process startup. Each entry -in `automations/registries/actions/*.py` calls `register_action(...)` -at module import time, putting its `ActionDefinition` (including the -handler function reference) into the registry. - -The definition is pure data. The registry is the engine's runtime -vocabulary. They meet at name-based lookup; nothing else crosses the -boundary. - -### The full expressive spectrum - -The contract supports a continuous spectrum from purely deterministic to -fully agentic. Six practical shapes worth recognizing: - -| Shape | Example | Cost / latency profile | -| --- | --- | --- | -| **1. Direct call** | `slack_post` with literal channel and template | No LLM. ~200ms. Fractions of a cent. | -| **2. Direct call with computed inputs** | `linear_create_issue` using `{{summary.title}}` from a prior step | No LLM for this step. Cheap. | -| **3. Single-domain agent task** | `agent_task` with `tools: ["slack.*"]` only | One LLM, bounded toolset. | -| **4. Multi-domain agent task, narrow** | `agent_task` with `tools: ["github.list_pull_requests", "linear.create_issue"]` | One LLM, named tools. | -| **5. Multi-domain agent task, broad** | `agent_task` with `tools: ["slack.*", "github.*", "linear.*"]` | One LLM, large toolset, most agentic. | -| **6. Composed plan** | `agent_task` (narrow) for thinking → `slack_post` + `linear_create_issue` for acting | Best cost-to-power ratio. | - -Shape 6 is the underrated one and the cost-and-speed answer. The agent -reasons once (Shape 3 or 4) and its structured output drives several -deterministic actions. This is roughly 5–10x cheaper and 3–4x faster than -forcing the agent to do everything (Shape 5) and produces the same outcome. - -**The NL generator's job is to propose Shape 6-style plans by default.** -The Review LLM flags proposals that use `agent_task` for steps a -deterministic action could handle. This is the discipline that keeps -automations cheap at scale. - -The user navigates the spectrum by intent (describing what they want), not -by mechanism — the shape selection is the engine's responsibility, not the -user's. - ---- - -## 5. Automation definition - -This is the JSON the user writes (or the NL generator produces). Stored in -`automations.definition` as JSONB. - -### Top-level shape - -```jsonc -{ - "schema_version": "1.0", - "name": "Daily competitor digest", - "goal": "Summarize new competitor content and post to Slack", - - "inputs": { - "schema": { - "type": "object", - "required": ["since"], - "properties": { - "since": { "type": "string", "format": "date-time", - "default": "$last_fired_at" }, - "tags": { "type": "array", "items": { "type": "string" }, - "default": ["competitor"] } - } - } - }, - - "triggers": [ - { - "type": "schedule", - "params": { "cron": "0 9 * * 1-5", "timezone": "Africa/Kigali" } - } - ], - - "plan": [ - { - "step_id": "research", - "action": "agent_task", - "params": { - "prompt": "Find documents tagged {{inputs.tags}} indexed since {{inputs.since}}. Return JSON with bullets and source_doc_ids.", - "tools": ["search_space.query", "search_space.fetch_document"], - "model": "anthropic/claude-sonnet-4-7", - "output_schema": { - "type": "object", - "required": ["bullets", "source_doc_ids"], - "properties": { - "bullets": { "type": "array", "items": { "type": "string" } }, - "source_doc_ids": { "type": "array", "items": { "type": "string" } } - } - } - }, - "output_as": "summary" - }, - { - "step_id": "deliver", - "action": "slack_post", - "params": { - "channel_id": "C0123", - "message_template": "*Competitor digest*\n\n{% for b in summary.bullets %}• {{b}}\n{% endfor %}" - } - } - ], - - "execution": { - "timeout_seconds": 600, - "max_retries": 2, - "retry_backoff": "exponential", - "concurrency": "drop_if_running", - "on_failure": [ /* steps to run if main plan fails after retries */ ] - }, - - "metadata": { "tags": ["digest"] } -} -``` - -### Plan steps - -```jsonc -{ - "step_id": "...", // unique within plan - "action": "...", // references an ActionDefinition.type - "when": "{{ ... }}", // optional Jinja expr → bool; false = skip - "params": { ... }, // validated against action's params_schema - "output_as": "...", // binds output to this name for later steps - "max_retries": 0, // optional, overrides automation default - "timeout_seconds": 1200 // optional, overrides automation default -} -``` - -Steps run **sequentially**. No parallelism, no DAGs, no loops. If a user -needs branching, they use `when:` on multiple steps. If they need -parallelism or iteration, they use `agent_task` and let the agent reason -about it, or they compose automations through events (§7.5). - ---- - -## 6. Trigger contract - -Three trigger types. That's the entire taxonomy. - -### `schedule` - -```python -TriggerDefinition( - type="schedule", - params_model=ScheduleTriggerParams, # cron + timezone -) -# At fire time the schedule producer emits runtime inputs -# (fired_at, scheduled_for, last_fired_at) which are merged with the -# trigger row's static_inputs (static wins) and validated against -# automation.definition.inputs.schema_. -``` - -Implementation: extends `app/utils/periodic_scheduler.py`, which already -reads connector sync schedules. Adds a second source — `automation_triggers -WHERE type='schedule'`. Same Celery Beat checker, two source tables. - -Minimum interval: 1 minute (the existing checker's resolution). The form -editor warns when users set intervals under 15 minutes that they probably -want an event trigger instead. - -### `webhook` - -```python -TriggerDefinition( - type="webhook", - params_schema={ - "type": "object", - "properties": { - "input_mapping": { - "type": "object", - "additionalProperties": { "type": "string" } - # values are JSONPath expressions - } - } - }, - # payload is whatever the POST body is; user-defined shape via mapping -) -``` - -Endpoint: `POST /api/v1/automations/{id}/fire`. Bearer token shown once, -hashed at rest, rotatable, revocable. Returns `202 Accepted` with the -created run's URL. Caller polls for status; we do not push callbacks in -v1 (a `callback_webhook` action can be added later). - -Idempotency: honors `Idempotency-Key` header or `idempotency_key` in body. -Dedups against runs in the last 24 hours. - -### `event` - -```python -TriggerDefinition( - type="event", - params_schema={ - "type": "object", - "required": ["event_type"], - "properties": { - "event_type": { "type": "string" }, # e.g. "drive.file_added" - # or "surfsense.podcast.generated" - "filters": { "$ref": "#/definitions/filter_expression" } - } - } - # payload shape is documented per event_type in a separate registry -) -``` - -**Events absorb both connector events and internal SurfSense events.** A -file added to Drive and a podcast finishing in SurfSense are both events -in the same `domain_events` table, both subscribable by automations, both -matched by the same dispatcher code. The engine doesn't distinguish. - -### Filter grammar - -Filters are JSON-structured operators, not expressions. This is the one -place we deliberately don't use Jinja, because filters run on a hot path -(every event matched against every subscribing trigger) and structured -filters can be indexed and short-circuited. - -Vocabulary: -- Equality: `equals`, `not_equals` -- String: `starts_with`, `ends_with`, `contains`, `regex` -- Numeric: `gt`, `gte`, `lt`, `lte` -- Set: `in`, `not_in` -- Existence: `exists` -- Composition: `$and`, `$or`, `$not` - -Inspired by AWS EventBridge and MongoDB query syntax. The filter grammar -itself is published as a JSON Schema, so users get inline error messages. - ---- - -## 7. Runtime components - -Each component is distinct, replaceable, and has one job. - -### 7.1 Dispatcher - -What it does: matches firing triggers to automations, creates `AutomationRun` -rows, enqueues executor tasks. - -For schedule triggers: Celery Beat polls the trigger table, computes due -ones, fires. - -For webhook triggers: the FastAPI handler is the dispatcher entry point. -Validates token, runs input_mapping, creates run. - -For event triggers: subscribes to the `domain_events` table. For each new -event, evaluates all matching triggers' filters, fires the matches. - -Common path (after a trigger has fired): -1. Resolve `inputs` from trigger payload and defaults -2. Validate resolved inputs against the automation's input schema -3. **Idempotency check** — dedup against existing pending/running runs -4. **Snapshot the resolved definition** into the run row (immutable history) -5. Enqueue executor task on the single `automations_default` Celery queue - -The cost-estimate pre-check (originally step 3) is **deferred**. v1 -actions do not declare cost estimates, the run row has no `cost_usd` -column, and no handler reports tokens used — so neither pre-flight -prediction nor mid-flight accumulation can be enforced. `Execution` -therefore does not expose `budget_cap_usd` in v1; it returns as a single -field addition the day the cost ledger ships (per-action cost reporting -+ `automation_runs.cost_usd` column + executor accumulation). - -Queue routing by `expected_duration_seconds` is **deferred** until load -patterns justify a second queue. v1 uses a single queue. - -### 7.2 Executor - -What it is: **a Celery task wrapping a single function that walks a plan -step by step.** Not an agent, not a workflow engine, not a scheduler. A -loop with bookkeeping. Maybe 200 lines. - -```python -async def execute_run(run_id: int) -> None: - run = load_run(run_id); run.status = "running"; save(run) - context = build_run_context(run) - step_outputs = {} - - for step in run.plan: - if step.when and not evaluate_predicate(step.when, context | step_outputs): - record_step_skipped(run, step); continue - - resolved_params = render_params(step.params, context | step_outputs) - action = action_registry.get(step.action) - validate(resolved_params, action.params_schema) - - try: - result = await with_retries( - action.handler, - ctx=build_action_context(run, action), - args=resolved_params, - policy=step.retry_policy or run.execution.retry_policy, - ) - validate(result, step.output_schema) - if step.output_as: - step_outputs[step.output_as] = result - record_step_succeeded(run, step, result) - except Exception as e: - record_step_failed(run, step, e) - await run_on_failure(run, e) - return - - run.status = "succeeded"; save(run) - publish_event("automation.run.succeeded", run) # see §7.5 -``` - -Intelligence lives **inside handlers**, not in the executor. The most -intelligent handler is `agent_task`, which spins up a LangGraph Deep Agent -for one step and returns when the agent finishes. The executor sees a -validated dict come back; it doesn't know that step was "smart." - -### 7.3 Action handlers - -One handler per `ActionDefinition.type`. Receives the validated `args` -dict and returns whatever the step's output validates against (a fixed -shape declared by tight actions, or a dynamic shape declared via -`output_schema` in the step params for `agent_task`). - -Handlers do not know about retries or timeouts — those are the -executor's concern. - -In v1, handlers take `(args)` only. The `CallContext` parameter sketched -in §7.2's pseudo-code (caller user id, search space id, run id, -credential resolver) arrives with the unification layer redesign (§3); -v1's single action (`agent_task`) reads what it needs from app-level -configuration. - -### 7.4 Template engine - -#### Why it exists - -Most fields in an automation definition contain literal strings the user -authored once — but the actual rendered value has to change per run, because -it includes data from the trigger payload or from prior step outputs. The -template engine is what turns `"Daily digest for {{run.started_at}}"` into -`"Daily digest for 2026-05-26"` at run time. - -Three fields use it: -- `*_template` strings in tight action configs (Slack messages, email bodies, - Linear titles, etc.) -- `prompt` in `agent_task` configs (so the agent sees resolved values, not - `{{...}}` placeholders) -- `when:` step predicates (which need to evaluate to a boolean) - -#### Public interface - -Single module, ~80 lines. Three public functions — everything else in the -engine routes through these: - -```python -def render_template(template: str, context: dict) -> str: ... -def evaluate_predicate(expression: str, context: dict) -> bool: ... -def build_run_context(run, step_outputs) -> dict: ... -``` - -Backed by Jinja2's `SandboxedEnvironment`. The whole module is the seam: if -the template language is ever swapped, only this file changes. - -#### Security architecture: allowlist by default - -`SandboxedEnvironment` starts empty. A freshly-created instance gives a -template access to: -- Variables in the context dict we pass in (`run`, `inputs`, prior step - outputs) -- Public (non-underscore) attributes of those variables -- Jinja's built-in control flow (`{% if %}`, `{% for %}`, `{% set %}`) - -Nothing else. No Python builtins, no modules, no I/O, no network, no -filesystem. Everything beyond the above must be **explicitly registered.** -This is the structurally important property: anything we didn't add is -inaccessible. The risk surface equals the size of what we registered. - -The three sandbox rules that enforce this: -1. **Attribute access is filtered** — names starting with underscore are - rejected. This blocks the entire family of `{{x.__class__.__mro__...}}` - Python escape paths in one rule. -2. **Globals are allowlist-only** — `open`, `eval`, `exec`, `__import__`, - `getattr`, every module name, are all absent unless we register them. - We register zero globals. -3. **Unsafe callables are blocked** — `str.format` and `str.format_map` - specifically (due to CVE-2016-10745), plus anything marked - `unsafe_callable`. - -#### What we register, exactly - -- **Filters: a curated 15**, no more. `join`, `length`, `default`, `upper`, - `lower`, `truncate`, `tojson`, `date`, `replace`, `trim`, `slugify`, - `first`, `last`, `sort`, `reverse`. Each one is audited for what it does - with its input; none of them takes a callable, runs `eval`, or reaches - into Python objects beyond simple data transformation. -- **Globals: none.** -- **Tests: only the safe built-ins** (`defined`, `none`, `number`, `string`, - `mapping`, `sequence`, `boolean`). - -Adding a new filter requires a deliberate code change and review: does this -filter do anything dangerous with its input? If yes, don't add it. The list -only grows by audited additions. - -#### Runtime limits (defense in depth) - -The sandbox handles the attack surface inside the template language. Three -additional limits handle resource exhaustion that the language permits but -the runtime shouldn't tolerate: - -- **Template source length capped at 8 KB.** Checked before parsing. -- **Render time capped at 100 ms per render.** Implemented via a watchdog - thread; renders that exceed are killed and the step fails. Catches - `{% for i in range(10**9) %}` and nested loop bombs. -- **Output size capped at 1 MB.** A small template can produce a multi-GB - string via `{{ 'A' * 10**8 }}`-style multiplication; this catches it. - -Plus `StrictUndefined`: any reference to a missing variable raises -immediately rather than silently rendering empty, so misconfigurations -fail fast. - -#### Threat model and residual risk - -The trust model from day one is: - -- Templates are generated by an LLM from a user's natural-language input - (see §10), or written/edited by humans in the editable form -- A second LLM reviews the proposal and produces a plain-language summary - plus flagged anomalies for the user -- The user reviews and approves before the automation runs -- The Generator LLM's input is scoped (user prompt + schema + registry - only — no arbitrary document content), minimizing prompt-injection paths - -The sandbox + runtime limits + curated filter list protect against the -malformed-template attack. Human review protects against the -semantically-malicious-but-syntactically-valid attack. These are -complementary layers, not redundant. - -Known residual risks, each genuinely small: - -- **Future Jinja CVEs.** Historical sandbox bypasses have existed and - been patched. This is a generic third-party-dependency risk, comparable - to bugs in any other library we rely on. Mitigation: subscribe to - security advisories, ship updates within a week of disclosure. -- **Side channels via prompts to LLMs.** A template that renders into a - prompt can attempt prompt injection of the agent at run time. This is - not a sandbox concern but a separate concern in `agent_task`'s design. -- **Operator deployments with long-lived secrets in worker env vars.** - Mitigation: credentials fetched per-handler-per-call via - `ActionContext.resolve_credentials`, never pre-loaded into worker - env vars accessible to templates. - -The sandbox-with-allowlist architecture means **the attack surface -equals the set of things we registered.** With zero globals registered -and 15 audited filters, the surface is small, bounded, and reviewable. -This is the structural property that makes the architecture sound, and -it doesn't depend on hypothetical assumptions about who authors templates. - -#### Pre-Phase-5 gate - -One trust-model change is documented in the roadmap: **Phase 5 introduces -template sharing across SearchSpaces** (automation templates as -exportable, importable artifacts). At that point, the *approver* of a -template (the original author) is no longer the *runner* (the importer). -The "human reviews before save" mitigation breaks down because the -reviewer doesn't bear the risk. - -Before Phase 5 ships, this needs an explicit re-approval flow: importing -a template triggers a fresh review pass by the importing user, with the -flagged-anomalies output prominently displayed, and the import cannot -complete without explicit per-template approval. - -This is a UX/flow decision, not a template-language migration. Jinja -itself stays; what changes is the approval workflow at the import boundary. - -#### The `run.*` namespace exposed in every template - -``` -run.id, run.started_at, run.automation_id, run.automation_name, -run.automation_version, run.trigger_type, run.trigger_id, -run.search_space_id, run.creator_id, run.attempt, -run.failed_step_id, run.error.* (only in on_failure context) -``` - -#### Default value rendering - -Non-string template values render as JSON by default (via the `finalize` -hook): lists become `["a", "b"]`, dicts become `{"k": "v"}`, datetimes -become ISO 8601. The `| join`, `| length`, `| tojson` filters give explicit -control. Strings render as themselves with no quoting. `None` renders as -empty string in templates, as `null` in JSON contexts. - -### 7.5 Event bus - -`domain_events` table, polled by Celery Beat alongside the existing -scheduler. Both connector events and internal SurfSense events publish to -it. Both are consumed by the dispatcher's event-trigger subscriber. - -**Automations themselves publish events.** Successful and failed runs emit -`automation.run.succeeded` / `automation.run.failed` events with the run -metadata. This makes automations composable through events — chain them by -subscribing one automation's event trigger to another's run event. No new -mechanism; the trigger filter and event publishing already exist. - -Upgrade path documented: when throughput or latency demands it, replace -PostgreSQL polling with Redis Streams. The `events.publish()` and -`events.subscribe()` interfaces stay the same. Nothing else changes. - ---- - -## 8. Cross-cutting concerns - -### Concurrency policy - -Per-automation `concurrency` field controls what happens when a new fire -occurs while a previous run is still running: - -- `drop_if_running` — silently skip the new fire -- `queue` — execute serially, in arrival order -- `allow_parallel` — start a new run independently - -The dispatcher enforces this before enqueueing. - -### Retry policy - -Three fields, per-automation defaults with optional per-step overrides: -- `max_retries`: integer, 0–10 -- `retry_backoff`: `none` | `linear` | `exponential` -- `timeout_seconds`: integer - -Retries on: -- Action handler exceptions -- Output schema validation failures (for dynamic-output actions, the - validation error is fed back to the LLM in the retry) - -Not retries: -- `when:` evaluation failures (these are user errors, surface immediately) -- Input validation failures (caught at dispatch, never reach the executor) - -### Budget enforcement *(deferred — not in v1)* - -Future shape: `budget_cap_usd` on `Execution`, dispatcher refuses to -enqueue if estimated cost exceeds it, executor kills the run if -accumulated cost crosses it mid-flight (the LLM ops handler reports -tokens consumed back to the executor between calls). - -Prerequisites before this can land: -- Each action declares cost reporting (tokens × model price, API call - charges) — `ActionDefinition` has no such field today. -- `automation_runs.cost_usd` column + executor accumulates per step. -- A historical-cost ledger so pre-flight estimation can return useful - numbers (otherwise the dispatcher gate is guessing). - -Until all three exist, v1 has no surface for budget enforcement. - -### On-failure handlers - -`execution.on_failure` is a list of steps that run after the main plan has -failed and all retries are exhausted. Same step shape as the main plan. -Cannot have their own `on_failure`. See `run.error.*` in the run context. - -### Artifacts - -Actions that produce artifacts declare `produces_artifacts: list[ArtifactSpec]`: - -```python -@dataclass -class ArtifactSpec: - kind: str # "audio", "document", "image", "data" - retention: str # "transient" | "default" | "permanent" - visibility: str # "private" | "search_space" | "shared" -``` - -The engine handles storage (writes to SurfSense's existing object storage), -URL generation (signed, scoped to the run's permissions), and cleanup (a -nightly Celery Beat task deletes expired artifacts). - -### Duration classes and queue routing — deferred - -The original design routed runs to multiple Celery queues based on each -action's declared `expected_duration_seconds`. v1 ships with **one -queue** (`automations_default`) and actions do not declare a duration. -Multi-queue routing returns when burst load on a single queue actually -justifies the operational complexity of independent worker pools. - -Adding the second queue is a config change plus reintroducing -`expected_duration_seconds` on the `ActionDefinition` dataclass — both -mechanical, additive, and free of design rewrite. - ---- - -## 9. Data model - -**v1 ships three tables:** `automations`, `automation_triggers`, -`automation_runs`. All scoped by `search_space_id` for RBAC. - -The other three tables described in earlier drafts are deferred: - -- `domain_events` → **deferred to Phase 3** (introduced with the event - trigger). -- `mcp_connections`, `mcp_tools` → **deferred to Phase 4** (MCP - integration). - -The deferred tables ship as-is when their consuming feature lands; -nothing in the v1 schema needs to change to accommodate them. The three -v1 tables form the engine's persistent state — definitions, triggers, -and an immutable run history. - -### `automations` - -| field | type | notes | -| ----------------- | ----------------------------------- | -------------------------------------------------------------------------- | -| `id` | int PK | | -| `search_space_id` | FK → `search_spaces.id` | | -| `created_by` | FK → `users.id` | runs execute as this identity | -| `name` | str | | -| `description` | str | | -| `status` | enum | `active`, `paused`, `archived` | -| `definition` | jsonb | the editable structured spec | -| `version` | int | bumped on every edit | -| `created_at` / `updated_at` | timestamps | | - -### `automation_triggers` - -| field | type | notes | -| --------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------- | -| `id` | int PK | | -| `automation_id` | FK | | -| `type` | enum: `schedule`, `manual` (Phase 2/3 add `webhook`, `event`) | | -| `params` | jsonb | trigger-type config, validated against trigger's `params_schema` | -| `static_inputs` | jsonb | per-attachment domain values merged into every run (static wins on collision) | -| `enabled` | bool | | -| `last_fired_at` | timestamp | | -| `next_fire_at` | timestamp / null | precomputed next fire moment for schedule triggers | - -`secret_hash` (for webhook bearer tokens) is **deferred to Phase 2** with -the webhook trigger. - -### `automation_runs` - -| field | type | notes | -| ----------------- | ---------------------------------------------------------------------------- | -------------------------------------------------- | -| `id` | int PK | | -| `automation_id` | FK | | -| `trigger_id` | FK / null | null = manual via UI | -| `status` | enum | `pending`, `running`, `succeeded`, `failed`, `cancelled`, `timed_out` | -| `definition_snapshot` | jsonb | the definition as it was when this run fired | -| `inputs` | jsonb | merged & validated inputs (trigger.static_inputs ∪ producer runtime data, static wins) | -| `step_results` | jsonb | array of per-step results with timing | -| `output` | jsonb / null | | -| `artifacts` | jsonb | references to created artifacts | -| `error` | jsonb / null | | -| `started_at` / `finished_at` | timestamps | | -| `agent_session_id`| str / null | link to LangGraph trace if agent_task was used | - -`cost_usd` (per-run accumulated cost) is **deferred** until at least one -action records token-level cost. When reintroduced it lands as a -column-only migration. - -### Deferred tables - -- **`domain_events`** — the event bus backing event triggers. Ships in - Phase 3 with the event trigger. v1 only emits `automation.run.*` - events into application logs; the table is added when at least one - consumer needs to subscribe to them. -- **`mcp_connections`** / **`mcp_tools`** — see §3. Both ship in Phase 4 - alongside the MCP harvester and the two-tier registry. - -NL drafts are **not** a core table. They live in a generic short-TTL -store (Redis or a transient table) when the NL flow is built in -Phase 3. - ---- - -## 10. NL authoring flow - -**This is how the system is intended to be used from day one, not just a -Phase 3 addition.** The product surface is: user describes intent in natural -language, LLM produces a structured proposal, user reviews and edits in an -auto-generated form, then saves. Hand-authoring JSON directly is supported -but is not the primary path. - -This shapes the trust model. Templates are LLM-generated from day one, not -hand-written by power users. The mitigation is human-in-the-loop review, -not "trusted authors only." - -### Pass 1: Proposal generation - -User provides natural-language input. The Generator LLM is given: -- The full schema set (input schema for definition, registry of action - types with their params_schemas, registry of trigger types, list of - allowed Jinja filters) -- A tool to list available connectors, channels, and other SearchSpace - resources, so it doesn't invent names that don't exist -- A few-shot set of examples - -**Scoped input.** The Generator does *not* receive arbitrary SearchSpace -document content. Its context is the user's prompt plus the schema and -registry information. This minimizes the prompt-injection surface — there's -no document text in the context for an attacker to seed instructions into. - -If a user wants document-aware generation later ("create an automation -that processes documents like this one"), that's a deliberate feature -extension with its own prompt-injection mitigations, not the default flow. - -Output: a structured proposal matching the automation definition schema. - -### Pass 2: Deterministic validation - -Server-side, before the proposal reaches the user: -- Validate against JSON Schema (shape correctness) -- Verify every action and trigger type referenced exists in the registry -- Verify every connector/channel/resource referenced exists in this SearchSpace -- Validate every template against the sandbox's allowlist (no underscore - attributes, no unregistered filter names, length under cap) - -Failures here are deterministic errors, not warnings. A proposal that -references a non-existent action or includes a template using -`{{x.__class__}}` is rejected before the user sees it; the Generator is -re-prompted with the validation error and asked to fix the proposal. - -### Pass 2.5: Review pass - -A second LLM call — the **Review LLM** — examines the validated proposal and -produces two outputs for the user: - -1. **A plain-language summary** of what the automation will do, in business - terms. "This automation will run every weekday at 9am. It reads documents - in this SearchSpace tagged 'competitor' that were indexed since the last - run, asks an agent to summarize them as 5 bullets, and posts the summary - to your #engineering-standup Slack channel. Estimated cost: $0.40 per - run." - -2. **A "things worth checking" list** flagging anything unusual: - - Templates with unusual attribute paths or filter usage - - Prompts containing instructions that look more like commands than - descriptions ("ignore previous instructions" style) - - Action sequences that touch external systems without obvious benefit - to the user - - Cost estimates that seem high relative to the goal - - References to actions the user hasn't used before - - Schedules tighter than 15 minutes (likely should be event triggers) - -The Review LLM is a **UX layer** that makes review actually useful. It is -**not a security boundary.** The deterministic controls (sandbox, runtime -limits, schema validator) are the security boundaries. The Review LLM -helps users catch their own intent mismatches and surfaces anomalies for -attention, but the sandbox would block dangerous templates even if the -Review LLM missed them. - -This separation is important: two probabilistic controls compounding can -create a false sense of security. The Review LLM is explicitly framed in -the architecture as helper, not gatekeeper. - -### Pass 3: Editable review - -The user lands on a form pre-filled with the proposal. The page shows: -- The plain-language summary from the Review pass -- The flagged items, prominently displayed near the relevant fields -- The full editable form, auto-generated from the JSON Schemas -- Cost estimate and impact summary (which external systems get touched) - -**Every field is editable.** Clarifications appear as required fields. -Templates are shown in code-styled fields with syntax highlighting and the -filter palette visible. The user can edit any field; saving re-runs Pass 2 -(deterministic validation) before persisting. - -Hitting **Save** promotes the proposal to an `automation` row. - -### Editing existing automations - -NL editing of an existing automation is a patch operation: the Generator -LLM receives the current definition plus the NL instruction and produces a -modified proposal. The same Pass 2 (validation) and Pass 2.5 (review) run -against the modified version, and the user reviews the diff before saving. -Existing run history is unaffected — only future runs use the new version. - -### Why human-in-the-loop is non-negotiable - -The Generator LLM, the Review LLM, and the sandbox are three layers of -defense against malformed or malicious proposals. The human approval step -is the fourth and most important layer. It exists because: - -- LLMs can be prompt-injected; humans can spot text that asks them to - ignore instructions -- LLMs can produce confident-but-wrong proposals; humans can catch - semantic mismatches between intent and output -- The cost of a bad automation running unattended is high; the cost of a - user clicking "approve" after reading is low - -The architecture must never offer "auto-approve" or "skip review" options -for LLM-generated proposals. Save requires human action on the proposal, -always. - ---- - -## 11. Repository layout - -``` -surfsense_backend/app/ -├── automations/ # NEW: the engine -│ ├── __init__.py -│ ├── persistence/ # SQLAlchemy models + enums for 3 tables -│ ├── schemas/ # Pydantic schemas (definition envelope, etc.) -│ ├── routes.py # FastAPI router (/api/v1/automations) -│ ├── service.py # CRUD + business logic -│ ├── dispatcher.py # trigger matching, run creation -│ ├── executor.py # the Celery task that runs a plan -│ ├── templating.py # Jinja sandbox + filters -│ ├── events.py # publish/subscribe for domain_events -│ ├── filters.py # JSON filter grammar evaluator -│ ├── registries/ # action and trigger registries -│ │ ├── actions/ # ActionDefinition + handler registration -│ │ └── triggers/ # TriggerDefinition -│ └── nl/ # Phase 1 — primary user path -│ ├── generator.py # Generator LLM -│ ├── reviewer.py # Review LLM (summary + flagged items) -│ ├── validator.py # deterministic schema + resource checks -│ └── prompts.py # system prompts for both LLMs -│ -├── utils/ -│ └── periodic_scheduler.py # EXTENDED to scan automation_triggers -│ -└── alembic/versions/ - └── NN_add_automation_tables.py - -surfsense_web/app/(routes)/ -└── automations/ # NEW: UI - ├── page.tsx # list - ├── new/page.tsx # NL input + draft preview (Phase 1) - ├── [id]/page.tsx # editor (auto-generated forms) - └── [id]/runs/page.tsx # run history, streamed via Electric SQL -``` - ---- - -## 12. Phased delivery - -Each phase delivers something usable. Each de-risks the next. **NL authoring -is the primary user path from Phase 1** — what evolves across phases is -which actions and triggers are available, not whether users can describe -automations in natural language. - -### Phase 1 — Engine MVP with NL authoring - -**Step 1 (current scope, this batch of commits):** -- 3 tables (`automations`, `automation_triggers`, `automation_runs`) + - Alembic migration -- Empty action and trigger registries under - `app/automations/registries/` (concrete entries land in later steps) -- Pydantic schemas for the automation definition envelope, the two v1 - trigger params shapes (`schedule`, `manual`), and the one v1 action - params shape (`agent_task`) -- Module structure under `app/automations/` (persistence/, schemas/, - registries/), fully isolated from the existing codebase - -**Step 2:** -- The `agent_task` action handler and the `schedule` / `manual` triggers - registered in `app/automations/registries/`. Tool resolution for - `agent_task.params.tools` is opaque to the contract — the handler - decides what string identifiers it accepts and how they resolve. - -**Step 3:** -- Executor (single-queue Celery task) with retries and timeouts -- Template engine (Jinja sandbox + the v1 filter allowlist + runtime - limits) -- Manual "Run now" endpoint - -**Step 4:** -- NL authoring flow: Generator LLM, deterministic validator, Review LLM, - editable form -- Run history UI with Electric SQL streaming - -**After Phase 1**: a user can describe an automation in natural language, -review the proposal (with summary + flagged anomalies), edit any field, -save, and watch it run on a schedule. - -### Phase 2 — Webhooks and delivery -- `webhook` trigger with per-automation bearer tokens -- Tight actions: `slack_post`, `send_email`, `notification` -- `transform_data` action -- `on_failure` hooks -- Step-level retry/timeout overrides -- Concurrency policy enforcement - -**After Phase 2**: external systems can drive automations, results go -somewhere humans see, complex pipelines have proper error handling. - -### Phase 3 — NL authoring polish -- NL patch flow for editing existing automations (diff-based) -- Conversational refinement during proposal review ("change the schedule - to weekdays only," "add a Slack notification on failure") -- Improved Review LLM coverage (more anomaly patterns, cost-relative-to- - goal heuristics) -- Saved prompt templates and starter examples - -**After Phase 3**: NL authoring is the polished primary surface; edit -flows are conversational rather than form-only. - -### Phase 4 — Event triggers + integration tooling -- `domain_events` table and `events.py` module -- Indexing pipeline publishes `connector.*` events (smallest change — just - add publish calls to the existing flow) -- Automations publish `automation.run.*` events on completion -- `event` trigger with filter grammar -- The unification layer redesign (see §3) — `CallContext`, scope - declarations, per-user authorization gating -- MCP integration on top of the unification layer (external tool servers - harvested into the shared catalog) - -**After Phase 4**: "do X when Y happens" automations work, including -automation-chaining through events; external MCP tools and SurfSense -actions share one vocabulary. - -### Phase 5 — Wrapping existing features and sharing -- Wrap existing SurfSense features as actions: `podcast_generation`, - `report_generation`, `indexing_sweep` -- Artifact lifecycle implementation -- `expected_duration_seconds` based queue routing (split `automations_long` - from `automations_default`) -- **Automation templates** (shareable, exportable, importable) — with - the import re-approval flow that handles the approver-≠-runner trust - shift documented in §7.4's pre-Phase-5 gate -- Cross-automation composition examples in the docs - -**After Phase 5**: every existing SurfSense feature is automatable -without any per-feature code, and automations can be shared between -SearchSpaces and users. - ---- - -## 13. Decisions locked - -For reference — every decision made through the design process, in one -place. - -### Foundations -1. ✅ JSON Schema (draft 2020-12) is the single schema language for everything -2. ✅ Definition is the program; infrastructure is the interpreter -3. ✅ List of steps (not single action) in the plan, with `output_as` chaining -4. ⏸ Capability unification layer (one catalog shared by automations, agents, and future surfaces) — **deferred to post-v1** (see §3). v1 ships actions only. -5. ✅ Name-based resolution: definitions reference action and trigger types by string ID. The registry is the runtime's vocabulary; lookup is a dict access. No code references in definitions. -6. ✅ The expressive spectrum runs from pure direct calls to broad agent_task; the NL generator proposes the cheapest shape that meets intent (Shape 6 from §4 by default) - -### Trigger taxonomy -8. ✅ Three trigger types: `schedule`, `webhook`, `event` -9. ✅ Events absorb both connector events and internal SurfSense events -10. ✅ Filter grammar is JSON-structured operators (not Jinja) - -### Templating cluster -11. ✅ Jinja2 `SandboxedEnvironment` for templates and `when:` predicates — but with the explicit understanding that the sandbox is an allowlist-by-default architecture, not a denylist -12. ✅ Zero globals registered. Curated 15 filters only, each audited for safe behavior with hostile input. List grows only by reviewed addition -13. ✅ Four runtime mitigations: `StrictUndefined`, 8 KB template source cap, 100 ms render time cap (watchdog-enforced), 1 MB output size cap -14. ✅ Non-string template values render as JSON by default -15. ✅ Fixed `run.*` namespace, documented -16. ⏸ **Pre-Phase-5 gate**: template sharing across SearchSpaces breaks the approver-equals-runner trust model. Mitigation is a re-approval flow at the import boundary (UX-level), not a template-language migration. Jinja itself stays. - -### Execution -17. ✅ Executor is a Celery task wrapping a sequential loop — not an agent -18. ✅ `when:` is optional per step; false = skipped (not failed) -19. ✅ No DAGs, no parallelism, no loops — composition via agent_task or events -20. ✅ `on_failure` part of execution policy from v1 -21. ✅ Step-level retry and timeout overrides -22. ⏸ Budget cap enforced pre-enqueue and mid-flight — **deferred** until the cost ledger ships (see §8 Budget enforcement) - -### Components -23. ✅ Dispatcher / executor / handlers / registry — distinct, each replaceable -24. ⏸ Side effects are a set, including `USER_VISIBLE` — **deferred** until multi-user automation RBAC ships -25. ⏸ `expected_duration_seconds` integer drives queue routing — **deferred** until a second Celery queue is needed -26. ⏸ `produces_artifacts` is a list of `ArtifactSpec`, not a bool — **deferred** until artifacts beyond the deliverable handlers' own persistence are needed -27. ✅ Output schemas recommended on `agent_task`; editor warns when missing - -### Event bus -28. ✅ `domain_events` table for v1, with upgrade path to Redis Streams -29. ✅ Automations publish run events for composability -30. ✅ Publish/subscribe behind interface — no direct table access elsewhere - -### Capability unification — all deferred to post-v1 -31. ⏸ One shared catalog of "things this SurfSense instance can do" — **deferred**, see §3 -32. ⏸ Handler `CallContext` (caller user id, search space id, run id) — **deferred** with unification -33. ⏸ Per-capability scope declarations driving authorization — **deferred** with unification -34. ⏸ MCP integration on top of the unification layer (`mcp_connections`, `mcp_tools`, harvester) — **deferred to Phase 4** - -### Credentials — all deferred to Phase 2 -35. ⏸ Credentials never appear in the automation definition — only connection IDs do — **Phase 2** -36. ⏸ Credentials never appear in the LLM's context — the host holds them — **Phase 2** -37. ⏸ Credentials resolved per-call by the handler context, not pre-loaded into worker environment — **Phase 2** -38. ⏸ Tokens encrypted at rest; refresh handled automatically by the handler context — **Phase 2** - -### v1-minimum -39. ✅ v1 ships actions only — no separate capability layer. `ActionDefinition` is five fields: `type`, `name`, `description`, `params_schema`, `handler`. Additional fields are added only when a concrete consumer feature requires them. -40. ✅ Cost is **measured** from a per-run ledger, not declared. Pre-flight cost checks return when the ledger has enough history. -41. ✅ Single `automations_default` Celery queue in v1. Multi-queue routing returns when load justifies it. - -### NL authoring -42. ✅ LLM-authored templates is the primary path from day one — not a Phase 3 addition. Hand-authoring JSON is supported but secondary -43. ✅ Generator LLM produces JSON; deterministic schema + resource validation runs before user sees the proposal -44. ✅ Review LLM produces plain-language summary + flagged anomalies for the user — UX layer, not a security boundary -45. ✅ Generator LLM's input is scoped (user prompt + schema + registry only); arbitrary document content is not fed in -46. ✅ Human approval is required before save — no auto-approval option, ever -47. ✅ Every field editable in the proposal; unresolved questions surface as clarifications -48. ✅ NL drafts are transient storage, not a core table - -### Data model -49. ✅ v1 ships three tables (`automations`, `automation_triggers`, `automation_runs`). `domain_events` lands in Phase 3; `mcp_connections` and `mcp_tools` in Phase 4. -50. ✅ Run rows snapshot the definition (immutable history) -51. ✅ All entities scoped by `search_space_id` for RBAC -52. ✅ Editing an automation bumps `version`; existing runs unaffected - ---- - -## 14. Open questions deferred to implementation - -None of these block design; they're decisions a developer will make in -context, with the principle from §1 as their guide. - -- Exact retry backoff formulas (multipliers, jitter, ceilings) -- Webhook signature verification standards (HMAC scheme, header naming) -- Whether to support inline JSON Schema `$ref` to external schemas, or - inline everything -- Specific CDN/storage backend choices for artifacts (probably - whatever SurfSense already uses for podcasts) -- Rate limits per SearchSpace and per user -- Audit log retention policy - ---- - -## 15. Why this is ready to build - -This document satisfies five tests: - -1. **The four worked examples** (digest, CI webhook, file-added-trigger, - weekly podcast) all express cleanly in the contract without special - cases. Each one was used to find gaps before the gaps reached code. - -2. **The audit pass identified six refinements**, all incorporated. No - pending audit items. - -3. **Every decision points back to the principle from §1.** When a future - feature request lands, "does it belong in the definition or in the - engine?" gives a clear answer. - -4. **The build is staged** so Phase 1 ships in weeks, not months, and - each subsequent phase delivers user value while de-risking the next. - -5. **Existing SurfSense infrastructure is reused**, not paralleled. Celery - Beat, PostgreSQL/JSONB, Electric SQL, SQLAlchemy/Alembic, the existing - `tools/registry.py` pattern, the existing Search Space scoping — all - continue to do what they already do. The automation engine is a new - directory, not a new system. - -The next document a developer needs is the Pydantic models and JSON -Schemas spelled out concretely. Those follow mechanically from this plan. - ---- - -*Sources consulted: Claude Code Routines documentation; NousResearch/hermes- -agent (cron and skills subsystems); n8n documentation on node types and -workflow data model; the SurfSense repository and DeepWiki architecture -notes (FastAPI + Celery Beat + Electric SQL + LangGraph Deep Agents + -Search Space RBAC); Model Context Protocol specification for external -tool harvesting; AWS EventBridge for filter grammar; workflow-pattern -literature (van der Aalst et al.) for the trigger / action / concurrency -vocabulary.* From 2d8d42bd9cee790c8884a75cbc1ea5f9aa113126 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 15:40:18 +0200 Subject: [PATCH 143/198] refactor(web): polish automations detail view --- .../automation-detail-content.tsx | 24 ++++---- .../automation-definition-section.tsx | 2 +- .../components/automation-detail-loading.tsx | 60 ++++++++++++------- .../components/automation-runs-section.tsx | 2 +- .../automation-triggers-section.tsx | 2 +- .../components/plan-step-card.tsx | 2 +- .../components/run-details-panel.tsx | 2 +- .../[automation_id]/components/run-row.tsx | 2 +- .../components/runs-loading.tsx | 2 +- .../components/trigger-card.tsx | 2 +- .../new/components/automation-json-form.tsx | 2 +- .../layout/providers/LayoutDataProvider.tsx | 8 ++- 12 files changed, 65 insertions(+), 45 deletions(-) diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx index 49df3633e..4085d47a8 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/automation-detail-content.tsx @@ -72,16 +72,20 @@ export function AutomationDetailContent({ canDelete={perms.canDelete} /> - <AutomationDefinitionSection definition={automation.definition} /> - - <AutomationTriggersSection - triggers={automation.triggers} - automationId={automation.id} - canUpdate={perms.canUpdate} - canDelete={perms.canDelete} - /> - - <AutomationRunsSection automationId={automation.id} /> + <div className="grid grid-cols-1 gap-6 lg:grid-cols-3"> + <div className="space-y-6 min-w-0 lg:col-span-2"> + <AutomationDefinitionSection definition={automation.definition} /> + <AutomationRunsSection automationId={automation.id} /> + </div> + <div className="space-y-6 min-w-0"> + <AutomationTriggersSection + triggers={automation.triggers} + automationId={automation.id} + canUpdate={perms.canUpdate} + canDelete={perms.canDelete} + /> + </div> + </div> </> ); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx index 9545f363b..e8721d9b0 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx @@ -24,7 +24,7 @@ export function AutomationDefinitionSection({ definition }: AutomationDefinition const hasInputs = !!definition.inputs; return ( - <Card> + <Card className="border-border/60 bg-accent"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> <CardTitle className="text-base font-semibold">Definition</CardTitle> <span className="text-xs font-mono text-muted-foreground border border-border/60 rounded px-1.5 py-0.5"> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx index 1d01305ee..0d6ba3110 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-loading.tsx @@ -3,12 +3,13 @@ import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; /** - * Skeleton for the detail page. Same shell as the loaded view (header + - * two stacked cards) so the layout doesn't jump on data arrival. + * Skeleton for the detail page. Mirrors the loaded view's main/sidebar + * grid (Definition + Runs on the left, Triggers on the right) so layout + * doesn't reflow when data arrives. */ export function AutomationDetailLoading() { return ( - <div className="space-y-6"> + <> <div className="space-y-3"> <Skeleton className="h-4 w-32" /> <div className="flex items-center gap-3"> @@ -18,25 +19,38 @@ export function AutomationDetailLoading() { <Skeleton className="h-4 w-96" /> </div> - <Card> - <CardHeader> - <Skeleton className="h-5 w-32" /> - </CardHeader> - <CardContent className="space-y-4"> - <Skeleton className="h-4 w-3/4" /> - <Skeleton className="h-4 w-1/2" /> - <Skeleton className="h-24 w-full" /> - </CardContent> - </Card> - - <Card> - <CardHeader> - <Skeleton className="h-5 w-24" /> - </CardHeader> - <CardContent> - <Skeleton className="h-20 w-full" /> - </CardContent> - </Card> - </div> + <div className="grid grid-cols-1 gap-6 lg:grid-cols-3"> + <div className="space-y-6 min-w-0 lg:col-span-2"> + <Card className="border-border/60 bg-accent"> + <CardHeader> + <Skeleton className="h-5 w-32" /> + </CardHeader> + <CardContent className="space-y-4"> + <Skeleton className="h-4 w-3/4" /> + <Skeleton className="h-4 w-1/2" /> + <Skeleton className="h-24 w-full" /> + </CardContent> + </Card> + <Card className="border-border/60 bg-accent"> + <CardHeader> + <Skeleton className="h-5 w-32" /> + </CardHeader> + <CardContent> + <Skeleton className="h-20 w-full" /> + </CardContent> + </Card> + </div> + <div className="space-y-6 min-w-0"> + <Card className="border-border/60 bg-accent"> + <CardHeader> + <Skeleton className="h-5 w-24" /> + </CardHeader> + <CardContent> + <Skeleton className="h-20 w-full" /> + </CardContent> + </Card> + </div> + </div> + </> ); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx index b6158cab2..d31bd696d 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-runs-section.tsx @@ -23,7 +23,7 @@ export function AutomationRunsSection({ automationId }: AutomationRunsSectionPro const runs = data?.items ?? []; return ( - <Card> + <Card className="border-border/60 bg-accent"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> <div className="space-y-1"> <CardTitle className="text-base font-semibold inline-flex items-center gap-2"> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx index 33c8373a1..558a089ac 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-triggers-section.tsx @@ -23,7 +23,7 @@ export function AutomationTriggersSection({ canDelete, }: AutomationTriggersSectionProps) { return ( - <Card> + <Card className="border-border/60 bg-accent"> <CardHeader className="pb-4"> <CardTitle className="text-base font-semibold">Triggers</CardTitle> <p className="text-xs text-muted-foreground"> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx index 3feb77712..b9fda00db 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx @@ -15,7 +15,7 @@ interface PlanStepCardProps { */ export function PlanStepCard({ step, index }: PlanStepCardProps) { return ( - <div className="rounded-md border border-border/60 bg-background overflow-hidden"> + <div className="rounded-md border border-border/60 overflow-hidden"> <div className="flex items-center gap-2 px-4 py-2 border-b border-border/60 bg-muted/30"> <span className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground"> {index + 1} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx index d1d46900a..94a96b199 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx @@ -109,7 +109,7 @@ function Section({ function JsonBlock({ value }: { value: unknown }) { return ( - <pre className="rounded-md bg-background/60 px-3 py-2 text-[11px] font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words max-h-64"> + <pre className="rounded-md bg-muted/40 px-3 py-2 text-[11px] font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words max-h-64"> {JSON.stringify(value, null, 2)} </pre> ); diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx index b8d2bcc8b..02ca0569c 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-row.tsx @@ -24,7 +24,7 @@ export function RunRow({ run, automationId }: RunRowProps) { : formatRelativeDate(run.created_at); return ( - <div className="rounded-md border border-border/60 bg-background overflow-hidden"> + <div className="rounded-md border border-border/60 overflow-hidden"> <button type="button" onClick={() => setOpen((value) => !value)} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx index 5cab18f4c..61ce25e32 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/runs-loading.tsx @@ -9,7 +9,7 @@ export function RunsLoading() { {ROW_KEYS.map((key) => ( <div key={key} - className="flex items-center justify-between gap-4 rounded-md border border-border/60 bg-background/50 px-4 py-3" + className="flex items-center justify-between gap-4 rounded-md border border-border/60 px-4 py-3" > <div className="flex items-center gap-3"> <Skeleton className="h-5 w-20 rounded-md" /> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx index ec0246e49..200a15f57 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx @@ -48,7 +48,7 @@ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: Tri return ( <> - <div className="rounded-md border border-border/60 bg-background overflow-hidden"> + <div className="rounded-md border border-border/60 overflow-hidden"> <div className="flex items-center justify-between gap-4 px-4 py-3 border-b border-border/60"> <div className="flex items-center gap-3 min-w-0"> <CalendarClock className="h-4 w-4 text-muted-foreground shrink-0" aria-hidden /> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx index 845d95166..8fe065295 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx @@ -75,7 +75,7 @@ export function AutomationJsonForm({ searchSpaceId }: AutomationJsonFormProps) { const hasIssues = issues.length > 0; return ( - <Card> + <Card className="border-border/60 bg-accent"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> <CardTitle className="text-base font-semibold inline-flex items-center gap-2"> <FileJson className="h-4 w-4 text-muted-foreground" aria-hidden /> diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index 67971e435..663e4b96f 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -726,9 +726,11 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid : undefined } workspacePanelContentClassName={ - isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage || isAutomationsPage - ? "max-w-5xl" - : undefined + isAutomationsPage + ? "max-w-none" + : isUserSettingsPage || isSearchSpaceSettingsPage || isTeamPage + ? "max-w-5xl" + : undefined } isLoadingChats={isLoadingThreads} activeSlideoutPanel={activeSlideoutPanel} From fa0cdb9760f0b08bfb5db4596c79ffbefdbc845b Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 16:07:54 +0200 Subject: [PATCH 144/198] feat(web): unified json viewer/editor + edit existing automation --- .../automation-definition-section.tsx | 5 +- .../components/automation-detail-header.tsx | 10 +- .../components/inputs-schema-preview.tsx | 15 +- .../components/plan-step-card.tsx | 7 +- .../components/run-details-panel.tsx | 7 +- .../components/trigger-card.tsx | 7 +- .../edit/automation-edit-content.tsx | 56 ++++++ .../edit/components/automation-edit-form.tsx | 121 +++++++++++++ .../automations/[automation_id]/edit/page.tsx | 18 ++ .../new/components/automation-json-form.tsx | 62 +++---- .../components/json-metadata-viewer.tsx | 11 +- surfsense_web/components/json-view.tsx | 93 ++++++++++ .../tool-ui/automation/create-automation.tsx | 50 ++---- surfsense_web/package.json | 2 +- surfsense_web/pnpm-lock.yaml | 159 ++++++++++++++++-- 15 files changed, 504 insertions(+), 119 deletions(-) create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/page.tsx create mode 100644 surfsense_web/components/json-view.tsx diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx index e8721d9b0..4ff9b8b8c 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-definition-section.tsx @@ -11,9 +11,8 @@ interface AutomationDefinitionSectionProps { } /** - * The Definition card. Read-only in v1 — editing definitions happens via - * chat (re-run create_automation with a refined intent) or, later, via - * the raw-JSON path. Layout is top-down: + * The Definition card. Read view; editing happens on the sibling /edit + * route (Edit button in the header). Layout is top-down: * goal → tags → execution defaults → inputs schema (if any) → plan * * The schema_version is rendered as a small badge next to the section diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx index 4cf3efcc1..0bce3fa2d 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/automation-detail-header.tsx @@ -1,6 +1,6 @@ "use client"; import { useAtomValue } from "jotai"; -import { ArrowLeft, Pause, Play, Trash2 } from "lucide-react"; +import { ArrowLeft, Pause, Pencil, Play, Trash2 } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useCallback, useState } from "react"; @@ -82,6 +82,14 @@ export function AutomationDetailHeader({ </div> <div className="flex items-center gap-2 shrink-0"> + {canUpdate && ( + <Button asChild type="button" variant="outline" size="sm"> + <Link href={`/dashboard/${searchSpaceId}/automations/${automation.id}/edit`}> + <Pencil className="mr-2 h-4 w-4" /> + Edit + </Link> + </Button> + )} {canToggle && ( <Button type="button" diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx index bf2db8986..29d79d99b 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/inputs-schema-preview.tsx @@ -1,4 +1,5 @@ "use client"; +import { JsonView } from "@/components/json-view"; import type { Inputs } from "@/contracts/types/automation.types"; interface InputsSchemaPreviewProps { @@ -6,15 +7,15 @@ interface InputsSchemaPreviewProps { } /** - * Read-only JSON preview of an automation's accepted-inputs schema. - * Most automations don't define inputs (defaults are baked into the - * trigger's static_inputs), so the parent skips rendering this card - * when ``inputs`` is null. + * Read-only preview of an automation's accepted-inputs schema. Most + * automations don't define inputs (defaults are baked into the trigger's + * static_inputs), so the parent skips rendering this card when ``inputs`` + * is null. */ export function InputsSchemaPreview({ inputs }: InputsSchemaPreviewProps) { return ( - <pre className="rounded-md bg-muted/40 px-3 py-2 text-xs font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words max-h-72"> - {JSON.stringify(inputs.schema, null, 2)} - </pre> + <div className="rounded-md bg-muted/40 px-3 py-2 max-h-72 overflow-auto"> + <JsonView src={inputs.schema} collapsed={2} /> + </div> ); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx index b9fda00db..27cecf3bf 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/plan-step-card.tsx @@ -1,5 +1,6 @@ "use client"; import { ArrowRightCircle, GitCommitHorizontal } from "lucide-react"; +import { JsonView } from "@/components/json-view"; import type { PlanStep } from "@/contracts/types/automation.types"; interface PlanStepCardProps { @@ -54,9 +55,9 @@ export function PlanStepCard({ step, index }: PlanStepCardProps) { <GitCommitHorizontal className="h-3.5 w-3.5" aria-hidden /> Params </div> - <pre className="rounded-md bg-muted/40 px-3 py-2 text-xs font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words"> - {JSON.stringify(step.params, null, 2)} - </pre> + <div className="rounded-md bg-muted/40 px-3 py-2 overflow-auto"> + <JsonView src={step.params} collapsed={1} /> + </div> </div> </div> </div> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx index 94a96b199..f9c6fbb5a 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/run-details-panel.tsx @@ -1,5 +1,6 @@ "use client"; import { AlertCircle, FileOutput, GitCommitHorizontal, Package, Settings2 } from "lucide-react"; +import { JsonView } from "@/components/json-view"; import { Skeleton } from "@/components/ui/skeleton"; import { useAutomationRun } from "@/hooks/use-automation-runs"; @@ -109,8 +110,8 @@ function Section({ function JsonBlock({ value }: { value: unknown }) { return ( - <pre className="rounded-md bg-muted/40 px-3 py-2 text-[11px] font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words max-h-64"> - {JSON.stringify(value, null, 2)} - </pre> + <div className="rounded-md bg-muted/40 px-3 py-2 max-h-64 overflow-auto"> + <JsonView src={value} collapsed={1} /> + </div> ); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx index 200a15f57..a1d84d2d7 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx @@ -3,6 +3,7 @@ import { useAtomValue } from "jotai"; import { CalendarClock, Clock, Trash2 } from "lucide-react"; import { useState } from "react"; import { updateTriggerMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { JsonView } from "@/components/json-view"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import type { Trigger } from "@/contracts/types/automation.types"; @@ -109,9 +110,9 @@ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: Tri {hasStaticInputs && ( <div> <div className="text-muted-foreground mb-1">Static inputs</div> - <pre className="rounded-md bg-muted/40 px-3 py-2 font-mono text-foreground overflow-x-auto whitespace-pre-wrap break-words"> - {JSON.stringify(trigger.static_inputs, null, 2)} - </pre> + <div className="rounded-md bg-muted/40 px-3 py-2 overflow-auto"> + <JsonView src={trigger.static_inputs} collapsed={1} /> + </div> </div> )} </div> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx new file mode 100644 index 000000000..219552a1a --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx @@ -0,0 +1,56 @@ +"use client"; +import { ShieldAlert } from "lucide-react"; +import { useAutomation } from "@/hooks/use-automation"; +import { useAutomationPermissions } from "../../hooks/use-automation-permissions"; +import { AutomationDetailLoading } from "../components/automation-detail-loading"; +import { AutomationNotFound } from "../components/automation-not-found"; +import { AutomationEditForm } from "./components/automation-edit-form"; + +interface AutomationEditContentProps { + searchSpaceId: number; + automationId: number; +} + +/** + * Client orchestrator for the edit route. Mirrors detail-content's branch + * structure but gates on ``canUpdate`` instead of ``canRead``: a user who + * can read but not update is bounced to the access-denied panel. + */ +export function AutomationEditContent({ + searchSpaceId, + automationId, +}: AutomationEditContentProps) { + const perms = useAutomationPermissions(); + const validId = Number.isInteger(automationId) && automationId > 0; + const { data: automation, isLoading, error } = useAutomation(validId ? automationId : undefined); + + if (perms.loading) { + return <AutomationDetailLoading />; + } + + if (!perms.canUpdate) { + return ( + <div className="rounded-lg border border-border/60 bg-muted/20 px-6 py-12 text-center"> + <ShieldAlert className="mx-auto h-10 w-10 text-muted-foreground" aria-hidden /> + <h2 className="mt-3 text-base font-semibold text-foreground">Access denied</h2> + <p className="mt-1 text-sm text-muted-foreground max-w-md mx-auto"> + You don't have permission to edit automations in this search space. + </p> + </div> + ); + } + + if (!validId) { + return <AutomationNotFound searchSpaceId={searchSpaceId} />; + } + + if (isLoading) { + return <AutomationDetailLoading />; + } + + if (error || !automation) { + return <AutomationNotFound searchSpaceId={searchSpaceId} error={error} />; + } + + return <AutomationEditForm automation={automation} searchSpaceId={searchSpaceId} />; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx new file mode 100644 index 000000000..86b355838 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx @@ -0,0 +1,121 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { AlertCircle, ArrowLeft, Save } from "lucide-react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { updateAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { JsonView } from "@/components/json-view"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Spinner } from "@/components/ui/spinner"; +import { + type Automation, + automationUpdateRequest, +} from "@/contracts/types/automation.types"; + +interface AutomationEditFormProps { + automation: Automation; + searchSpaceId: number; +} + +/** + * Edit-existing-automation form. Surfaces the four mutable fields + * (name, description, status, definition) as one editable JSON tree; + * triggers stay on the detail page where they have their own management + * UI. Validates with the same Zod schema the API expects, then PATCHes + * the changed shape back. + */ +export function AutomationEditForm({ automation, searchSpaceId }: AutomationEditFormProps) { + const router = useRouter(); + const { mutateAsync: updateAutomation, isPending } = useAtomValue(updateAutomationMutationAtom); + const detailHref = `/dashboard/${searchSpaceId}/automations/${automation.id}`; + + const [value, setValue] = useState(() => ({ + name: automation.name, + description: automation.description ?? null, + status: automation.status, + definition: automation.definition, + })); + const [issues, setIssues] = useState<string[]>([]); + + async function handleSave() { + setIssues([]); + const result = automationUpdateRequest.safeParse(value); + if (!result.success) { + setIssues( + result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`) + ); + return; + } + try { + await updateAutomation({ automationId: automation.id, patch: result.data }); + router.push(detailHref); + } catch (err) { + setIssues([(err as Error).message ?? "Update failed"]); + } + } + + return ( + <> + <div className="space-y-3"> + <Button asChild variant="ghost" size="sm" className="-ml-2 h-auto px-2 py-1"> + <Link href={detailHref} className="text-xs text-muted-foreground"> + <ArrowLeft className="mr-1.5 h-3.5 w-3.5" /> + Back to automation + </Link> + </Button> + <div> + <h1 className="text-xl md:text-2xl font-semibold text-foreground break-words"> + Edit automation + </h1> + <p className="text-sm text-muted-foreground mt-1">{automation.name}</p> + </div> + </div> + + <Card className="border-border/60 bg-accent"> + <CardHeader className="pb-4"> + <CardTitle className="text-base font-semibold">Definition</CardTitle> + </CardHeader> + <CardContent className="space-y-4"> + <div className="rounded-md border border-input bg-background px-3 py-2 max-h-[36rem] overflow-auto"> + <JsonView + src={value} + editable + onChange={(next) => setValue(next as typeof value)} + collapsed={false} + /> + </div> + + {issues.length > 0 && ( + <div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2"> + <div className="flex items-center gap-1.5 text-xs font-medium text-destructive mb-1.5"> + <AlertCircle className="h-3.5 w-3.5" aria-hidden /> + {issues.length === 1 ? "1 issue" : `${issues.length} issues`} + </div> + <ul className="space-y-0.5 text-xs text-destructive list-disc list-inside"> + {issues.map((issue) => ( + <li key={issue}>{issue}</li> + ))} + </ul> + </div> + )} + + <div className="flex items-center justify-end gap-2"> + <Button asChild type="button" variant="ghost" size="sm"> + <Link href={detailHref}>Cancel</Link> + </Button> + <Button type="button" onClick={handleSave} disabled={isPending} size="sm"> + {isPending ? ( + <Spinner size="xs" className="mr-2" /> + ) : ( + <Save className="mr-2 h-4 w-4" /> + )} + Save changes + </Button> + </div> + </CardContent> + </Card> + </> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/page.tsx new file mode 100644 index 000000000..8477b9e12 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/page.tsx @@ -0,0 +1,18 @@ +import { AutomationEditContent } from "./automation-edit-content"; + +export default async function AutomationEditPage({ + params, +}: { + params: Promise<{ search_space_id: string; automation_id: string }>; +}) { + const { search_space_id, automation_id } = await params; + + return ( + <div className="w-full space-y-6"> + <AutomationEditContent + searchSpaceId={Number(search_space_id)} + automationId={Number(automation_id)} + /> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx index 8fe065295..94b608b8f 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx @@ -1,9 +1,10 @@ "use client"; import { useAtomValue } from "jotai"; -import { AlertCircle, Code, FileJson, Save } from "lucide-react"; +import { AlertCircle, FileJson, Save } from "lucide-react"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { createAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; +import { JsonView } from "@/components/json-view"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Spinner } from "@/components/ui/spinner"; @@ -17,45 +18,24 @@ interface AutomationJsonFormProps { /** * Raw-JSON create form. Lets power users skip the chat drafter when they * already know the shape they want. Flow: - * parse JSON → inject search_space_id → Zod validate → POST → navigate + * edit tree → inject search_space_id → Zod validate → POST → navigate * - * ``search_space_id`` is injected here rather than required in the pasted - * payload — the user shouldn't have to know their numeric id, and it - * keeps the template copy-paste-friendly across search spaces. + * ``search_space_id`` is injected here rather than required in the edited + * tree — the user shouldn't have to know their numeric id, and it keeps + * the template copy-paste-friendly across search spaces. */ export function AutomationJsonForm({ searchSpaceId }: AutomationJsonFormProps) { const router = useRouter(); const { mutateAsync: createAutomation, isPending } = useAtomValue(createAutomationMutationAtom); - const [text, setText] = useState(() => JSON.stringify(DEFAULT_AUTOMATION_TEMPLATE, null, 2)); + const [value, setValue] = useState<Record<string, unknown>>( + () => DEFAULT_AUTOMATION_TEMPLATE as Record<string, unknown> + ); const [issues, setIssues] = useState<string[]>([]); - function handleFormat() { - try { - const parsed = JSON.parse(text); - setText(JSON.stringify(parsed, null, 2)); - setIssues([]); - } catch (err) { - setIssues([`Cannot format — not valid JSON: ${(err as Error).message}`]); - } - } - async function handleSubmit() { setIssues([]); - let parsed: unknown; - try { - parsed = JSON.parse(text); - } catch (err) { - setIssues([`Invalid JSON: ${(err as Error).message}`]); - return; - } - - if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { - setIssues(["Root must be a JSON object."]); - return; - } - - const payload = { ...(parsed as Record<string, unknown>), search_space_id: searchSpaceId }; + const payload = { ...value, search_space_id: searchSpaceId }; const result = automationCreateRequest.safeParse(payload); if (!result.success) { setIssues( @@ -76,25 +56,21 @@ export function AutomationJsonForm({ searchSpaceId }: AutomationJsonFormProps) { return ( <Card className="border-border/60 bg-accent"> - <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4"> + <CardHeader className="pb-4"> <CardTitle className="text-base font-semibold inline-flex items-center gap-2"> <FileJson className="h-4 w-4 text-muted-foreground" aria-hidden /> Definition + triggers </CardTitle> - <Button type="button" variant="outline" size="sm" onClick={handleFormat}> - <Code className="mr-2 h-3.5 w-3.5" /> - Format - </Button> </CardHeader> <CardContent className="space-y-4"> - <textarea - value={text} - onChange={(e) => setText(e.target.value)} - spellCheck={false} - rows={24} - className="w-full rounded-md border border-input bg-background px-3 py-2 text-xs font-mono text-foreground shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring resize-y min-h-[16rem]" - aria-label="Automation JSON" - /> + <div className="rounded-md border border-input bg-background px-3 py-2 max-h-[32rem] overflow-auto"> + <JsonView + src={value} + editable + onChange={(next) => setValue(next as Record<string, unknown>)} + collapsed={false} + /> + </div> {hasIssues && ( <div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2"> diff --git a/surfsense_web/components/json-metadata-viewer.tsx b/surfsense_web/components/json-metadata-viewer.tsx index cc87a75c5..8ebbbc84e 100644 --- a/surfsense_web/components/json-metadata-viewer.tsx +++ b/surfsense_web/components/json-metadata-viewer.tsx @@ -1,6 +1,6 @@ import { FileJson } from "lucide-react"; import React from "react"; -import { defaultStyles, JsonView } from "react-json-view-lite"; +import { JsonView } from "@/components/json-view"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -10,7 +10,6 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { Spinner } from "@/components/ui/spinner"; -import "react-json-view-lite/dist/index.css"; interface JsonMetadataViewerProps { title: string; @@ -56,13 +55,13 @@ export function JsonMetadataViewer({ {title} - Metadata </DialogTitle> </DialogHeader> - <div className="mt-2 sm:mt-4 p-2 sm:p-4 bg-muted/30 rounded-md text-xs sm:text-sm"> + <div className="mt-2 sm:mt-4 p-2 sm:p-4 bg-muted/30 rounded-md text-xs sm:text-sm overflow-auto"> {loading ? ( <div className="flex items-center justify-center py-12"> <Spinner size="lg" className="text-muted-foreground" /> </div> ) : ( - <JsonView data={jsonData} style={defaultStyles} /> + <JsonView src={jsonData} collapsed={2} /> )} </div> </DialogContent> @@ -87,8 +86,8 @@ export function JsonMetadataViewer({ {title} - Metadata </DialogTitle> </DialogHeader> - <div className="mt-2 sm:mt-4 p-2 sm:p-4 bg-muted/30 rounded-md text-xs sm:text-sm"> - <JsonView data={jsonData} style={defaultStyles} /> + <div className="mt-2 sm:mt-4 p-2 sm:p-4 bg-muted/30 rounded-md text-xs sm:text-sm overflow-auto"> + <JsonView src={jsonData} collapsed={2} /> </div> </DialogContent> </Dialog> diff --git a/surfsense_web/components/json-view.tsx b/surfsense_web/components/json-view.tsx new file mode 100644 index 000000000..c293828b3 --- /dev/null +++ b/surfsense_web/components/json-view.tsx @@ -0,0 +1,93 @@ +"use client"; + +import ReactJson, { type InteractionProps } from "@microlink/react-json-view"; +import { useTheme } from "next-themes"; +import { useCallback, useMemo } from "react"; + +/** + * Shared JSON viewer/editor wrapper around @microlink/react-json-view. + * + * One component, dual mode: passing ``editable`` + ``onChange`` enables + * inline value editing, key renaming, add and delete. Omitting them + * yields a read-only viewer. The underlying library is uncontrolled — it + * mutates its own internal copy of ``src`` and surfaces the final tree on + * each interaction via ``updated_src``, which we forward to ``onChange``. + * + * Theme follows ``next-themes``: a dark base-16 palette in dark mode, the + * library's neutral default in light mode. Defaults are tuned for our + * compact UI surfaces (no data-type labels, no key quotes, triangle icons, + * tight indent). + */ +export interface JsonViewProps { + /** The JSON value to display. Primitives are wrapped under ``{ value }`` + * because the underlying library requires an object root. */ + src: unknown; + /** Enables value/key editing + add + delete. Requires ``onChange`` to + * observe the result; without it the toggle is silently a no-op. */ + editable?: boolean; + /** Called with the full updated tree on every accepted interaction. */ + onChange?: (next: unknown) => void; + /** Collapse depth. ``true`` collapses everything past the root; a number + * collapses from that depth onward. */ + collapsed?: boolean | number; + /** Root label. Default ``false`` (no label — saves vertical space). */ + name?: string | false; + className?: string; +} + +const DARK_THEME = "monokai" as const; +const LIGHT_THEME = "rjv-default" as const; + +const SHARED_DEFAULTS = { + iconStyle: "triangle" as const, + indentWidth: 2, + enableClipboard: true, + displayDataTypes: false, + displayObjectSize: true, + quotesOnKeys: false, + collapseStringsAfterLength: 80, +}; + +export function JsonView({ + src, + editable = false, + onChange, + collapsed = 2, + name = false, + className, +}: JsonViewProps) { + const { resolvedTheme } = useTheme(); + const theme = resolvedTheme === "dark" ? DARK_THEME : LIGHT_THEME; + + // The library throws on non-object roots. Wrap primitives and null/undefined. + const safeSrc = useMemo(() => { + if (src && typeof src === "object") return src as object; + return { value: src }; + }, [src]); + + const handleChange = useCallback( + (interaction: InteractionProps) => { + onChange?.(interaction.updated_src); + return true; + }, + [onChange] + ); + + const interactive = editable && onChange ? handleChange : (false as const); + + return ( + <div className={className}> + <ReactJson + src={safeSrc} + name={name} + theme={theme} + collapsed={collapsed} + onEdit={interactive} + onAdd={interactive} + onDelete={interactive} + style={{ backgroundColor: "transparent", fontSize: 12, fontFamily: "var(--font-mono)" }} + {...SHARED_DEFAULTS} + /> + </div> + ); +} diff --git a/surfsense_web/components/tool-ui/automation/create-automation.tsx b/surfsense_web/components/tool-ui/automation/create-automation.tsx index 00b120d38..b152f9055 100644 --- a/surfsense_web/components/tool-ui/automation/create-automation.tsx +++ b/surfsense_web/components/tool-ui/automation/create-automation.tsx @@ -2,17 +2,11 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react"; import { useAtomValue } from "jotai"; -import { - AlertCircle, - Code, - CornerDownLeftIcon, - ExternalLink, - Pencil, - Workflow, -} from "lucide-react"; +import { AlertCircle, CornerDownLeftIcon, ExternalLink, Pencil, Workflow } from "lucide-react"; import Link from "next/link"; import { useCallback, useEffect, useMemo, useState } from "react"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; +import { JsonView } from "@/components/json-view"; import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { Button } from "@/components/ui/button"; import { automationCreateRequest } from "@/contracts/types/automation.types"; @@ -231,28 +225,12 @@ interface JsonEditorProps { } function JsonEditor({ initialValue, onSave, onCancel }: JsonEditorProps) { - const [text, setText] = useState(() => JSON.stringify(initialValue, null, 2)); + const [value, setValue] = useState<Record<string, unknown>>(initialValue); const [issues, setIssues] = useState<string[]>([]); - function handleFormat() { - try { - setText(JSON.stringify(JSON.parse(text), null, 2)); - setIssues([]); - } catch (err) { - setIssues([`Cannot format — not valid JSON: ${(err as Error).message}`]); - } - } - function handleSave() { setIssues([]); - let parsed: unknown; - try { - parsed = JSON.parse(text); - } catch (err) { - setIssues([`Invalid JSON: ${(err as Error).message}`]); - return; - } - const result = editArgsSchema.safeParse(parsed); + const result = editArgsSchema.safeParse(value); if (!result.success) { setIssues( result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`) @@ -264,14 +242,14 @@ function JsonEditor({ initialValue, onSave, onCancel }: JsonEditorProps) { return ( <div className="space-y-3"> - <textarea - value={text} - onChange={(e) => setText(e.target.value)} - spellCheck={false} - rows={16} - className="w-full rounded-md border border-input bg-background px-3 py-2 text-xs font-mono text-foreground shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring resize-y min-h-[12rem]" - aria-label="Automation JSON" - /> + <div className="rounded-md border border-input bg-background px-3 py-2 max-h-[24rem] overflow-auto"> + <JsonView + src={value} + editable + onChange={(next) => setValue(next as Record<string, unknown>)} + collapsed={false} + /> + </div> {issues.length > 0 && ( <div className="rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2"> <div className="flex items-center gap-1.5 text-xs font-medium text-destructive"> @@ -291,10 +269,6 @@ function JsonEditor({ initialValue, onSave, onCancel }: JsonEditorProps) { <Button type="button" variant="ghost" size="sm" onClick={onCancel}> Cancel </Button> - <Button type="button" variant="outline" size="sm" onClick={handleFormat}> - <Code className="mr-1.5 h-3.5 w-3.5" /> - Format - </Button> <Button type="button" size="sm" onClick={handleSave}> Save edits </Button> diff --git a/surfsense_web/package.json b/surfsense_web/package.json index 213adbaad..6ac32160b 100644 --- a/surfsense_web/package.json +++ b/surfsense_web/package.json @@ -36,6 +36,7 @@ "@babel/standalone": "^7.29.2", "@hookform/resolvers": "^5.2.2", "@marsidev/react-turnstile": "^1.5.0", + "@microlink/react-json-view": "^1.31.20", "@monaco-editor/react": "^4.7.0", "@number-flow/react": "^0.5.10", "@platejs/autoformat": "^52.0.11", @@ -134,7 +135,6 @@ "react-dom": "^19.2.3", "react-dropzone": "^14.3.8", "react-hook-form": "^7.61.1", - "react-json-view-lite": "^2.4.1", "react-syntax-highlighter": "^15.6.1", "react-wrap-balancer": "^1.1.1", "rehype-katex": "^7.0.1", diff --git a/surfsense_web/pnpm-lock.yaml b/surfsense_web/pnpm-lock.yaml index 8602feb8d..7cbff6923 100644 --- a/surfsense_web/pnpm-lock.yaml +++ b/surfsense_web/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: '@marsidev/react-turnstile': specifier: ^1.5.0 version: 1.5.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@microlink/react-json-view': + specifier: ^1.31.20 + version: 1.31.20(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@monaco-editor/react': specifier: ^4.7.0 version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -323,9 +326,6 @@ importers: react-hook-form: specifier: ^7.61.1 version: 7.71.2(react@19.2.4) - react-json-view-lite: - specifier: ^2.4.1 - version: 2.5.0(react@19.2.4) react-syntax-highlighter: specifier: ^15.6.1 version: 15.6.6(react@19.2.4) @@ -1143,24 +1143,28 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [musl] '@biomejs/cli-linux-arm64@2.4.6': resolution: {integrity: sha512-kMLaI7OF5GN1Q8Doymjro1P8rVEoy7BKQALNz6fiR8IC1WKduoNyteBtJlHT7ASIL0Cx2jR6VUOBIbcB1B8pew==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-x64-musl@2.4.6': resolution: {integrity: sha512-C9s98IPDu7DYarjlZNuzJKTjVHN03RUnmHV5htvqsx6vEUXCDSJ59DNwjKVD5XYoSS4N+BYhq3RTBAL8X6svEg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [musl] '@biomejs/cli-linux-x64@2.4.6': resolution: {integrity: sha512-oHXmUFEoH8Lql1xfc3QkFLiC1hGR7qedv5eKNlC185or+o4/4HiaU7vYODAH3peRCfsuLr1g6v2fK9dFFOYdyw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [glibc] '@biomejs/cli-win32-arm64@2.4.6': resolution: {integrity: sha512-xzThn87Pf3YrOGTEODFGONmqXpTwUNxovQb72iaUOdcw8sBSY3+3WD8Hm9IhMYLnPi0n32s3L3NWU6+eSjfqFg==} @@ -1836,89 +1840,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -1989,6 +2009,13 @@ packages: peerDependencies: mediabunny: ^1.0.0 + '@microlink/react-json-view@1.31.20': + resolution: {integrity: sha512-gNLkGvjFDeAqVGvK3H7lfoDqetn/9lW2ugiYiJhchc7jQU1ZaKsZnt97ANluXWFfd/wifoA9TrVOTsUXwXCJwA==} + engines: {node: '>=17'} + peerDependencies: + react: '>= 15' + react-dom: '>= 15' + '@monaco-editor/loader@1.7.0': resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} @@ -2028,30 +2055,35 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@napi-rs/canvas-linux-arm64-musl@0.1.97': resolution: {integrity: sha512-kKmSkQVnWeqg7qdsiXvYxKhAFuHz3tkBjW/zyQv5YKUPhotpaVhpBGv5LqCngzyuRV85SXoe+OFj+Tv0a0QXkQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@napi-rs/canvas-linux-riscv64-gnu@0.1.97': resolution: {integrity: sha512-Jc7I3A51jnEOIAXeLsN/M/+Z28LUeakcsXs07FLq9prXc0eYOtVwsDEv913Gr+06IRo34gJJVgT0TXvmz+N2VA==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@napi-rs/canvas-linux-x64-gnu@0.1.97': resolution: {integrity: sha512-iDUBe7AilfuBSRbSa8/IGX38Mf+iCSBqoVKLSQ5XaY2JLOaqz1TVyPFEyIck7wT6mRQhQt5sN6ogfjIDfi74tg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@napi-rs/canvas-linux-x64-musl@0.1.97': resolution: {integrity: sha512-AKLFd/v0Z5fvgqBDqhvqtAdx+fHMJ5t9JcUNKq4FIZ5WH+iegGm8HPdj00NFlCSnm83Fp3Ln8I2f7uq1aIiWaA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@napi-rs/canvas-win32-arm64-msvc@0.1.97': resolution: {integrity: sha512-u883Yr6A6fO7Vpsy9YE4FVCIxzzo5sO+7pIUjjoDLjS3vQaNMkVzx5bdIpEL+ob+gU88WDK4VcxYMZ6nmnoX9A==} @@ -2095,24 +2127,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@16.1.6': resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@16.1.6': resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@16.1.6': resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@16.1.6': resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} @@ -2768,48 +2804,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-arm64-musl@0.45.0': resolution: {integrity: sha512-XQKXZIKYJC3GQJ8FnD3iMntpw69Wd9kDDK/Xt79p6xnFYlGGxSNv2vIBvRTDg5CKByWFWWZLCRDOXoP/m6YN4g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxfmt/binding-linux-ppc64-gnu@0.45.0': resolution: {integrity: sha512-+g5RiG+xOkdrCWkKodv407nTvMq4vYM18Uox2MhZBm/YoqFxxJpWKsloskFFG5NU13HGPw1wzYjjOVcyd9moCA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-riscv64-gnu@0.45.0': resolution: {integrity: sha512-V7dXKoSyEbWAkkSF4JJNtF+NJZDmJoSarSoP30WCsB3X636Rehd3CvxBj49FIJxEBFWhvcUjGSHVeU8Erck1bQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-riscv64-musl@0.45.0': resolution: {integrity: sha512-Vdelft1sAEYojVGgcODEFXSWYQYlIvoyIGWebKCuUibd1tvS1TjTx413xG2ZLuHpYj45CkN/ztMLMX6jrgqpgg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxfmt/binding-linux-s390x-gnu@0.45.0': resolution: {integrity: sha512-RR7xKgNpqwENnK0aYCGYg0JycY2n93J0reNjHyes+I9Gq52dH95x+CBlnlAQHCPfz6FGnKA9HirgUl14WO6o7w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-x64-gnu@0.45.0': resolution: {integrity: sha512-U/QQ0+BQNSHxjuXR/utvXnQ50Vu5kUuqEomZvQ1/3mhgbBiMc2WU9q5kZ5WwLp3gnFIx9ibkveoRSe2EZubkqg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxfmt/binding-linux-x64-musl@0.45.0': resolution: {integrity: sha512-o5TLOUCF0RWQjsIS06yVC+kFgp092/yLe6qBGSUvtnmTVw9gxjpdQSXc3VN5Cnive4K11HNstEZF8ROKHfDFSw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxfmt/binding-openharmony-arm64@0.45.0': resolution: {integrity: sha512-RnGcV3HgPuOjsGx/k9oyRNKmOp+NBLGzZTdPDYbc19r7NGeYPplnUU/BfU35bX2Y/O4ejvHxcfkvW2WoYL/gsg==} @@ -2864,36 +2908,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.6': resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} @@ -4222,66 +4272,79 @@ packages: resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.59.0': resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.59.0': resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.59.0': resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.59.0': resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.59.0': resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.59.0': resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.59.0': resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.59.0': resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.59.0': resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.59.0': resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.59.0': resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.59.0': resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.59.0': resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} @@ -4472,24 +4535,28 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] '@swc/core-linux-arm64-musl@1.15.13': resolution: {integrity: sha512-SmZ9m+XqCB35NddHCctvHFLqPZDAs5j8IgD36GoutufDJmeq2VNfgk5rQoqNqKmAK3Y7iFdEmI76QoHIWiCLyw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] '@swc/core-linux-x64-gnu@1.15.13': resolution: {integrity: sha512-5rij+vB9a29aNkHq72EXI2ihDZPszJb4zlApJY4aCC/q6utgqFA6CkrfTfIb+O8hxtG3zP5KERETz8mfFK6A0A==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] '@swc/core-linux-x64-musl@1.15.13': resolution: {integrity: sha512-OlSlaOK9JplQ5qn07WiBLibkOw7iml2++ojEXhhR3rbWrNEKCD7sd8+6wSavsInyFdw4PhLA+Hy6YyDBIE23Yw==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] '@swc/core-win32-arm64-msvc@1.15.13': resolution: {integrity: sha512-zwQii5YVdsfG8Ti9gIKgBKZg8qMkRZxl+OlYWUT5D93Jl4NuNBRausP20tfEkQdAPSRrMCSUZBM6FhW7izAZRg==} @@ -4573,24 +4640,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.1': resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.1': resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.1': resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.1': resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} @@ -4732,6 +4803,9 @@ packages: '@types/katex@0.16.8': resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} + '@types/lodash@4.17.24': + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -4915,41 +4989,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -5323,6 +5405,13 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + comma-separated-tokens@1.0.8: resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} @@ -6471,6 +6560,9 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} @@ -6838,24 +6930,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.31.1: resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} @@ -6880,6 +6976,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} @@ -7673,6 +7772,9 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true + react-base16-styling@0.10.0: + resolution: {integrity: sha512-H1k2eFB6M45OaiRru3PBXkuCcn2qNmx+gzLb4a9IPMR7tMH8oBRXU5jGbPDYG1Hz+82d88ED0vjR8BmqU3pQdg==} + react-compiler-runtime@1.0.0: resolution: {integrity: sha512-rRfjYv66HlG8896yPUDONgKzG5BxZD1nV9U6rkm+7VCuvQc903C4MjcoZR4zPw53IKSOX9wMQVpA1IAbRtzQ7w==} peerDependencies: @@ -7729,11 +7831,8 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react-json-view-lite@2.5.0: - resolution: {integrity: sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g==} - engines: {node: '>=18'} - peerDependencies: - react: ^18.0.0 || ^19.0.0 + react-lifecycles-compat@3.0.4: + resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} @@ -8121,6 +8220,9 @@ packages: simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + slate-dom@0.119.0: resolution: {integrity: sha512-foc8a2NkE+1SldDIYaoqjhVKupt8RSuvHI868rfYOcypD4we5TT7qunjRKJ852EIRh/Ql8sSTepXgXKOUJnt1w==} peerDependencies: @@ -10316,6 +10418,16 @@ snapshots: dependencies: mediabunny: 1.39.2 + '@microlink/react-json-view@1.31.20(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + react: 19.2.4 + react-base16-styling: 0.10.0 + react-dom: 19.2.4(react@19.2.4) + react-lifecycles-compat: 3.0.4 + react-textarea-autosize: 8.5.9(@types/react@19.2.14)(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + '@monaco-editor/loader@1.7.0': dependencies: state-local: 1.0.7 @@ -13283,6 +13395,8 @@ snapshots: '@types/katex@0.16.8': {} + '@types/lodash@4.17.24': {} + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -13905,6 +14019,16 @@ snapshots: color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + comma-separated-tokens@1.0.8: {} comma-separated-tokens@2.0.3: {} @@ -15327,6 +15451,8 @@ snapshots: is-arrayish@0.2.1: {} + is-arrayish@0.3.4: {} + is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -15662,6 +15788,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.18.1: {} + lodash.camelcase@4.3.0: {} lodash.debounce@4.0.8: {} @@ -16843,6 +16971,13 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 + react-base16-styling@0.10.0: + dependencies: + '@types/lodash': 4.17.24 + color: 4.2.3 + csstype: 3.2.3 + lodash-es: 4.18.1 + react-compiler-runtime@1.0.0(react@19.2.4): dependencies: react: 19.2.4 @@ -16894,9 +17029,7 @@ snapshots: react-is@16.13.1: {} - react-json-view-lite@2.5.0(react@19.2.4): - dependencies: - react: 19.2.4 + react-lifecycles-compat@3.0.4: {} react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.4): dependencies: @@ -17458,6 +17591,10 @@ snapshots: once: 1.4.0 simple-concat: 1.0.1 + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + slate-dom@0.119.0(slate@0.120.0): dependencies: '@juggle/resize-observer': 3.4.0 From 4f202e1fa380c13e0dc8e81a3044335306c1ce7b Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 17:58:00 +0200 Subject: [PATCH 145/198] feat(web): inline edit on trigger cards --- .../components/trigger-card.tsx | 164 +++++++++++++++--- 1 file changed, 137 insertions(+), 27 deletions(-) diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx index a1d84d2d7..681877523 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/components/trigger-card.tsx @@ -1,12 +1,13 @@ "use client"; import { useAtomValue } from "jotai"; -import { CalendarClock, Clock, Trash2 } from "lucide-react"; +import { AlertCircle, CalendarClock, Clock, Pencil, Save, Trash2 } from "lucide-react"; import { useState } from "react"; import { updateTriggerMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; import { JsonView } from "@/components/json-view"; import { Button } from "@/components/ui/button"; +import { Spinner } from "@/components/ui/spinner"; import { Switch } from "@/components/ui/switch"; -import type { Trigger } from "@/contracts/types/automation.types"; +import { type Trigger, triggerUpdateRequest } from "@/contracts/types/automation.types"; import { describeCron } from "@/lib/automations/describe-cron"; import { formatRelativeDate, formatRelativeFutureDate } from "@/lib/format-date"; import { DeleteTriggerDialog } from "./delete-trigger-dialog"; @@ -18,20 +19,36 @@ interface TriggerCardProps { canDelete: boolean; } +interface TriggerDraft { + params: Record<string, unknown>; + static_inputs: Record<string, unknown>; +} + +function draftFromTrigger(trigger: Trigger): TriggerDraft { + return { + params: trigger.params, + static_inputs: trigger.static_inputs ?? {}, + }; +} + /** * One trigger row in the Triggers section of the detail page. Renders: * - type icon + human-readable schedule + timezone * - last_fired_at / next_fire_at hints * - static_inputs as formatted JSON (when present) - * - enable toggle + remove button (each gated independently) + * - enable toggle + remove button + inline edit (each gated independently) * - * Editing params (cron, timezone, static_inputs) lives behind the future - * raw-JSON path; this card stays read-only-except-for-toggle for v1. + * Inline edit covers ``params`` and ``static_inputs`` — the two fields the + * backend ``PATCH /triggers/[id]`` endpoint accepts beyond ``enabled``. + * ``enabled`` stays on the Switch so the two surfaces don't fight. */ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: TriggerCardProps) { const { mutateAsync: updateTrigger, isPending: updating } = useAtomValue(updateTriggerMutationAtom); const [deleteOpen, setDeleteOpen] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [draft, setDraft] = useState<TriggerDraft>(() => draftFromTrigger(trigger)); + const [issues, setIssues] = useState<string[]>([]); const cron = typeof trigger.params.cron === "string" ? trigger.params.cron : undefined; const tz = typeof trigger.params.timezone === "string" ? trigger.params.timezone : "UTC"; @@ -47,6 +64,38 @@ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: Tri }); } + function startEdit() { + setDraft(draftFromTrigger(trigger)); + setIssues([]); + setIsEditing(true); + } + + function cancelEdit() { + setIsEditing(false); + setIssues([]); + } + + async function saveEdit() { + setIssues([]); + const result = triggerUpdateRequest.safeParse(draft); + if (!result.success) { + setIssues( + result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`) + ); + return; + } + try { + await updateTrigger({ + automationId, + triggerId: trigger.id, + patch: result.data, + }); + setIsEditing(false); + } catch (err) { + setIssues([(err as Error).message ?? "Update failed"]); + } + } + return ( <> <div className="rounded-md border border-border/60 overflow-hidden"> @@ -71,17 +120,29 @@ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: Tri <Switch checked={trigger.enabled} onCheckedChange={handleToggle} - disabled={updating} + disabled={updating || isEditing} aria-label={trigger.enabled ? "Disable trigger" : "Enable trigger"} /> </div> )} + {canUpdate && !isEditing && ( + <Button + variant="ghost" + size="icon" + className="h-8 w-8 text-muted-foreground" + onClick={startEdit} + aria-label="Edit trigger" + > + <Pencil className="h-4 w-4" /> + </Button> + )} {canDelete && ( <Button variant="ghost" size="icon" className="h-8 w-8 text-muted-foreground hover:text-destructive" onClick={() => setDeleteOpen(true)} + disabled={isEditing} aria-label="Remove trigger" > <Trash2 className="h-4 w-4" /> @@ -91,29 +152,78 @@ export function TriggerCard({ trigger, automationId, canUpdate, canDelete }: Tri </div> <div className="px-4 py-3 space-y-3 text-xs"> - {(trigger.last_fired_at || trigger.next_fire_at) && ( - <dl className="grid grid-cols-[auto_minmax(0,1fr)] items-baseline gap-x-3 gap-y-1"> - {trigger.next_fire_at && ( - <TimeRow - label="Next fire" - iso={trigger.next_fire_at} - tense="future" - highlight={trigger.enabled} + {isEditing ? ( + <> + <div className="rounded-md border border-input bg-background px-3 py-2 max-h-[24rem] overflow-auto"> + <JsonView + src={draft} + editable + onChange={(next) => setDraft(next as TriggerDraft)} + collapsed={false} /> - )} - {trigger.last_fired_at && ( - <TimeRow label="Last fired" iso={trigger.last_fired_at} tense="past" /> - )} - </dl> - )} - - {hasStaticInputs && ( - <div> - <div className="text-muted-foreground mb-1">Static inputs</div> - <div className="rounded-md bg-muted/40 px-3 py-2 overflow-auto"> - <JsonView src={trigger.static_inputs} collapsed={1} /> </div> - </div> + + {issues.length > 0 && ( + <div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2"> + <div className="flex items-center gap-1.5 font-medium text-destructive mb-1"> + <AlertCircle className="h-3 w-3" aria-hidden /> + {issues.length === 1 ? "1 issue" : `${issues.length} issues`} + </div> + <ul className="space-y-0.5 text-destructive list-disc list-inside"> + {issues.map((issue) => ( + <li key={issue}>{issue}</li> + ))} + </ul> + </div> + )} + + <div className="flex items-center justify-end gap-2"> + <Button + type="button" + variant="ghost" + size="sm" + onClick={cancelEdit} + disabled={updating} + > + Cancel + </Button> + <Button type="button" size="sm" onClick={saveEdit} disabled={updating}> + {updating ? ( + <Spinner size="xs" className="mr-1.5" /> + ) : ( + <Save className="mr-1.5 h-3.5 w-3.5" /> + )} + Save + </Button> + </div> + </> + ) : ( + <> + {(trigger.last_fired_at || trigger.next_fire_at) && ( + <dl className="grid grid-cols-[auto_minmax(0,1fr)] items-baseline gap-x-3 gap-y-1"> + {trigger.next_fire_at && ( + <TimeRow + label="Next fire" + iso={trigger.next_fire_at} + tense="future" + highlight={trigger.enabled} + /> + )} + {trigger.last_fired_at && ( + <TimeRow label="Last fired" iso={trigger.last_fired_at} tense="past" /> + )} + </dl> + )} + + {hasStaticInputs && ( + <div> + <div className="text-muted-foreground mb-1">Static inputs</div> + <div className="rounded-md bg-muted/40 px-3 py-2 overflow-auto"> + <JsonView src={trigger.static_inputs} collapsed={1} /> + </div> + </div> + )} + </> )} </div> </div> From 2a76f43387276576be0513496c643f080644b6bd Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 19:02:52 +0200 Subject: [PATCH 146/198] test(automations/triggers): lock schedule cron + params Cover the cron + IANA timezone + UTC normalization contract for the schedule trigger: next-match strictly-after, DST offset shift across spring-forward, malformed cron / unknown timezone rejection, and the ScheduleTriggerParams Pydantic gate that surfaces InvalidCronError as ValidationError at the API boundary. 8 tests, pure unit (no DB, no mocks). --- .../unit/automations/triggers/__init__.py | 0 .../automations/triggers/schedule/__init__.py | 0 .../triggers/schedule/test_cron.py | 82 +++++++++++++++++++ .../triggers/schedule/test_params.py | 34 ++++++++ 4 files changed, 116 insertions(+) create mode 100644 surfsense_backend/tests/unit/automations/triggers/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/triggers/schedule/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py create mode 100644 surfsense_backend/tests/unit/automations/triggers/schedule/test_params.py diff --git a/surfsense_backend/tests/unit/automations/triggers/__init__.py b/surfsense_backend/tests/unit/automations/triggers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/triggers/schedule/__init__.py b/surfsense_backend/tests/unit/automations/triggers/schedule/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py b/surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py new file mode 100644 index 000000000..261e51b18 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py @@ -0,0 +1,82 @@ +"""Lock the cron + timezone + UTC normalization contract.""" + +from __future__ import annotations + +from datetime import UTC, datetime + +import pytest + +from app.automations.triggers.schedule.cron import ( + InvalidCronError, + compute_next_fire_at, + validate_cron, +) + +pytestmark = pytest.mark.unit + + +def test_compute_next_fire_at_returns_next_match_normalized_to_utc() -> None: + """``compute_next_fire_at`` evaluates the cron in the given IANA timezone + and returns the next strictly-later match expressed in UTC. + + Setup: ``0 9 * * 1-5`` (09:00 Monday-Friday) in ``Africa/Kigali`` + (UTC+2, no DST). With ``after`` = Tuesday 05:00 UTC (= 07:00 local), + the next fire is the same Tuesday at 09:00 local = 07:00 UTC. + """ + after = datetime(2026, 5, 26, 5, 0, tzinfo=UTC) # Tue 07:00 Kigali + + next_fire = compute_next_fire_at("0 9 * * 1-5", "Africa/Kigali", after=after) + + assert next_fire == datetime(2026, 5, 26, 7, 0, tzinfo=UTC) + + +def test_compute_next_fire_at_respects_dst_offset_change() -> None: + """A daily cron in a DST-observing tz fires at the same local hour + across the DST boundary, which produces a different UTC offset on + either side of the transition. + + Setup: ``0 9 * * *`` (09:00 every day) in ``America/New_York``. + NY is UTC-5 in winter (EST), UTC-4 in summer (EDT). Evaluating from + each side of the spring-forward in 2026 (Sun Mar 8 at 02:00 → 03:00): + + - winter: ``after`` = 2026-02-15 (EST, UTC-5) → next 09:00 EST = 14:00 UTC + - summer: ``after`` = 2026-04-15 (EDT, UTC-4) → next 09:00 EDT = 13:00 UTC + """ + winter_after = datetime(2026, 2, 15, 0, 0, tzinfo=UTC) + summer_after = datetime(2026, 4, 15, 0, 0, tzinfo=UTC) + + winter_fire = compute_next_fire_at("0 9 * * *", "America/New_York", after=winter_after) + summer_fire = compute_next_fire_at("0 9 * * *", "America/New_York", after=summer_after) + + assert winter_fire == datetime(2026, 2, 15, 14, 0, tzinfo=UTC) + assert summer_fire == datetime(2026, 4, 15, 13, 0, tzinfo=UTC) + + +def test_compute_next_fire_at_is_strictly_after_when_after_equals_a_match() -> None: + """When ``after`` lands exactly on a cron match, the result jumps to the + next match — never the same instant. Required so the schedule-tick + can pass ``next_fire_at`` itself as ``after`` to advance to the + following slot without double-firing. + + Setup: weekday 09:00 Kigali. ``after`` = Mon 09:00 Kigali = 07:00 UTC + (an exact match) → next fire must be Tue 09:00 Kigali = next day 07:00 UTC. + """ + after = datetime(2026, 5, 25, 7, 0, tzinfo=UTC) # Mon 09:00 Kigali — exact match + + next_fire = compute_next_fire_at("0 9 * * 1-5", "Africa/Kigali", after=after) + + assert next_fire == datetime(2026, 5, 26, 7, 0, tzinfo=UTC) # Tue 09:00 Kigali + + +def test_validate_cron_rejects_malformed_cron_expression() -> None: + """A syntactically invalid cron must be rejected at validation time so + bad triggers can't reach storage and explode at fire time.""" + with pytest.raises(InvalidCronError): + validate_cron("this is not cron", "UTC") + + +def test_validate_cron_rejects_unknown_timezone() -> None: + """A non-IANA timezone string must be rejected at validation time — + the same protective gate as the cron expression itself.""" + with pytest.raises(InvalidCronError): + validate_cron("0 9 * * *", "Mars/Olympus_Mons") diff --git a/surfsense_backend/tests/unit/automations/triggers/schedule/test_params.py b/surfsense_backend/tests/unit/automations/triggers/schedule/test_params.py new file mode 100644 index 000000000..be98c5be1 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/triggers/schedule/test_params.py @@ -0,0 +1,34 @@ +"""Lock the ``ScheduleTriggerParams`` validation contract.""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.triggers.schedule.params import ScheduleTriggerParams + +pytestmark = pytest.mark.unit + + +def test_schedule_params_accept_valid_cron_and_iana_timezone() -> None: + """A well-formed cron + IANA timezone yields a populated model. + Locks the round-trip path users go through when creating a trigger.""" + params = ScheduleTriggerParams(cron="0 9 * * 1-5", timezone="Africa/Kigali") + + assert params.cron == "0 9 * * 1-5" + assert params.timezone == "Africa/Kigali" + + +def test_schedule_params_reject_malformed_cron_with_validation_error() -> None: + """``InvalidCronError`` from ``validate_cron`` must surface as + Pydantic ``ValidationError`` so the FastAPI layer returns 422 instead + of letting the bad value reach storage.""" + with pytest.raises(ValidationError): + ScheduleTriggerParams(cron="not cron", timezone="UTC") + + +def test_schedule_params_reject_unknown_timezone_with_validation_error() -> None: + """An unknown timezone is rejected at the API boundary — same gate + as the cron expression itself.""" + with pytest.raises(ValidationError): + ScheduleTriggerParams(cron="0 9 * * *", timezone="Mars/Olympus_Mons") From 18b4800e49ab8463b563e9129edcfc33da1143e8 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 19:03:00 +0200 Subject: [PATCH 147/198] test(automations/dispatch): lock _validate_inputs + DispatchError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cover the input-validation contract dispatch_run relies on: - no declared schema → inputs pass through unchanged (regression site that previously stripped runtime keys like fired_at / last_fired_at and broke Jinja templates). - declared schema, valid inputs → passthrough validated. - declared schema, invalid inputs → DispatchError (uniform exception type, not raw jsonschema.ValidationError). Plus the DispatchError exception identity (Exception subclass, message preserved, isinstance-friendly for the dispatch layer's consumers). 4 tests, pure unit. --- .../unit/automations/dispatch/__init__.py | 0 .../unit/automations/dispatch/test_errors.py | 28 +++++++ .../dispatch/test_validate_inputs.py | 77 +++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 surfsense_backend/tests/unit/automations/dispatch/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/dispatch/test_errors.py create mode 100644 surfsense_backend/tests/unit/automations/dispatch/test_validate_inputs.py diff --git a/surfsense_backend/tests/unit/automations/dispatch/__init__.py b/surfsense_backend/tests/unit/automations/dispatch/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/dispatch/test_errors.py b/surfsense_backend/tests/unit/automations/dispatch/test_errors.py new file mode 100644 index 000000000..89c1bede9 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/dispatch/test_errors.py @@ -0,0 +1,28 @@ +"""Lock the ``DispatchError`` exception contract. + +``DispatchError`` is the uniform exception type the dispatch layer raises +for any "cannot turn this fire request into a run" condition. Other +modules (templates of error envelopes, run records) compare on +``isinstance(exc, DispatchError)``, so the inheritance is the contract. +""" + +from __future__ import annotations + +import pytest + +from app.automations.dispatch.errors import DispatchError + +pytestmark = pytest.mark.unit + + +def test_dispatch_error_is_exception_subclass_and_carries_message() -> None: + """Lifting a string into ``DispatchError`` preserves the message and + behaves as a regular ``Exception`` for ``isinstance`` / ``raise`` / + ``except`` consumers.""" + error = DispatchError("missing trigger") + + assert isinstance(error, Exception) + assert str(error) == "missing trigger" + + with pytest.raises(DispatchError): + raise error diff --git a/surfsense_backend/tests/unit/automations/dispatch/test_validate_inputs.py b/surfsense_backend/tests/unit/automations/dispatch/test_validate_inputs.py new file mode 100644 index 000000000..ec99e51c2 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/dispatch/test_validate_inputs.py @@ -0,0 +1,77 @@ +"""Lock the input-validation contract used by ``dispatch_run``. + +``_validate_inputs`` is module-internal by convention (underscore), but it +encodes a real behavior contract the rest of the system depends on, and the +public alternative (``dispatch_run``) requires a real DB session. Tests +target the pure function directly; the contract — not the symbol — is what's +locked. +""" + +from __future__ import annotations + +import pytest + +from app.automations.dispatch.errors import DispatchError +from app.automations.dispatch.run import _validate_inputs +from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.schemas.definition.inputs import Inputs +from app.automations.schemas.definition.plan_step import PlanStep + +pytestmark = pytest.mark.unit + + +def _minimal_definition(*, inputs: Inputs | None = None) -> AutomationDefinition: + """One-step definition with an optional declared input schema.""" + return AutomationDefinition( + name="test", + inputs=inputs, + plan=[PlanStep(step_id="s1", action="agent_task")], + ) + + +def test_validate_inputs_passes_through_when_no_schema_is_declared() -> None: + """When the definition declares no input schema, runtime inputs reach + the template context **unchanged**. Regression site: previously this + branch returned ``{}``, which stripped runtime keys like ``fired_at`` + and ``last_fired_at`` and made Jinja blow up on ``{{ inputs.* }}``. + """ + definition = _minimal_definition(inputs=None) + runtime_inputs = { + "fired_at": "2026-01-01T00:00:00+00:00", + "last_fired_at": None, + "static_key": "value", + } + + assert _validate_inputs(definition, runtime_inputs) == runtime_inputs + + +def test_validate_inputs_returns_inputs_when_they_match_declared_schema() -> None: + """With a declared JSON schema, inputs that satisfy it pass through + unchanged (validation succeeds; the function does not coerce or + strip extra fields not mentioned in the schema).""" + schema = { + "type": "object", + "properties": {"topic": {"type": "string"}}, + "required": ["topic"], + } + definition = _minimal_definition(inputs=Inputs(schema=schema)) + + inputs = {"topic": "weekly report"} + + assert _validate_inputs(definition, inputs) == inputs + + +def test_validate_inputs_raises_dispatch_error_when_inputs_violate_schema() -> None: + """Inputs that don't match the declared schema must surface as + ``DispatchError`` (not the raw ``jsonschema.ValidationError``), so the + schedule tick and any other caller can handle one dispatch-domain + exception type uniformly.""" + schema = { + "type": "object", + "properties": {"topic": {"type": "string"}}, + "required": ["topic"], + } + definition = _minimal_definition(inputs=Inputs(schema=schema)) + + with pytest.raises(DispatchError): + _validate_inputs(definition, {"topic": 42}) # type violates string From 49af95b65241a5bdfaba79fe36f17a40c9c72acd Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 19:03:08 +0200 Subject: [PATCH 148/198] test(automations/runtime): lock execute_step + with_retries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit execute_step (6 tests): happy path, when=falsy → skipped, unknown action → ActionNotFound failure, retry budget exhaustion (attempts = 1 + max_retries), retry recovery, and template-rendering of step params against the run context. with_retries (3 tests): first-try success returns attempts=1, recovery returns the actual attempt that produced the result, and exhaustion re-raises the last exception with the handler called 1 + max_retries times. All tests use backoff="none" to keep wall-clock time zero; timeout testing is intentionally skipped (would need >= 1s per the int contract, and exhaustion already locks that any Exception triggers retry). --- .../unit/automations/runtime/__init__.py | 0 .../automations/runtime/test_execute_step.py | 272 ++++++++++++++++++ .../unit/automations/runtime/test_retries.py | 72 +++++ 3 files changed, 344 insertions(+) create mode 100644 surfsense_backend/tests/unit/automations/runtime/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/runtime/test_execute_step.py create mode 100644 surfsense_backend/tests/unit/automations/runtime/test_retries.py diff --git a/surfsense_backend/tests/unit/automations/runtime/__init__.py b/surfsense_backend/tests/unit/automations/runtime/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/runtime/test_execute_step.py b/surfsense_backend/tests/unit/automations/runtime/test_execute_step.py new file mode 100644 index 000000000..9b203fdba --- /dev/null +++ b/surfsense_backend/tests/unit/automations/runtime/test_execute_step.py @@ -0,0 +1,272 @@ +"""Lock the ``execute_step`` orchestration contract. + +Covers the pure step-execution logic: predicate gate, params rendering, +action lookup, retry budget, error shaping. The ``ActionContext.session`` +is never touched by ``execute_step`` itself (it's only forwarded to the +handler), so unit tests pass ``None`` cast to the type. +""" + +from __future__ import annotations + +from typing import Any, cast + +import pytest +from pydantic import BaseModel +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.actions.store import register_action +from app.automations.actions.types import ActionContext, ActionDefinition +from app.automations.runtime.step import execute_step +from app.automations.schemas.definition.plan_step import PlanStep + +pytestmark = pytest.mark.unit + + +class _AnyParams(BaseModel): + """Open params model used by test actions — they never validate.""" + + model_config = {"extra": "allow"} + + +def _action_context() -> ActionContext: + """Minimal context: session is unused by ``execute_step``, only forwarded.""" + return ActionContext( + session=cast(AsyncSession, None), + run_id=1, + step_id="s1", + search_space_id=1, + creator_user_id=None, + ) + + +async def test_execute_step_runs_registered_action_handler_and_wraps_result( + isolated_action_registry: None, +) -> None: + """A step pointing at a registered action runs its handler with the + step's params and returns a ``succeeded`` entry carrying the handler's + output plus ``attempts=1`` (one try, no retries triggered).""" + invocations: list[dict[str, Any]] = [] + + async def echo(params: dict[str, Any]) -> dict[str, Any]: + invocations.append(params) + return {"echoed": params["value"]} + + register_action( + ActionDefinition( + type="test_echo", + name="Echo", + description="Test action.", + params_model=_AnyParams, + build_handler=lambda _ctx: echo, + ) + ) + + step = PlanStep(step_id="s1", action="test_echo", params={"value": "hello"}) + + result = await execute_step( + step=step, + template_context={}, + action_context=_action_context(), + default_max_retries=0, + default_retry_backoff="none", + default_timeout_seconds=30, + ) + + assert result["status"] == "succeeded" + assert result["step_id"] == "s1" + assert result["action"] == "test_echo" + assert result["attempts"] == 1 + assert result["result"] == {"echoed": "hello"} + assert invocations == [{"value": "hello"}] + + +async def test_execute_step_skips_step_when_predicate_is_falsy( + isolated_action_registry: None, +) -> None: + """If ``step.when`` evaluates to falsy in the template context, the + handler is **not** invoked, the result entry has ``status=skipped`` + and ``attempts=0``, and no ``result`` key is present.""" + invoked = False + + async def must_not_run(_params: dict[str, Any]) -> dict[str, Any]: + nonlocal invoked + invoked = True + return {} + + register_action( + ActionDefinition( + type="test_guarded", + name="Guarded", + description="Test action that should not run.", + params_model=_AnyParams, + build_handler=lambda _ctx: must_not_run, + ) + ) + + step = PlanStep( + step_id="s1", + action="test_guarded", + when="inputs.enabled", + params={}, + ) + + result = await execute_step( + step=step, + template_context={"inputs": {"enabled": False}}, + action_context=_action_context(), + default_max_retries=0, + default_retry_backoff="none", + default_timeout_seconds=30, + ) + + assert result["status"] == "skipped" + assert result["attempts"] == 0 + assert "result" not in result + assert invoked is False + + +async def test_execute_step_fails_when_step_references_an_unknown_action( + isolated_action_registry: None, +) -> None: + """A step pointing at an action that isn't in the registry must fail + with ``ActionNotFound`` rather than crashing. Catches typos in the + plan and removed actions without the run going off the rails.""" + step = PlanStep(step_id="s1", action="no_such_action", params={}) + + result = await execute_step( + step=step, + template_context={}, + action_context=_action_context(), + default_max_retries=0, + default_retry_backoff="none", + default_timeout_seconds=30, + ) + + assert result["status"] == "failed" + assert result["attempts"] == 0 + assert result["error"]["type"] == "ActionNotFound" + assert "no_such_action" in result["error"]["message"] + + +async def test_execute_step_retries_failing_handler_up_to_default_budget( + isolated_action_registry: None, +) -> None: + """A handler that raises on every attempt consumes the retry budget + (1 initial try + ``default_max_retries`` retries) and the step ends + ``failed`` with the exception's type and message surfaced through + the error envelope.""" + calls = 0 + + async def always_fails(_params: dict[str, Any]) -> dict[str, Any]: + nonlocal calls + calls += 1 + raise RuntimeError("boom") + + register_action( + ActionDefinition( + type="test_fails", + name="Fails", + description="Always raises.", + params_model=_AnyParams, + build_handler=lambda _ctx: always_fails, + ) + ) + + step = PlanStep(step_id="s1", action="test_fails", params={}) + + result = await execute_step( + step=step, + template_context={}, + action_context=_action_context(), + default_max_retries=2, + default_retry_backoff="none", + default_timeout_seconds=30, + ) + + assert result["status"] == "failed" + assert result["attempts"] == 3 + assert calls == 3 + assert result["error"]["type"] == "RuntimeError" + assert "boom" in result["error"]["message"] + + +async def test_execute_step_succeeds_when_handler_recovers_within_retry_budget( + isolated_action_registry: None, +) -> None: + """A handler that fails the first N times and then succeeds yields a + ``succeeded`` entry with ``attempts == N + 1``. Locks that retries + can actually recover (not just exhaust).""" + calls = 0 + + async def flaky(_params: dict[str, Any]) -> dict[str, Any]: + nonlocal calls + calls += 1 + if calls < 3: + raise RuntimeError("transient") + return {"ok": True} + + register_action( + ActionDefinition( + type="test_flaky", + name="Flaky", + description="Fails twice, succeeds third time.", + params_model=_AnyParams, + build_handler=lambda _ctx: flaky, + ) + ) + + step = PlanStep(step_id="s1", action="test_flaky", params={}) + + result = await execute_step( + step=step, + template_context={}, + action_context=_action_context(), + default_max_retries=2, + default_retry_backoff="none", + default_timeout_seconds=30, + ) + + assert result["status"] == "succeeded" + assert result["attempts"] == 3 + assert result["result"] == {"ok": True} + assert calls == 3 + + +async def test_execute_step_renders_step_params_through_template_engine( + isolated_action_registry: None, +) -> None: + """Step params are rendered against the template context before the + handler is invoked. String values containing Jinja expressions get + substituted from ``inputs`` and ``steps`` in the run context.""" + received: list[dict[str, Any]] = [] + + async def capture(params: dict[str, Any]) -> dict[str, Any]: + received.append(params) + return {} + + register_action( + ActionDefinition( + type="test_capture", + name="Capture", + description="Captures the params passed in.", + params_model=_AnyParams, + build_handler=lambda _ctx: capture, + ) + ) + + step = PlanStep( + step_id="s1", + action="test_capture", + params={"message": "Hello {{ inputs.name }}"}, + ) + + await execute_step( + step=step, + template_context={"inputs": {"name": "World"}, "steps": {}}, + action_context=_action_context(), + default_max_retries=0, + default_retry_backoff="none", + default_timeout_seconds=30, + ) + + assert received == [{"message": "Hello World"}] diff --git a/surfsense_backend/tests/unit/automations/runtime/test_retries.py b/surfsense_backend/tests/unit/automations/runtime/test_retries.py new file mode 100644 index 000000000..f0f12ca59 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/runtime/test_retries.py @@ -0,0 +1,72 @@ +"""Lock the ``with_retries`` policy: budget, recovery, exhaustion, timeout, backoff. + +Tests with ``backoff="none"`` to keep wall-clock time zero. Backoff sleep +values themselves are observed by monkeypatching ``asyncio.sleep`` so we +don't introduce flakiness via real timing. +""" + +from __future__ import annotations + +import pytest + +from app.automations.runtime.retries import with_retries + +pytestmark = pytest.mark.unit + + +async def test_with_retries_returns_result_and_attempts_one_on_first_success() -> None: + """A coroutine that succeeds on the first call returns its result + paired with ``attempts=1`` — no retry consumed.""" + calls = 0 + + async def succeed() -> str: + nonlocal calls + calls += 1 + return "ok" + + result, attempts = await with_retries( + succeed, max_retries=2, backoff="none", timeout=None + ) + + assert result == "ok" + assert attempts == 1 + assert calls == 1 + + +async def test_with_retries_returns_attempt_count_when_succeeding_after_failures() -> None: + """A coroutine that fails twice then succeeds returns ``attempts=3`` + (the actual attempt that produced the result). Locks the contract + that the caller can distinguish first-try success from a recovery.""" + calls = 0 + + async def flaky() -> str: + nonlocal calls + calls += 1 + if calls < 3: + raise RuntimeError("transient") + return "ok" + + result, attempts = await with_retries( + flaky, max_retries=5, backoff="none", timeout=None + ) + + assert result == "ok" + assert attempts == 3 + assert calls == 3 + + +async def test_with_retries_reraises_after_exhausting_the_budget() -> None: + """When the coroutine raises on every attempt within + ``1 + max_retries`` tries, the last exception propagates and the + handler is called exactly ``1 + max_retries`` times.""" + calls = 0 + + async def always_fails() -> str: + nonlocal calls + calls += 1 + raise RuntimeError(f"boom-{calls}") + + with pytest.raises(RuntimeError, match="boom-3"): + await with_retries(always_fails, max_retries=2, backoff="none", timeout=None) + + assert calls == 3 # 1 initial + 2 retries From db4eef651f62f735e9c918d5bd23f93c2d1f66ab Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 19:03:22 +0200 Subject: [PATCH 149/198] test(automations/templating): lock render, filters, environment, context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit render.py (4): variable substitution, StrictUndefined raises on missing keys, evaluate_predicate coerces to bool, render_value walks dicts/lists and renders string leaves. filters.py (4): slugify produces URL-safe output, date formats datetime with strftime, date(None) → "" so templates can write {{ inputs.last_fired_at | date }} on first run, date(str) passes through. environment.py (4): the sandbox boundary — disallowed Jinja built-ins (e.g. pprint) raise, and the finalize hook coerces non-string outputs to predictable wire shapes (datetime → ISO, None → "", dict → JSON). context.py (1): build_run_context exposes {run, inputs, steps} with the exact shape every plan template body relies on. 13 tests total, all pure unit. --- .../unit/automations/templating/__init__.py | 0 .../automations/templating/test_context.py | 53 +++++++++++++++++ .../templating/test_environment.py | 51 ++++++++++++++++ .../automations/templating/test_filters.py | 42 +++++++++++++ .../automations/templating/test_render.py | 59 +++++++++++++++++++ 5 files changed, 205 insertions(+) create mode 100644 surfsense_backend/tests/unit/automations/templating/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/templating/test_context.py create mode 100644 surfsense_backend/tests/unit/automations/templating/test_environment.py create mode 100644 surfsense_backend/tests/unit/automations/templating/test_filters.py create mode 100644 surfsense_backend/tests/unit/automations/templating/test_render.py diff --git a/surfsense_backend/tests/unit/automations/templating/__init__.py b/surfsense_backend/tests/unit/automations/templating/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/templating/test_context.py b/surfsense_backend/tests/unit/automations/templating/test_context.py new file mode 100644 index 000000000..54f372e77 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/templating/test_context.py @@ -0,0 +1,53 @@ +"""Lock the ``{run, inputs, steps}`` namespace exposed to every template.""" + +from __future__ import annotations + +from datetime import UTC, datetime +from uuid import UUID + +import pytest + +from app.automations.templating.context import build_run_context + +pytestmark = pytest.mark.unit + + +def test_build_run_context_exposes_run_inputs_and_steps_namespaces() -> None: + """The namespace handed to templates groups run metadata under ``run``, + runtime + static inputs under ``inputs``, and step outputs (keyed by + ``output_as`` / ``step_id``) under ``steps``. Locks the contract that + every plan template body relies on.""" + creator = UUID("00000000-0000-0000-0000-000000000001") + started = datetime(2026, 5, 28, 14, 30, tzinfo=UTC) + + ctx = build_run_context( + run_id=42, + automation_id=7, + automation_name="Weekly digest", + automation_version=3, + search_space_id=1, + creator_id=creator, + trigger_id=11, + trigger_type="schedule", + started_at=started, + attempt=2, + inputs={"topic": "weekly"}, + step_outputs={"summarize": {"text": "ok"}}, + ) + + assert ctx == { + "run": { + "id": 42, + "automation_id": 7, + "automation_name": "Weekly digest", + "automation_version": 3, + "search_space_id": 1, + "creator_id": creator, + "trigger_id": 11, + "trigger_type": "schedule", + "started_at": started, + "attempt": 2, + }, + "inputs": {"topic": "weekly"}, + "steps": {"summarize": {"text": "ok"}}, + } diff --git a/surfsense_backend/tests/unit/automations/templating/test_environment.py b/surfsense_backend/tests/unit/automations/templating/test_environment.py new file mode 100644 index 000000000..ec1c0ee40 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/templating/test_environment.py @@ -0,0 +1,51 @@ +"""Lock the sandbox boundary: disallowed filters/tests reject, finalize coerces non-strings. + +These behaviors live in ``environment.py`` but are observed through the +public ``render_template`` surface — the same surface every step uses. +""" + +from __future__ import annotations + +from datetime import UTC, datetime + +import pytest +from jinja2.exceptions import TemplateError + +from app.automations.templating.render import render_template + +pytestmark = pytest.mark.unit + + +def test_environment_rejects_filters_not_in_the_allowlist() -> None: + """A template that pipes through a Jinja built-in **not** in the + allowlist (e.g. ``pprint``) must fail rather than rendering. Locks + the sandbox surface against accidental re-introduction of removed + filters.""" + with pytest.raises(TemplateError): + render_template("{{ value | pprint }}", {"value": {"k": 1}}) + + +def test_environment_finalizes_datetime_output_to_iso_string() -> None: + """A datetime that lands directly at an output site is stringified + via ``isoformat()`` rather than producing ``str(datetime)`` (which + has a space separator). Locks the wire shape templates produce + when emitting ``inputs.fired_at`` and other datetime values.""" + dt = datetime(2026, 5, 28, 14, 30, tzinfo=UTC) + + assert render_template("{{ moment }}", {"moment": dt}) == "2026-05-28T14:30:00+00:00" + + +def test_environment_finalizes_none_output_to_empty_string() -> None: + """A ``None`` at an output site becomes the empty string. Lets + templates write ``{{ inputs.last_fired_at }}`` unconditionally on + the first run without exploding on the null.""" + assert render_template("{{ missing }}", {"missing": None}) == "" + + +def test_environment_finalizes_dict_output_to_json() -> None: + """A dict at an output site is JSON-serialized. Same for lists. + Locks the wire shape so users embedding structured values into + prompts get deterministic, parseable output.""" + rendered = render_template("{{ payload }}", {"payload": {"a": 1, "b": [2, 3]}}) + + assert rendered == '{"a": 1, "b": [2, 3]}' diff --git a/surfsense_backend/tests/unit/automations/templating/test_filters.py b/surfsense_backend/tests/unit/automations/templating/test_filters.py new file mode 100644 index 000000000..cf83ee337 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/templating/test_filters.py @@ -0,0 +1,42 @@ +"""Lock the custom Jinja filters: ``date`` and ``slugify``.""" + +from __future__ import annotations + +from datetime import UTC, datetime + +import pytest + +from app.automations.templating.filters import filter_date, filter_slugify + +pytestmark = pytest.mark.unit + + +def test_filter_slugify_produces_url_safe_slug_from_typical_title() -> None: + """``filter_slugify`` lowercases, replaces non-alphanumerics with + hyphens, collapses repeats, and trims edge hyphens — the standard + URL-slug contract users expect when piping titles into paths.""" + assert filter_slugify("Hello, World! 2026") == "hello-world-2026" + + +def test_filter_date_formats_datetime_with_strftime_format() -> None: + """``filter_date`` calls ``strftime`` on datetime-like values with the + provided format. Default format yields ISO date (YYYY-MM-DD).""" + dt = datetime(2026, 5, 28, 14, 30, tzinfo=UTC) + + assert filter_date(dt) == "2026-05-28" + assert filter_date(dt, "%Y/%m/%d %H:%M") == "2026/05/28 14:30" + + +def test_filter_date_returns_empty_string_for_none() -> None: + """``None`` (e.g., a never-fired ``last_fired_at``) renders as the + empty string rather than the literal ``"None"`` or raising. This is + what lets templates write ``{{ inputs.last_fired_at | date }}`` + unconditionally on the first run.""" + assert filter_date(None) == "" + + +def test_filter_date_passes_strings_through_unchanged() -> None: + """Already-formatted ISO strings (the JSON-serialized shape of + runtime inputs like ``fired_at``) pass through unchanged so callers + don't have to special-case the type.""" + assert filter_date("2026-05-28T14:30:00+00:00") == "2026-05-28T14:30:00+00:00" diff --git a/surfsense_backend/tests/unit/automations/templating/test_render.py b/surfsense_backend/tests/unit/automations/templating/test_render.py new file mode 100644 index 000000000..42a7c7082 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/templating/test_render.py @@ -0,0 +1,59 @@ +"""Lock the public template-rendering surface: render, predicate, recursive.""" + +from __future__ import annotations + +import pytest +from jinja2 import UndefinedError + +from app.automations.templating.render import ( + evaluate_predicate, + render_template, + render_value, +) + +pytestmark = pytest.mark.unit + + +def test_render_template_substitutes_context_variables() -> None: + """A template referencing a context variable produces the substituted + string. Most basic contract of the template engine.""" + result = render_template("Hello {{ name }}!", {"name": "World"}) + + assert result == "Hello World!" + + +def test_render_template_raises_on_undefined_variable() -> None: + """Referencing a variable that isn't in the context raises rather than + rendering the empty string. Locks the StrictUndefined safety net so + template typos surface as run failures instead of silent corruption.""" + with pytest.raises(UndefinedError): + render_template("Hello {{ missing }}!", {}) + + +def test_evaluate_predicate_returns_truthy_outcome_of_expression() -> None: + """``evaluate_predicate`` compiles a Jinja **expression** (not template + body) and coerces the value to ``bool``. Drives ``step.when`` gating.""" + assert evaluate_predicate("inputs.count > 0", {"inputs": {"count": 3}}) is True + assert evaluate_predicate("inputs.count > 0", {"inputs": {"count": 0}}) is False + + +def test_render_value_renders_strings_recursively_through_dicts_and_lists() -> None: + """``render_value`` walks dicts and lists, renders string leaves through + the template engine, and leaves non-strings untouched. This is the + primitive ``execute_step`` uses to render step params at run time.""" + context = {"inputs": {"name": "World"}, "topic": "weekly"} + + rendered = render_value( + { + "greeting": "Hello {{ inputs.name }}", + "tags": ["{{ topic }}", "static"], + "config": {"retries": 3, "label": "{{ topic }}-{{ inputs.name }}"}, + }, + context, + ) + + assert rendered == { + "greeting": "Hello World", + "tags": ["weekly", "static"], + "config": {"retries": 3, "label": "weekly-World"}, + } From acbeb60a4325e1f1216594293363955d07a934f3 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 19:03:29 +0200 Subject: [PATCH 150/198] test(automations/actions): lock agent_task helpers (auto_decide + finalize) auto_decide.build_auto_decisions (3): produces one decision per action_request entry, defaults to one decision for legacy scalar interrupts, and skips malformed interrupts silently so a misbehaving tool can't take down the whole agent_task step. finalize.extract_final_assistant_message (4): string-content AIMessage returned verbatim, list-of-parts content concatenated (skipping non-text parts like tool_use), walks back past trailing ToolMessages to find the last AIMessage, and returns None when no extractable text is present (so callers can branch on silence vs. empty). 7 tests, pure unit. --- .../unit/automations/actions/__init__.py | 0 .../actions/agent_task/__init__.py | 0 .../actions/agent_task/test_auto_decide.py | 73 +++++++++++++++++ .../actions/agent_task/test_finalize.py | 80 +++++++++++++++++++ 4 files changed, 153 insertions(+) create mode 100644 surfsense_backend/tests/unit/automations/actions/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/actions/agent_task/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/actions/agent_task/test_auto_decide.py create mode 100644 surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py diff --git a/surfsense_backend/tests/unit/automations/actions/__init__.py b/surfsense_backend/tests/unit/automations/actions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/actions/agent_task/__init__.py b/surfsense_backend/tests/unit/automations/actions/agent_task/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/actions/agent_task/test_auto_decide.py b/surfsense_backend/tests/unit/automations/actions/agent_task/test_auto_decide.py new file mode 100644 index 000000000..439f32e41 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/actions/agent_task/test_auto_decide.py @@ -0,0 +1,73 @@ +"""Lock ``build_auto_decisions`` — the HITL auto-approve/reject wire mapper. + +``build_auto_decisions`` walks ``state.interrupts`` (duck-typed) and produces +two parallel resume maps: one keyed by LangGraph ``Interrupt.id`` and one +keyed by ``tool_call_id`` for the subagent middleware bridge. Both carry +the same decision payload. +""" + +from __future__ import annotations + +from types import SimpleNamespace +from typing import Any + +import pytest + +from app.automations.actions.agent_task.auto_decide import build_auto_decisions + +pytestmark = pytest.mark.unit + + +def _state(interrupts: list[Any]) -> SimpleNamespace: + """Build a duck-typed LangGraph state stub carrying ``interrupts``.""" + return SimpleNamespace(interrupts=interrupts) + + +def _interrupt(*, id_: str, value: Any) -> SimpleNamespace: + """Build a duck-typed interrupt with the canonical ``(id, value)`` shape.""" + return SimpleNamespace(id=id_, value=value) + + +def test_build_auto_decisions_produces_one_decision_per_action_request() -> None: + """An interrupt carrying N ``action_requests`` produces N decisions of + the requested type in both maps. This is the canonical batched-HITL + wire shape — losing a decision would leave a pending action stuck.""" + interrupt = _interrupt( + id_="lg-1", + value={ + "tool_call_id": "tc-1", + "action_requests": [{"id": "a"}, {"id": "b"}], + }, + ) + + lg_map, routed = build_auto_decisions(_state([interrupt]), "approve") + + assert lg_map == {"lg-1": {"decisions": [{"type": "approve"}, {"type": "approve"}]}} + assert routed == {"tc-1": {"decisions": [{"type": "approve"}, {"type": "approve"}]}} + + +def test_build_auto_decisions_defaults_to_one_decision_for_scalar_interrupt() -> None: + """When an interrupt's value has no ``action_requests`` list, the + function defaults to a single decision. Locks compatibility with + older single-action interrupt shapes still emitted by some tools.""" + interrupt = _interrupt(id_="lg-2", value={"tool_call_id": "tc-2"}) + + lg_map, routed = build_auto_decisions(_state([interrupt]), "reject") + + assert lg_map == {"lg-2": {"decisions": [{"type": "reject"}]}} + assert routed == {"tc-2": {"decisions": [{"type": "reject"}]}} + + +def test_build_auto_decisions_skips_interrupts_with_invalid_shape() -> None: + """Interrupts missing the canonical ``(str id, dict value)`` shape are + skipped silently rather than crashing the resume loop. Locks the + resilience contract — a malformed interrupt from a misbehaving tool + shouldn't take down the whole agent_task step.""" + good = _interrupt(id_="lg-good", value={"tool_call_id": "tc-good"}) + bad_value = _interrupt(id_="lg-bad-value", value="not a dict") + bad_id = _interrupt(id_=None, value={"tool_call_id": "tc-bad-id"}) # type: ignore[arg-type] + + lg_map, routed = build_auto_decisions(_state([good, bad_value, bad_id]), "approve") + + assert lg_map == {"lg-good": {"decisions": [{"type": "approve"}]}} + assert routed == {"tc-good": {"decisions": [{"type": "approve"}]}} diff --git a/surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py b/surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py new file mode 100644 index 000000000..bd49d764c --- /dev/null +++ b/surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py @@ -0,0 +1,80 @@ +"""Lock ``extract_final_assistant_message`` — what surfaces in run output. + +Each scenario is one shape the agent runtime is observed to produce. +Locking these means we can refactor the extractor without losing +backwards compatibility with already-stored ``run.output`` payloads. +""" + +from __future__ import annotations + +import pytest +from langchain_core.messages import AIMessage, HumanMessage, ToolMessage + +from app.automations.actions.agent_task.finalize import extract_final_assistant_message + +pytestmark = pytest.mark.unit + + +def test_extract_returns_last_ai_message_string_content() -> None: + """The canonical shape: the agent's final ``AIMessage`` carries a + plain string. That string is returned verbatim, trimmed.""" + result = { + "messages": [ + HumanMessage(content="ask"), + AIMessage(content="the answer"), + ] + } + + assert extract_final_assistant_message(result) == "the answer" + + +def test_extract_concatenates_text_parts_and_skips_non_text_parts() -> None: + """Multi-part AIMessage content (Anthropic / OpenAI list shape) joins + its ``text`` parts in order; non-text parts (tool_use, images, ...) + are skipped. Locks the wire shape used when the model emits tool + calls alongside narrative text in the same turn.""" + result = { + "messages": [ + AIMessage( + content=[ + {"type": "text", "text": "Hello "}, + {"type": "tool_use", "name": "search", "input": {}}, + {"type": "text", "text": "world"}, + ] + ) + ] + } + + assert extract_final_assistant_message(result) == "Hello world" + + +def test_extract_returns_last_ai_message_skipping_tool_messages() -> None: + """When the transcript ends with tool calls and tool results, the + extractor still walks back to the **last** ``AIMessage`` (the agent's + final narrative answer). Locks resilience against trailing + ``ToolMessage`` payloads in the transcript.""" + result = { + "messages": [ + HumanMessage(content="ask"), + AIMessage(content="thinking..."), + ToolMessage(content="tool output", tool_call_id="tc-1"), + AIMessage(content="final answer"), + ToolMessage(content="trailing tool noise", tool_call_id="tc-2"), + ] + } + + assert extract_final_assistant_message(result) == "final answer" + + +def test_extract_returns_none_when_no_assistant_text_is_present() -> None: + """No ``AIMessage`` with extractable text → ``None`` rather than the + empty string. Lets callers branch on "did the agent actually say + anything?" rather than guess whether ``""`` means silence or empty + output. Empty-string contents are normalized to ``None`` too.""" + no_ai = {"messages": [HumanMessage(content="just a question")]} + only_tools = {"messages": [AIMessage(content=[{"type": "tool_use", "name": "x", "input": {}}])]} + empty_string = {"messages": [AIMessage(content=" ")]} + + assert extract_final_assistant_message(no_ai) is None + assert extract_final_assistant_message(only_tools) is None + assert extract_final_assistant_message(empty_string) is None From 822940b09e4eddbcd903c5d480d156e84e11f402 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 19:03:42 +0200 Subject: [PATCH 151/198] test(automations/schemas): lock definition + api validation gates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit definition/ (29 tests): the envelope (defaults, extra=forbid, empty plan/name rejection), Inputs schema-alias roundtrip (Python schema_ ↔ wire schema), PlanStep numeric bounds + addressing-field constraints, Execution production defaults stability (10-min timeout, 2 retries, exponential backoff, drop_if_running) + closed-set Literal gates, Metadata's exceptional extra="allow" contract, and TriggerSpec type requirement. api/ (9 tests): AutomationCreate/Update cascade-validate into the nested definition, reject unknown payload fields, enforce name length; TriggerCreate exposes safe defaults (enabled=True, params={}, static_inputs={}) and rejects unknown TriggerType strings at the boundary. All pure unit, no DB. --- .../unit/automations/schemas/__init__.py | 0 .../unit/automations/schemas/api/__init__.py | 0 .../schemas/api/test_api_automation.py | 82 +++++++++++++++++++ .../schemas/api/test_api_trigger.py | 47 +++++++++++ .../schemas/definition/__init__.py | 0 .../schemas/definition/test_envelope.py | 57 +++++++++++++ .../schemas/definition/test_execution.py | 49 +++++++++++ .../schemas/definition/test_inputs.py | 39 +++++++++ .../schemas/definition/test_metadata.py | 37 +++++++++ .../schemas/definition/test_plan_step.py | 52 ++++++++++++ .../schemas/definition/test_trigger_spec.py | 33 ++++++++ 11 files changed, 396 insertions(+) create mode 100644 surfsense_backend/tests/unit/automations/schemas/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/api/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/api/test_api_automation.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/api/test_api_trigger.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/definition/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/definition/test_execution.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/definition/test_inputs.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/definition/test_metadata.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/definition/test_plan_step.py create mode 100644 surfsense_backend/tests/unit/automations/schemas/definition/test_trigger_spec.py diff --git a/surfsense_backend/tests/unit/automations/schemas/__init__.py b/surfsense_backend/tests/unit/automations/schemas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/schemas/api/__init__.py b/surfsense_backend/tests/unit/automations/schemas/api/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/schemas/api/test_api_automation.py b/surfsense_backend/tests/unit/automations/schemas/api/test_api_automation.py new file mode 100644 index 000000000..6ae3ce794 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/api/test_api_automation.py @@ -0,0 +1,82 @@ +"""Lock the request-side automation API schemas — the public validation gate.""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.schemas.api.automation import AutomationCreate, AutomationUpdate + +pytestmark = pytest.mark.unit + + +_VALID_DEFINITION = { + "name": "Test", + "plan": [{"step_id": "s1", "action": "agent_task"}], +} + + +def test_automation_create_accepts_valid_minimal_payload() -> None: + """Happy path: just search_space_id, name, and a valid definition. + Triggers default to ``[]`` so users can attach them later.""" + payload = AutomationCreate.model_validate( + { + "search_space_id": 1, + "name": "Daily digest", + "definition": _VALID_DEFINITION, + } + ) + + assert payload.name == "Daily digest" + assert payload.description is None + assert payload.triggers == [] + + +def test_automation_create_cascades_validation_into_nested_definition() -> None: + """A bad ``definition`` (e.g. empty plan) fails at the API boundary, + not at the DB layer. Locks the cascade so corrupt definitions can't + sneak through a misshapen wire payload.""" + with pytest.raises(ValidationError): + AutomationCreate.model_validate( + { + "search_space_id": 1, + "name": "Bad", + "definition": {"name": "X", "plan": []}, # empty plan + } + ) + + +def test_automation_create_rejects_unknown_top_level_field() -> None: + """``extra='forbid'`` catches typos in API payloads at the boundary.""" + with pytest.raises(ValidationError): + AutomationCreate.model_validate( + { + "search_space_id": 1, + "name": "X", + "definition": _VALID_DEFINITION, + "owner": "tg", # not allowed + } + ) + + +def test_automation_create_rejects_empty_name() -> None: + """Name is required and constrained to 1..200 chars.""" + with pytest.raises(ValidationError): + AutomationCreate.model_validate( + { + "search_space_id": 1, + "name": "", + "definition": _VALID_DEFINITION, + } + ) + + +def test_automation_update_accepts_partial_payload_with_no_fields() -> None: + """All fields on ``AutomationUpdate`` are optional. An empty body is + a valid no-op update (the service layer decides what to do with it).""" + update = AutomationUpdate.model_validate({}) + + assert update.name is None + assert update.description is None + assert update.status is None + assert update.definition is None diff --git a/surfsense_backend/tests/unit/automations/schemas/api/test_api_trigger.py b/surfsense_backend/tests/unit/automations/schemas/api/test_api_trigger.py new file mode 100644 index 000000000..cabfc41af --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/api/test_api_trigger.py @@ -0,0 +1,47 @@ +"""Lock the request-side trigger API schemas.""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.persistence.enums.trigger_type import TriggerType +from app.automations.schemas.api.trigger import TriggerCreate, TriggerUpdate + +pytestmark = pytest.mark.unit + + +def test_trigger_create_uses_safe_defaults_for_optional_fields() -> None: + """Defaults: empty ``params`` and ``static_inputs``, ``enabled=True``. + These let callers create a trigger with just ``type`` + the params + the trigger requires.""" + trigger = TriggerCreate(type=TriggerType.SCHEDULE) # type: ignore[arg-type] + + assert trigger.type is TriggerType.SCHEDULE + assert trigger.params == {} + assert trigger.static_inputs == {} + assert trigger.enabled is True + + +def test_trigger_create_rejects_unknown_trigger_type_string() -> None: + """``type`` is a ``TriggerType`` enum, so any string outside the + enum's known values fails validation at the boundary.""" + with pytest.raises(ValidationError): + TriggerCreate.model_validate({"type": "webhook"}) # not in TriggerType + + +def test_trigger_create_rejects_unknown_field() -> None: + """``extra='forbid'`` catches typos in trigger payloads.""" + with pytest.raises(ValidationError): + TriggerCreate.model_validate( + {"type": "schedule", "param": {}} # typo: param vs params + ) + + +def test_trigger_update_accepts_partial_payload_with_no_fields() -> None: + """``TriggerUpdate`` is fully optional — empty body is valid (no-op).""" + update = TriggerUpdate() + + assert update.enabled is None + assert update.params is None + assert update.static_inputs is None diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/__init__.py b/surfsense_backend/tests/unit/automations/schemas/definition/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py new file mode 100644 index 000000000..c625b0ec9 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py @@ -0,0 +1,57 @@ +"""Lock the ``AutomationDefinition`` envelope contract.""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.schemas.definition.plan_step import PlanStep + +pytestmark = pytest.mark.unit + + +def test_automation_definition_accepts_minimal_valid_input_with_sensible_defaults() -> None: + """A definition with just ``name`` + a one-step ``plan`` is valid and + fills in the rest with safe defaults so users don't have to write + out every section to get started.""" + definition = AutomationDefinition( + name="Daily digest", + plan=[PlanStep(step_id="s1", action="agent_task")], + ) + + assert definition.name == "Daily digest" + assert definition.schema_version == "1.0" + assert definition.goal is None + assert definition.inputs is None + assert definition.triggers == [] + + +def test_automation_definition_rejects_unknown_top_level_field() -> None: + """``extra='forbid'`` catches typos at validation time (e.g. ``pln`` + instead of ``plan``) before the bad definition reaches storage.""" + with pytest.raises(ValidationError): + AutomationDefinition.model_validate( + { + "name": "X", + "plan": [{"step_id": "s1", "action": "agent_task"}], + "extra_field": "unexpected", + } + ) + + +def test_automation_definition_rejects_empty_plan() -> None: + """An automation with no plan steps has nothing to execute and must + be rejected at validation time.""" + with pytest.raises(ValidationError): + AutomationDefinition(name="X", plan=[]) + + +def test_automation_definition_rejects_empty_name() -> None: + """Name is required and must be non-empty so list views and audit + logs have something meaningful to display.""" + with pytest.raises(ValidationError): + AutomationDefinition( + name="", + plan=[PlanStep(step_id="s1", action="agent_task")], + ) diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_execution.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_execution.py new file mode 100644 index 000000000..15adefab0 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_execution.py @@ -0,0 +1,49 @@ +"""Lock the ``Execution`` defaults + literal-constraint contract. + +These defaults control production behavior of every automation that +doesn't override them; the defaults *are* the contract. +""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.schemas.definition.execution import Execution + +pytestmark = pytest.mark.unit + + +def test_execution_uses_production_defaults_when_no_overrides_provided() -> None: + """The defaults shipped to prod: 10-minute wall clock, 2 retries + per step, exponential backoff, drop overlapping runs. Changing any + of these is a behavioral release-note change.""" + execution = Execution() + + assert execution.timeout_seconds == 600 + assert execution.max_retries == 2 + assert execution.retry_backoff == "exponential" + assert execution.concurrency == "drop_if_running" + assert execution.on_failure == [] + + +def test_execution_rejects_unknown_retry_backoff_strategy() -> None: + """``retry_backoff`` is constrained to a closed set — typos like + ``"expontential"`` must fail validation, not silently coerce.""" + with pytest.raises(ValidationError): + Execution(retry_backoff="expontential") # type: ignore[arg-type] + + +def test_execution_rejects_unknown_concurrency_strategy() -> None: + """Same closed-set constraint on ``concurrency``.""" + with pytest.raises(ValidationError): + Execution(concurrency="parallel") # type: ignore[arg-type] + + +def test_execution_rejects_invalid_numeric_bounds() -> None: + """``timeout_seconds > 0`` and ``max_retries >= 0``. Zero or negative + values would produce nonsensical run behavior.""" + with pytest.raises(ValidationError): + Execution(timeout_seconds=0) + with pytest.raises(ValidationError): + Execution(max_retries=-1) diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_inputs.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_inputs.py new file mode 100644 index 000000000..5dc24463f --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_inputs.py @@ -0,0 +1,39 @@ +"""Lock the ``Inputs`` JSON ``schema``-alias roundtrip. + +The field is ``schema_`` in Python (``schema`` shadows a Pydantic builtin) +but is wire-named ``schema``. Locking the roundtrip means JSON definitions +authored anywhere (UI raw editor, NL drafter, CLI export) speak the same +wire shape. +""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.schemas.definition.inputs import Inputs + +pytestmark = pytest.mark.unit + + +def test_inputs_parses_wire_field_named_schema_into_schema_attribute() -> None: + """JSON payloads use ``schema`` (the convention). The model stores it + on the Python attribute ``schema_`` without shadowing the builtin.""" + parsed = Inputs.model_validate({"schema": {"type": "object"}}) + + assert parsed.schema_ == {"type": "object"} + + +def test_inputs_serializes_schema_attribute_back_to_wire_field_named_schema() -> None: + """Round-trip: serializing emits ``schema`` (alias), not ``schema_``. + Locks the consumer-visible JSON shape regardless of the Python name.""" + inputs = Inputs(schema={"type": "object"}) # type: ignore[call-arg] + + assert inputs.model_dump() == {"schema": {"type": "object"}} + + +def test_inputs_rejects_unknown_field() -> None: + """``extra='forbid'`` catches typos like ``shema`` so bad definitions + don't silently lose their input declaration.""" + with pytest.raises(ValidationError): + Inputs.model_validate({"schema": {}, "extra": "x"}) diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_metadata.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_metadata.py new file mode 100644 index 000000000..9ac90bb3f --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_metadata.py @@ -0,0 +1,37 @@ +"""Lock the ``Metadata`` ``extra='allow'`` contract — the only schema +that does. Free-form annotations on definitions (e.g. ``owner``, +``project``, ``created_by_ai``) need to round-trip through the envelope +without being rejected. +""" + +from __future__ import annotations + +import pytest + +from app.automations.schemas.definition.metadata import Metadata + +pytestmark = pytest.mark.unit + + +def test_metadata_preserves_unknown_keys() -> None: + """Unlike every other definition sub-schema, ``Metadata`` allows + extra keys and round-trips them — that's its purpose.""" + metadata = Metadata.model_validate( + { + "tags": ["weekly", "report"], + "owner": "tg", + "created_by_ai": True, + } + ) + + dumped = metadata.model_dump() + + assert dumped["tags"] == ["weekly", "report"] + assert dumped["owner"] == "tg" + assert dumped["created_by_ai"] is True + + +def test_metadata_defaults_tags_to_empty_list() -> None: + """No tags is the common case; the default is the empty list so + callers can append without a None check.""" + assert Metadata().tags == [] diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_plan_step.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_plan_step.py new file mode 100644 index 000000000..6896a7f5a --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_plan_step.py @@ -0,0 +1,52 @@ +"""Lock the ``PlanStep`` validation contract.""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.schemas.definition.plan_step import PlanStep + +pytestmark = pytest.mark.unit + + +def test_plan_step_accepts_minimal_input_with_safe_defaults() -> None: + """A step with just ``step_id`` + ``action`` is valid. Defaults + (no when, empty params, no output_as override, no retry/timeout + override) let the run inherit automation-wide defaults.""" + step = PlanStep(step_id="s1", action="agent_task") + + assert step.step_id == "s1" + assert step.action == "agent_task" + assert step.when is None + assert step.params == {} + assert step.output_as is None + assert step.max_retries is None + assert step.timeout_seconds is None + + +def test_plan_step_rejects_empty_step_id_and_action() -> None: + """``step_id`` and ``action`` are addressing primitives — empty + strings would silently break runtime lookups.""" + with pytest.raises(ValidationError): + PlanStep(step_id="", action="agent_task") + with pytest.raises(ValidationError): + PlanStep(step_id="s1", action="") + + +def test_plan_step_rejects_negative_max_retries_and_non_positive_timeout() -> None: + """Numeric constraints: ``max_retries >= 0`` and ``timeout_seconds > 0``. + Negative budgets or zero timeouts produce nonsensical run behavior.""" + with pytest.raises(ValidationError): + PlanStep(step_id="s1", action="agent_task", max_retries=-1) + with pytest.raises(ValidationError): + PlanStep(step_id="s1", action="agent_task", timeout_seconds=0) + + +def test_plan_step_rejects_unknown_field() -> None: + """``extra='forbid'`` catches typos like ``actoin`` (instead of + ``action``) before the bad step reaches storage.""" + with pytest.raises(ValidationError): + PlanStep.model_validate( + {"step_id": "s1", "action": "agent_task", "actoin": "agent_task"} + ) diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_trigger_spec.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_trigger_spec.py new file mode 100644 index 000000000..cf1a52466 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_trigger_spec.py @@ -0,0 +1,33 @@ +"""Lock the ``TriggerSpec`` validation contract — the entry shape used +inside an automation's ``triggers[]`` array. +""" + +from __future__ import annotations + +import pytest +from pydantic import ValidationError + +from app.automations.schemas.definition.trigger_spec import TriggerSpec + +pytestmark = pytest.mark.unit + + +def test_trigger_spec_accepts_type_with_default_empty_params() -> None: + """``type`` is required; ``params`` defaults to ``{}`` so triggers + that take no params don't need an explicit body.""" + spec = TriggerSpec(type="schedule") + + assert spec.type == "schedule" + assert spec.params == {} + + +def test_trigger_spec_rejects_empty_type() -> None: + """``type`` is the registry lookup key — empty would silently miss.""" + with pytest.raises(ValidationError): + TriggerSpec(type="") + + +def test_trigger_spec_rejects_unknown_field() -> None: + """``extra='forbid'`` catches typos at definition-validation time.""" + with pytest.raises(ValidationError): + TriggerSpec.model_validate({"type": "schedule", "paramz": {}}) From 353755fd73fc6320758ac4c3962ed115e01a86ea Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 19:03:55 +0200 Subject: [PATCH 152/198] test(automations): cross-cutting registries, enums, side-effects + shared fixtures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Top-level tests that span multiple submodules: - test_stores.py (7): the trigger + action registry contracts — register round-trip, unknown type → None (not raise), duplicate registration rejected, defensive snapshot from all_*. - test_definition_types.py (2): params_schema property on both ActionDefinition and TriggerDefinition reflects the Pydantic model. - test_persistence_enums.py (3): exact string values + member sets of AutomationStatus / RunStatus / TriggerType — the postgres-mirrored contract that breaks stored rows if drifted. - test_import_registrations.py (2): the bundled agent_task action and schedule trigger self-register on package import (canary for the side-effect import chain). conftest.py adds isolated_action_registry / isolated_trigger_registry fixtures: snapshot + restore of the module-level _REGISTRY dicts so tests that add their own definitions don't leak across the suite. 14 tests, pure unit. --- .../tests/unit/automations/__init__.py | 0 .../tests/unit/automations/conftest.py | 39 ++++++ .../unit/automations/test_definition_types.py | 56 +++++++++ .../automations/test_import_registrations.py | 37 ++++++ .../automations/test_persistence_enums.py | 45 +++++++ .../tests/unit/automations/test_stores.py | 115 ++++++++++++++++++ 6 files changed, 292 insertions(+) create mode 100644 surfsense_backend/tests/unit/automations/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/conftest.py create mode 100644 surfsense_backend/tests/unit/automations/test_definition_types.py create mode 100644 surfsense_backend/tests/unit/automations/test_import_registrations.py create mode 100644 surfsense_backend/tests/unit/automations/test_persistence_enums.py create mode 100644 surfsense_backend/tests/unit/automations/test_stores.py diff --git a/surfsense_backend/tests/unit/automations/__init__.py b/surfsense_backend/tests/unit/automations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/conftest.py b/surfsense_backend/tests/unit/automations/conftest.py new file mode 100644 index 000000000..0fbf03234 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/conftest.py @@ -0,0 +1,39 @@ +"""Shared fixtures for the ``app.automations`` unit-test tree. + +Provides registry isolation: the built-in ``schedule`` trigger and +``agent_task`` action self-register at import time. Tests that register +additional triggers/actions (or assert on the registry contents) must +not leak that state to other tests. These fixtures snapshot and restore +the module-level registry dicts. +""" + +from __future__ import annotations + +from collections.abc import Iterator + +import pytest + +from app.automations.actions import store as action_store +from app.automations.triggers import store as trigger_store + + +@pytest.fixture +def isolated_action_registry() -> Iterator[None]: + """Snapshot and restore the action registry around a test.""" + snapshot = dict(action_store._REGISTRY) + try: + yield + finally: + action_store._REGISTRY.clear() + action_store._REGISTRY.update(snapshot) + + +@pytest.fixture +def isolated_trigger_registry() -> Iterator[None]: + """Snapshot and restore the trigger registry around a test.""" + snapshot = dict(trigger_store._REGISTRY) + try: + yield + finally: + trigger_store._REGISTRY.clear() + trigger_store._REGISTRY.update(snapshot) diff --git a/surfsense_backend/tests/unit/automations/test_definition_types.py b/surfsense_backend/tests/unit/automations/test_definition_types.py new file mode 100644 index 000000000..231e4fa97 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/test_definition_types.py @@ -0,0 +1,56 @@ +"""Lock the ``params_schema`` derivation on action + trigger definitions. + +Both definition dataclasses expose ``params_schema`` as the JSON Schema +of their ``params_model``. This is what the registry endpoints surface +to the UI as the "what shape do these params take?" contract. +""" + +from __future__ import annotations + +import pytest +from pydantic import BaseModel + +from app.automations.actions.types import ActionDefinition +from app.automations.triggers.types import TriggerDefinition + +pytestmark = pytest.mark.unit + + +class _Topic(BaseModel): + """Model with one required string field — minimal schema fingerprint.""" + + topic: str + + +def test_action_definition_params_schema_reflects_params_model() -> None: + """``ActionDefinition.params_schema`` returns a JSON Schema derived + from the Pydantic ``params_model`` — required fields and types are + visible to clients consuming the registry endpoint.""" + definition = ActionDefinition( + type="t", + name="N", + description="D", + params_model=_Topic, + build_handler=lambda _ctx: (lambda _p: {}), # type: ignore[arg-type,return-value] + ) + + schema = definition.params_schema + + assert schema["type"] == "object" + assert schema["properties"]["topic"]["type"] == "string" + assert "topic" in schema["required"] + + +def test_trigger_definition_params_schema_reflects_params_model() -> None: + """Same JSON-Schema derivation contract on the trigger side.""" + definition = TriggerDefinition( + type="t", + description="D", + params_model=_Topic, + ) + + schema = definition.params_schema + + assert schema["type"] == "object" + assert schema["properties"]["topic"]["type"] == "string" + assert "topic" in schema["required"] diff --git a/surfsense_backend/tests/unit/automations/test_import_registrations.py b/surfsense_backend/tests/unit/automations/test_import_registrations.py new file mode 100644 index 000000000..35b1effa7 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/test_import_registrations.py @@ -0,0 +1,37 @@ +"""Lock the bundled import side-effects. + +Importing ``app.automations`` (the package) registers the v1 bundled +action (``agent_task``) and the v1 bundled trigger (``schedule``). If the +import chain breaks (e.g. someone removes ``from . import definition`` +in a sub-package ``__init__``), the system would silently launch with an +empty registry. These tests are the canary. +""" + +from __future__ import annotations + +import pytest + +import app.automations # noqa: F401 (force the package import + its side-effects) +from app.automations.actions.store import get_action +from app.automations.persistence.enums.trigger_type import TriggerType +from app.automations.triggers.store import get_trigger + +pytestmark = pytest.mark.unit + + +def test_bundled_agent_task_action_is_registered_after_package_import() -> None: + """``agent_task`` — the v1 default action — must be discoverable in + the registry after the package is imported.""" + definition = get_action("agent_task") + + assert definition is not None + assert definition.type == "agent_task" + + +def test_bundled_schedule_trigger_is_registered_after_package_import() -> None: + """``schedule`` — the only v1 trigger — must be discoverable in the + registry after the package is imported.""" + definition = get_trigger(TriggerType.SCHEDULE.value) + + assert definition is not None + assert definition.type == TriggerType.SCHEDULE.value diff --git a/surfsense_backend/tests/unit/automations/test_persistence_enums.py b/surfsense_backend/tests/unit/automations/test_persistence_enums.py new file mode 100644 index 000000000..59703dfc6 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/test_persistence_enums.py @@ -0,0 +1,45 @@ +"""Lock the persistence enum string values + members. + +These enums are mirrored by Postgres enum types, embedded in stored DB +rows, and surfaced in the JSON API. Renaming a value (or removing a +member) silently breaks production data and previously-issued API +responses, so the strings + the set of members are the contract. +""" + +from __future__ import annotations + +import pytest + +from app.automations.persistence.enums.automation_status import AutomationStatus +from app.automations.persistence.enums.run_status import RunStatus +from app.automations.persistence.enums.trigger_type import TriggerType + +pytestmark = pytest.mark.unit + + +def test_automation_status_string_values_are_stable() -> None: + """The exact strings persisted to Postgres and served in API JSON.""" + assert {member.value for member in AutomationStatus} == { + "active", + "paused", + "archived", + } + + +def test_run_status_string_values_are_stable() -> None: + """Run lifecycle states embedded in the ``automation_runs`` table.""" + assert {member.value for member in RunStatus} == { + "pending", + "running", + "succeeded", + "failed", + "cancelled", + "timed_out", + } + + +def test_trigger_type_keeps_manual_member_even_though_unregistered() -> None: + """``MANUAL`` is reserved (mirrors the Postgres enum) but the trigger + store does not register it in v1. The enum must keep both members so + existing DB rows and the schema migration plan stay valid.""" + assert {member.value for member in TriggerType} == {"schedule", "manual"} diff --git a/surfsense_backend/tests/unit/automations/test_stores.py b/surfsense_backend/tests/unit/automations/test_stores.py new file mode 100644 index 000000000..e54062d64 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/test_stores.py @@ -0,0 +1,115 @@ +"""Lock the trigger + action registry contracts. + +Both stores share the same API shape (register/get/all + duplicate-raise), +so they're tested together to keep the contract visible side-by-side. +""" + +from __future__ import annotations + +import pytest +from pydantic import BaseModel + +from app.automations.actions.store import ( + get_action, + register_action, +) +from app.automations.actions.types import ActionDefinition +from app.automations.triggers.store import ( + all_triggers, + get_trigger, + register_trigger, +) +from app.automations.triggers.types import TriggerDefinition + +pytestmark = pytest.mark.unit + + +class _Params(BaseModel): + """Empty params model used by test-only registrations.""" + + +def _trigger(type_: str = "test_trigger") -> TriggerDefinition: + return TriggerDefinition(type=type_, description="Test trigger.", params_model=_Params) + + +def _action(type_: str = "test_action") -> ActionDefinition: + return ActionDefinition( + type=type_, + name="Test", + description="Test action.", + params_model=_Params, + build_handler=lambda _ctx: (lambda _p: {}), # type: ignore[arg-type,return-value] + ) + + +def test_register_trigger_then_get_trigger_returns_the_same_definition( + isolated_trigger_registry: None, +) -> None: + """The canonical round-trip: register, look up by type, get the same + definition back. Locks the basic registry contract.""" + definition = _trigger() + register_trigger(definition) + + assert get_trigger("test_trigger") is definition + + +def test_register_action_then_get_action_returns_the_same_definition( + isolated_action_registry: None, +) -> None: + """Same round-trip contract for the action registry.""" + definition = _action() + register_action(definition) + + assert get_action("test_action") is definition + + +def test_get_trigger_returns_none_for_unknown_type( + isolated_trigger_registry: None, +) -> None: + """An unknown type returns ``None`` (not raises). Lets callers like + the dispatcher branch on "is this trigger still registered?" without + try/except.""" + assert get_trigger("never_registered") is None + + +def test_get_action_returns_none_for_unknown_type( + isolated_action_registry: None, +) -> None: + """Same ``None``-not-raise contract on the action side.""" + assert get_action("never_registered") is None + + +def test_register_trigger_rejects_duplicate_type( + isolated_trigger_registry: None, +) -> None: + """Re-registering the same ``type`` raises rather than silently + overwriting. Locks the safety net against accidental double-import + (e.g., circular imports re-running the registration block).""" + register_trigger(_trigger()) + + with pytest.raises(ValueError, match="test_trigger"): + register_trigger(_trigger()) + + +def test_register_action_rejects_duplicate_type( + isolated_action_registry: None, +) -> None: + """Same duplicate-rejection contract on the action side.""" + register_action(_action()) + + with pytest.raises(ValueError, match="test_action"): + register_action(_action()) + + +def test_all_triggers_returns_defensive_snapshot( + isolated_trigger_registry: None, +) -> None: + """``all_triggers()`` returns a copy: mutating the returned dict does + not corrupt the internal registry. Locks the snapshot contract that + UI/listing endpoints rely on.""" + register_trigger(_trigger("snapshot_test")) + + snapshot = all_triggers() + snapshot.pop("snapshot_test") + + assert get_trigger("snapshot_test") is not None \ No newline at end of file From 958bf9f95ad0bf32450b0aa86bc99218c683aad3 Mon Sep 17 00:00:00 2001 From: CREDO23 <bakerathierry@gmail.com> Date: Thu, 28 May 2026 21:10:24 +0200 Subject: [PATCH 153/198] fix(automations/agent_task): use in-memory checkpointer to avoid Celery PoolTimeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared AsyncPostgresSaver caches DB connections in a module-level pool. Cached connections are bound to the asyncio loop that opened them, but `run_async_celery_task` discards the loop on each task's exit — so after the first task the pool holds connections pointing to a dead loop, and the next automation hangs 30s before failing with `PoolTimeout: couldn't get a connection after 30.00 sec`. Swap agent_task to `InMemorySaver`; automation runs only need state within one Celery task, so nothing is lost. Site-local TODO tracks the proper future fix (dispose the checkpointer pool around each Celery task, mirroring `_dispose_shared_db_engine`). --- .../actions/agent_task/dependencies.py | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/surfsense_backend/app/automations/actions/agent_task/dependencies.py b/surfsense_backend/app/automations/actions/agent_task/dependencies.py index 12273aa0f..79107cd65 100644 --- a/surfsense_backend/app/automations/actions/agent_task/dependencies.py +++ b/surfsense_backend/app/automations/actions/agent_task/dependencies.py @@ -5,17 +5,17 @@ from __future__ import annotations from dataclasses import dataclass from typing import Any +from langgraph.checkpoint.memory import InMemorySaver from sqlalchemy.ext.asyncio import AsyncSession from app.tasks.chat.streaming.flows.shared.llm_bundle import load_llm_bundle from app.tasks.chat.streaming.flows.shared.pre_stream_setup import ( - get_chat_checkpointer, setup_connector_and_firecrawl, ) class DependencyError(Exception): - """An external dependency (LLM config, checkpointer, ...) refused to load.""" + """An external dependency (LLM config, connector service, ...) refused to load.""" @dataclass(frozen=True, slots=True) @@ -34,7 +34,7 @@ async def build_dependencies( session: AsyncSession, search_space_id: int, ) -> AgentDependencies: - """Load the LLM bundle, connector service, and checkpointer for one invoke. + """Load the LLM bundle, connector service, and a per-invoke in-memory checkpointer. Uses the search space's default LLM config (``config_id=-1``). Per-step model overrides land in a future iteration alongside the ``model`` param. @@ -48,7 +48,24 @@ async def build_dependencies( connector_service, firecrawl_api_key = await setup_connector_and_firecrawl( session, search_space_id=search_space_id ) - checkpointer = await get_chat_checkpointer() + # Quick fix: use an in-memory checkpointer for automation runs. + # + # The shared Postgres checkpointer caches DB connections in a + # module-level pool. Each cached connection is bound to the asyncio + # loop that opened it. Celery throws away the loop after every task, + # so the pool ends up full of connections pointing to a dead loop, + # and the next Celery task (running on a fresh loop) can't use any + # of them — it hangs 30s and fails with + # `PoolTimeout: couldn't get a connection after 30.00 sec`. + # + # InMemorySaver has no cached connections, no loop binding — each + # Celery task creates one and drops it on exit. + # + # TODO(checkpointer): proper fix is to dispose the checkpointer + # pool around each Celery task in `run_async_celery_task`, the same + # way `_dispose_shared_db_engine` already does for the SQLAlchemy + # pool. Then this site can switch back to the shared checkpointer. + checkpointer = InMemorySaver() return AgentDependencies( llm=llm, agent_config=agent_config, From 94e834134f7269260095b1c7d1763bcf4fbd582b Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" <vermarohanfinal@gmail.com> Date: Thu, 28 May 2026 19:21:29 -0700 Subject: [PATCH 154/198] chore: linting --- .../versions/144_add_automation_tables.py | 4 +- .../main_agent/tools/automation/prompt.py | 1 - .../task_tool.py | 4 +- .../builtins/deliverables/tools/podcast.py | 4 +- .../deliverables/tools/video_presentation.py | 7 +- .../app/agents/new_chat/tools/podcast.py | 8 +- .../new_chat/tools/video_presentation.py | 4 +- .../app/automations/actions/__init__.py | 2 +- .../actions/agent_task/__init__.py | 2 +- .../automations/actions/agent_task/invoke.py | 1 - .../app/automations/persistence/models/run.py | 2 +- .../app/automations/runtime/executor.py | 11 +- .../app/automations/runtime/step.py | 21 ++- .../schemas/definition/execution.py | 4 +- .../schemas/definition/plan_step.py | 4 +- .../schemas/definition/trigger_spec.py | 4 +- .../app/automations/services/automation.py | 42 +++-- .../app/automations/services/run.py | 12 +- .../app/automations/services/trigger.py | 14 +- .../app/automations/tasks/execute_run.py | 2 +- .../app/automations/tasks/schedule_tick.py | 4 +- .../app/automations/triggers/__init__.py | 2 +- .../automations/triggers/schedule/__init__.py | 2 +- .../app/automations/triggers/schedule/cron.py | 6 +- .../automations/triggers/schedule/params.py | 4 +- surfsense_backend/app/db.py | 1 - surfsense_backend/app/routes/__init__.py | 3 +- .../streaming/context/deepagents_todos.py | 4 +- .../chat/streaming/flows/new_chat/auto_pin.py | 8 +- .../streaming/flows/new_chat/input_state.py | 4 +- .../flows/new_chat/llm_capability.py | 4 +- .../streaming/flows/new_chat/orchestrator.py | 27 ++-- .../flows/new_chat/runtime_context.py | 4 +- .../streaming/flows/new_chat/title_gen.py | 8 +- .../flows/resume_chat/orchestrator.py | 37 ++--- .../flows/resume_chat/resume_routing.py | 4 +- .../flows/shared/assistant_finalize.py | 4 +- .../streaming/flows/shared/finally_cleanup.py | 4 +- .../streaming/flows/shared/premium_quota.py | 10 +- .../tasks/chat/streaming/flows/shared/span.py | 3 +- .../streaming/flows/shared/terminal_error.py | 3 +- .../actions/agent_task/test_finalize.py | 6 +- .../unit/automations/runtime/test_retries.py | 4 +- .../schemas/definition/test_envelope.py | 4 +- .../templating/test_environment.py | 4 +- .../unit/automations/test_definition_types.py | 2 +- .../tests/unit/automations/test_stores.py | 8 +- .../triggers/schedule/test_cron.py | 8 +- .../test_parallel_refactor_parity.py | 20 ++- surfsense_web/app/(home)/free/page.tsx | 10 +- surfsense_web/app/(home)/privacy/page.tsx | 98 ++++++------ surfsense_web/app/api/zero/query/route.ts | 2 +- .../edit/automation-edit-content.tsx | 5 +- .../edit/components/automation-edit-form.tsx | 5 +- .../new-chat/[[...chat_id]]/page.tsx | 11 +- .../[search_space_id]/team/team-content.tsx | 2 +- .../atoms/members/members-query.atoms.ts | 8 +- .../assistant-ui/assistant-message.tsx | 2 +- .../components/obsidian-connect-form.tsx | 3 +- .../components/circleback-config.tsx | 2 +- .../views/connector-edit-view.tsx | 1 + .../views/connector-accounts-list-view.tsx | 1 + .../assistant-ui/inline-mention-editor.tsx | 20 ++- .../components/assistant-ui/thread.tsx | 89 ++++++----- .../components/assistant-ui/user-message.tsx | 8 +- .../components/editor-panel/editor-panel.tsx | 1 + .../components/free-chat/anonymous-chat.tsx | 26 ++-- .../layout/ui/icon-rail/SearchSpaceAvatar.tsx | 6 +- .../layout/ui/sidebar/DocumentsSidebar.tsx | 2 +- .../layout/ui/tabs/DocumentTabContent.tsx | 1 + .../new-chat/composer-suggestion-popup.tsx | 5 +- .../new-chat/document-mention-picker.tsx | 144 ++++++++++-------- .../components/new-chat/prompt-picker.tsx | 4 +- .../settings/general-settings-manager.tsx | 2 +- .../settings/prompt-config-manager.tsx | 8 +- .../components/tool-ui/generate-podcast.tsx | 9 +- .../generate-video-presentation.tsx | 2 +- .../hooks/use-search-source-connectors.ts | 12 +- surfsense_web/lib/auth-utils.ts | 1 + surfsense_web/lib/posthog/events.ts | 2 +- 80 files changed, 443 insertions(+), 404 deletions(-) diff --git a/surfsense_backend/alembic/versions/144_add_automation_tables.py b/surfsense_backend/alembic/versions/144_add_automation_tables.py index 8d836095d..39f927417 100644 --- a/surfsense_backend/alembic/versions/144_add_automation_tables.py +++ b/surfsense_backend/alembic/versions/144_add_automation_tables.py @@ -98,9 +98,7 @@ def upgrade() -> None: op.execute( "CREATE INDEX ix_automation_triggers_automation_id ON automation_triggers(automation_id);" ) - op.execute( - "CREATE INDEX ix_automation_triggers_type ON automation_triggers(type);" - ) + op.execute("CREATE INDEX ix_automation_triggers_type ON automation_triggers(type);") op.execute( "CREATE INDEX ix_automation_triggers_enabled ON automation_triggers(enabled);" ) diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py index 45870e768..09854aa2e 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/prompt.py @@ -28,7 +28,6 @@ from __future__ import annotations from datetime import UTC, datetime - _HEADER = """\ You are the SurfSense automation drafter. Convert the user intent below into a SINGLE JSON object matching the AutomationCreate schema. Output diff --git a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py index 91a0be506..eaed9a55f 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py +++ b/surfsense_backend/app/agents/multi_agent_chat/middleware/main_agent/checkpointed_subagent_middleware/task_tool.py @@ -404,9 +404,7 @@ def build_task_tool_with_parent_config( continue messages = payload.get("messages") or [] last_text = _safe_message_text(messages[-1]).rstrip() if messages else "" - message_blocks.append( - f"[task {task_index}] {last_text or '<empty>'}" - ) + message_blocks.append(f"[task {task_index}] {last_text or '<empty>'}") try: child_trace = _build_tool_trace(messages) except Exception: diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py index 84617d38b..298257799 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/podcast.py @@ -117,9 +117,7 @@ def create_generate_podcast_tool( "podcast_id": podcast_id, "title": podcast_title, "file_location": file_location, - "message": ( - "Podcast generated and saved to your podcast panel." - ), + "message": ("Podcast generated and saved to your podcast panel."), } return with_receipt( payload=payload, diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py index 8c52293de..5407c8834 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/video_presentation.py @@ -126,8 +126,7 @@ def create_generate_video_presentation_tool( elapsed, ) err = ( - "Background worker reported FAILED status for this " - "video presentation." + "Background worker reported FAILED status for this video presentation." ) payload = { "status": VideoPresentationStatus.FAILED.value, @@ -151,9 +150,7 @@ def create_generate_video_presentation_tool( except Exception as e: error_message = str(e) - logger.exception( - "[generate_video_presentation] Error: %s", error_message - ) + logger.exception("[generate_video_presentation] Error: %s", error_message) payload = { "status": VideoPresentationStatus.FAILED.value, "error": error_message, diff --git a/surfsense_backend/app/agents/new_chat/tools/podcast.py b/surfsense_backend/app/agents/new_chat/tools/podcast.py index 36aecfe49..83ac98768 100644 --- a/surfsense_backend/app/agents/new_chat/tools/podcast.py +++ b/surfsense_backend/app/agents/new_chat/tools/podcast.py @@ -131,9 +131,7 @@ def create_generate_podcast_tool( "podcast_id": podcast_id, "title": podcast_title, "file_location": file_location, - "message": ( - "Podcast generated and saved to your podcast panel." - ), + "message": ("Podcast generated and saved to your podcast panel."), } # Only other terminal state is FAILED. @@ -146,9 +144,7 @@ def create_generate_podcast_tool( "status": PodcastStatus.FAILED.value, "podcast_id": podcast_id, "title": podcast_title, - "error": ( - "Background worker reported FAILED status for this podcast." - ), + "error": ("Background worker reported FAILED status for this podcast."), } except Exception as e: diff --git a/surfsense_backend/app/agents/new_chat/tools/video_presentation.py b/surfsense_backend/app/agents/new_chat/tools/video_presentation.py index 4bf13b28e..34f5183ca 100644 --- a/surfsense_backend/app/agents/new_chat/tools/video_presentation.py +++ b/surfsense_backend/app/agents/new_chat/tools/video_presentation.py @@ -127,9 +127,7 @@ def create_generate_video_presentation_tool( except Exception as e: error_message = str(e) - logger.exception( - "[generate_video_presentation] Error: %s", error_message - ) + logger.exception("[generate_video_presentation] Error: %s", error_message) return { "status": VideoPresentationStatus.FAILED.value, "error": error_message, diff --git a/surfsense_backend/app/automations/actions/__init__.py b/surfsense_backend/app/automations/actions/__init__.py index 9ef091cb3..72669532f 100644 --- a/surfsense_backend/app/automations/actions/__init__.py +++ b/surfsense_backend/app/automations/actions/__init__.py @@ -21,4 +21,4 @@ __all__ = [ ] # Built-in actions self-register at import time. -from . import agent_task # noqa: E402, F401 +from . import agent_task # noqa: F401 diff --git a/surfsense_backend/app/automations/actions/agent_task/__init__.py b/surfsense_backend/app/automations/actions/agent_task/__init__.py index 308812211..3a42a2815 100644 --- a/surfsense_backend/app/automations/actions/agent_task/__init__.py +++ b/surfsense_backend/app/automations/actions/agent_task/__init__.py @@ -12,4 +12,4 @@ from .params import AgentTaskActionParams __all__ = ["AgentTaskActionParams", "build_handler"] # Side-effect: register on the actions store. -from . import definition # noqa: E402, F401 +from . import definition # noqa: F401 diff --git a/surfsense_backend/app/automations/actions/agent_task/invoke.py b/surfsense_backend/app/automations/actions/agent_task/invoke.py index a37e9beed..6cc92b232 100644 --- a/surfsense_backend/app/automations/actions/agent_task/invoke.py +++ b/surfsense_backend/app/automations/actions/agent_task/invoke.py @@ -13,7 +13,6 @@ from app.agents.multi_agent_chat import create_multi_agent_chat_deep_agent from app.db import ChatVisibility, async_session_maker from ..types import ActionContext - from .auto_decide import build_auto_decisions from .dependencies import build_dependencies from .finalize import extract_final_assistant_message diff --git a/surfsense_backend/app/automations/persistence/models/run.py b/surfsense_backend/app/automations/persistence/models/run.py index 262e4c2bf..471b2df77 100644 --- a/surfsense_backend/app/automations/persistence/models/run.py +++ b/surfsense_backend/app/automations/persistence/models/run.py @@ -50,7 +50,7 @@ class AutomationRun(BaseModel, TimestampMixin): definition_snapshot = Column(JSONB, nullable=False) # merged & validated inputs the run was dispatched with - # (trigger.static_inputs ∪ producer runtime data, static wins on collision) + # (trigger.static_inputs union producer runtime data, static wins on collision) inputs = Column(JSONB, nullable=False, server_default="{}") # one entry per executed step; agent_task entries carry their own # `agent_session_id` inside their entry diff --git a/surfsense_backend/app/automations/runtime/executor.py b/surfsense_backend/app/automations/runtime/executor.py index b8a377e5b..6a33ab314 100644 --- a/surfsense_backend/app/automations/runtime/executor.py +++ b/surfsense_backend/app/automations/runtime/executor.py @@ -6,9 +6,9 @@ from typing import Any from sqlalchemy.ext.asyncio import AsyncSession +from app.automations.actions.types import ActionContext from app.automations.persistence.enums.run_status import RunStatus from app.automations.persistence.models.run import AutomationRun -from app.automations.actions.types import ActionContext from app.automations.schemas.definition.envelope import AutomationDefinition from app.automations.schemas.definition.plan_step import PlanStep from app.automations.templating import build_run_context @@ -32,7 +32,10 @@ async def execute_run(session: AsyncSession, run_id: int) -> None: await repository.mark_failed( session, run, - {"message": f"definition_snapshot invalid: {exc}", "type": type(exc).__name__}, + { + "message": f"definition_snapshot invalid: {exc}", + "type": type(exc).__name__, + }, ) await session.commit() return @@ -92,7 +95,9 @@ async def _run_on_failure( await session.commit() -def _build_template_ctx(run: AutomationRun, step_outputs: dict[str, Any]) -> dict[str, Any]: +def _build_template_ctx( + run: AutomationRun, step_outputs: dict[str, Any] +) -> dict[str, Any]: automation = run.automation trigger = run.trigger return build_run_context( diff --git a/surfsense_backend/app/automations/runtime/step.py b/surfsense_backend/app/automations/runtime/step.py index ac18b5e1f..6e7c9c671 100644 --- a/surfsense_backend/app/automations/runtime/step.py +++ b/surfsense_backend/app/automations/runtime/step.py @@ -30,14 +30,18 @@ async def execute_step( try: should_run = evaluate_predicate(step.when, template_context) except Exception as exc: - return _result(step, "failed", started_at, attempts=0, error=_error(exc, "when")) + return _result( + step, "failed", started_at, attempts=0, error=_error(exc, "when") + ) if not should_run: return _result(step, "skipped", started_at, attempts=0) try: resolved_params = render_value(step.params, template_context) except Exception as exc: - return _result(step, "failed", started_at, attempts=0, error=_error(exc, "render")) + return _result( + step, "failed", started_at, attempts=0, error=_error(exc, "render") + ) action = get_action(step.action) if action is None: @@ -46,12 +50,17 @@ async def execute_step( "failed", started_at, attempts=0, - error={"message": f"action not registered: {step.action}", "type": "ActionNotFound"}, + error={ + "message": f"action not registered: {step.action}", + "type": "ActionNotFound", + }, ) handler = action.build_handler(action_context) - max_retries = step.max_retries if step.max_retries is not None else default_max_retries + max_retries = ( + step.max_retries if step.max_retries is not None else default_max_retries + ) timeout = step.timeout_seconds or default_timeout_seconds try: @@ -62,7 +71,9 @@ async def execute_step( timeout=timeout, ) except Exception as exc: - return _result(step, "failed", started_at, attempts=max_retries + 1, error=_error(exc)) + return _result( + step, "failed", started_at, attempts=max_retries + 1, error=_error(exc) + ) return _result(step, "succeeded", started_at, attempts=attempts, result=result) diff --git a/surfsense_backend/app/automations/schemas/definition/execution.py b/surfsense_backend/app/automations/schemas/definition/execution.py index 61861f8d8..bdbad62f8 100644 --- a/surfsense_backend/app/automations/schemas/definition/execution.py +++ b/surfsense_backend/app/automations/schemas/definition/execution.py @@ -12,7 +12,9 @@ from .plan_step import PlanStep class Execution(BaseModel): model_config = ConfigDict(extra="forbid") - timeout_seconds: int = Field(default=600, gt=0, description="Wall-clock cap for the run.") + timeout_seconds: int = Field( + default=600, gt=0, description="Wall-clock cap for the run." + ) max_retries: int = Field(default=2, ge=0, description="Per-step retry budget.") retry_backoff: Literal["exponential", "linear", "none"] = "exponential" concurrency: Literal["drop_if_running", "queue", "always"] = "drop_if_running" diff --git a/surfsense_backend/app/automations/schemas/definition/plan_step.py b/surfsense_backend/app/automations/schemas/definition/plan_step.py index 5d16f1f3e..0d3bb9dfc 100644 --- a/surfsense_backend/app/automations/schemas/definition/plan_step.py +++ b/surfsense_backend/app/automations/schemas/definition/plan_step.py @@ -11,7 +11,9 @@ class PlanStep(BaseModel): model_config = ConfigDict(extra="forbid") step_id: str = Field(..., min_length=1, description="Unique within the plan.") - action: str = Field(..., min_length=1, description="Action type; resolved via registry.") + action: str = Field( + ..., min_length=1, description="Action type; resolved via registry." + ) when: str | None = Field( default=None, description="Optional predicate; step is skipped when falsy.", diff --git a/surfsense_backend/app/automations/schemas/definition/trigger_spec.py b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py index a359a2f63..e6a995bbf 100644 --- a/surfsense_backend/app/automations/schemas/definition/trigger_spec.py +++ b/surfsense_backend/app/automations/schemas/definition/trigger_spec.py @@ -10,7 +10,9 @@ from pydantic import BaseModel, ConfigDict, Field class TriggerSpec(BaseModel): model_config = ConfigDict(extra="forbid") - type: str = Field(..., min_length=1, description="Trigger type; resolved via registry.") + type: str = Field( + ..., min_length=1, description="Trigger type; resolved via registry." + ) params: dict[str, Any] = Field( default_factory=dict, description="Type-specific params; validated against the trigger's schema.", diff --git a/surfsense_backend/app/automations/services/automation.py b/surfsense_backend/app/automations/services/automation.py index 9140da3b5..0d2937e0e 100644 --- a/surfsense_backend/app/automations/services/automation.py +++ b/surfsense_backend/app/automations/services/automation.py @@ -10,14 +10,14 @@ from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload +from app.automations.persistence.enums.trigger_type import TriggerType +from app.automations.persistence.models.automation import Automation +from app.automations.persistence.models.trigger import AutomationTrigger from app.automations.schemas.api import ( AutomationCreate, AutomationUpdate, TriggerCreate, ) -from app.automations.persistence.enums.trigger_type import TriggerType -from app.automations.persistence.models.automation import Automation -from app.automations.persistence.models.trigger import AutomationTrigger from app.automations.triggers import get_trigger from app.automations.triggers.schedule import compute_next_fire_at from app.db import Permission, User, get_async_session @@ -34,7 +34,9 @@ class AutomationService: async def create(self, payload: AutomationCreate) -> Automation: """Create an automation and its initial triggers in one transaction.""" - await self._authorize(payload.search_space_id, Permission.AUTOMATIONS_CREATE.value) + await self._authorize( + payload.search_space_id, Permission.AUTOMATIONS_CREATE.value + ) automation = Automation( search_space_id=payload.search_space_id, @@ -67,22 +69,32 @@ class AutomationService: ) rows = ( - await self.session.execute( - base.order_by(Automation.created_at.desc()).limit(limit).offset(offset) + ( + await self.session.execute( + base.order_by(Automation.created_at.desc()) + .limit(limit) + .offset(offset) + ) ) - ).scalars().all() + .scalars() + .all() + ) return list(rows), int(total or 0) async def get(self, automation_id: int) -> Automation: """Get an automation with its triggers loaded.""" automation = await self._get_with_triggers_or_raise(automation_id) - await self._authorize(automation.search_space_id, Permission.AUTOMATIONS_READ.value) + await self._authorize( + automation.search_space_id, Permission.AUTOMATIONS_READ.value + ) return automation async def update(self, automation_id: int, patch: AutomationUpdate) -> Automation: """Patch fields. Bumps ``version`` when ``definition`` changes.""" automation = await self._get_with_triggers_or_raise(automation_id) - await self._authorize(automation.search_space_id, Permission.AUTOMATIONS_UPDATE.value) + await self._authorize( + automation.search_space_id, Permission.AUTOMATIONS_UPDATE.value + ) data = patch.model_dump(exclude_unset=True) @@ -93,7 +105,9 @@ class AutomationService: if "status" in data: automation.status = data["status"] if "definition" in data: - automation.definition = patch.definition.model_dump(mode="json", by_alias=True) + automation.definition = patch.definition.model_dump( + mode="json", by_alias=True + ) automation.version += 1 await self.session.commit() @@ -102,7 +116,9 @@ class AutomationService: async def delete(self, automation_id: int) -> None: """Delete an automation; FK cascades remove triggers and runs.""" automation = await self._get_or_raise(automation_id) - await self._authorize(automation.search_space_id, Permission.AUTOMATIONS_DELETE.value) + await self._authorize( + automation.search_space_id, Permission.AUTOMATIONS_DELETE.value + ) await self.session.delete(automation) await self.session.commit() @@ -141,7 +157,9 @@ def _build_trigger(spec: TriggerCreate) -> AutomationTrigger: """Validate trigger params via its registered Pydantic model and build the ORM row.""" definition = get_trigger(spec.type.value) if definition is None: - raise HTTPException(status_code=422, detail=f"unknown trigger type {spec.type.value!r}") + raise HTTPException( + status_code=422, detail=f"unknown trigger type {spec.type.value!r}" + ) try: validated = definition.params_model.model_validate(spec.params) diff --git a/surfsense_backend/app/automations/services/run.py b/surfsense_backend/app/automations/services/run.py index ac9970241..3ef80416f 100644 --- a/surfsense_backend/app/automations/services/run.py +++ b/surfsense_backend/app/automations/services/run.py @@ -36,10 +36,16 @@ class RunService: ) rows = ( - await self.session.execute( - base.order_by(AutomationRun.created_at.desc()).limit(limit).offset(offset) + ( + await self.session.execute( + base.order_by(AutomationRun.created_at.desc()) + .limit(limit) + .offset(offset) + ) ) - ).scalars().all() + .scalars() + .all() + ) return list(rows), int(total or 0) async def get(self, *, automation_id: int, run_id: int) -> AutomationRun: diff --git a/surfsense_backend/app/automations/services/trigger.py b/surfsense_backend/app/automations/services/trigger.py index c76cc0740..29ac84557 100644 --- a/surfsense_backend/app/automations/services/trigger.py +++ b/surfsense_backend/app/automations/services/trigger.py @@ -8,10 +8,10 @@ from fastapi import Depends, HTTPException from pydantic import ValidationError from sqlalchemy.ext.asyncio import AsyncSession -from app.automations.schemas.api import TriggerCreate, TriggerUpdate from app.automations.persistence.enums.trigger_type import TriggerType from app.automations.persistence.models.automation import Automation from app.automations.persistence.models.trigger import AutomationTrigger +from app.automations.schemas.api import TriggerCreate, TriggerUpdate from app.automations.triggers import get_trigger from app.automations.triggers.schedule import compute_next_fire_at from app.db import Permission, User, get_async_session @@ -40,7 +40,9 @@ class TriggerService: params=validated_params, static_inputs=payload.static_inputs, enabled=payload.enabled, - next_fire_at=_initial_next_fire(payload.type, validated_params, payload.enabled), + next_fire_at=_initial_next_fire( + payload.type, validated_params, payload.enabled + ), ) self.session.add(trigger) await self.session.commit() @@ -54,7 +56,9 @@ class TriggerService: trigger_id: int, patch: TriggerUpdate, ) -> AutomationTrigger: - await self._authorize_automation(automation_id, Permission.AUTOMATIONS_UPDATE.value) + await self._authorize_automation( + automation_id, Permission.AUTOMATIONS_UPDATE.value + ) trigger = await self._get_trigger_or_raise(automation_id, trigger_id) data = patch.model_dump(exclude_unset=True) @@ -80,7 +84,9 @@ class TriggerService: return trigger async def remove(self, *, automation_id: int, trigger_id: int) -> None: - await self._authorize_automation(automation_id, Permission.AUTOMATIONS_UPDATE.value) + await self._authorize_automation( + automation_id, Permission.AUTOMATIONS_UPDATE.value + ) trigger = await self._get_trigger_or_raise(automation_id, trigger_id) await self.session.delete(trigger) await self.session.commit() diff --git a/surfsense_backend/app/automations/tasks/execute_run.py b/surfsense_backend/app/automations/tasks/execute_run.py index 5fc84698b..ed448515d 100644 --- a/surfsense_backend/app/automations/tasks/execute_run.py +++ b/surfsense_backend/app/automations/tasks/execute_run.py @@ -17,7 +17,7 @@ TASK_NAME = "automation_run_execute" @celery_app.task(name=TASK_NAME, bind=True) -def automation_run_execute(self, run_id: int) -> None: # noqa: ARG001 — Celery bind +def automation_run_execute(self, run_id: int) -> None: """Execute one ``AutomationRun``. Idempotent: terminal runs no-op.""" return run_async_celery_task(lambda: _impl(run_id)) diff --git a/surfsense_backend/app/automations/tasks/schedule_tick.py b/surfsense_backend/app/automations/tasks/schedule_tick.py index 385bd7242..90fff66fc 100644 --- a/surfsense_backend/app/automations/tasks/schedule_tick.py +++ b/surfsense_backend/app/automations/tasks/schedule_tick.py @@ -103,9 +103,7 @@ async def _self_heal_null_next_fire(session: AsyncSession, *, now: datetime) -> await session.commit() -async def _claim_due_triggers( - session: AsyncSession, *, now: datetime -) -> list[_Claim]: +async def _claim_due_triggers(session: AsyncSession, *, now: datetime) -> list[_Claim]: """Lock and advance due rows; return per-trigger fire context.""" stmt = ( select(AutomationTrigger) diff --git a/surfsense_backend/app/automations/triggers/__init__.py b/surfsense_backend/app/automations/triggers/__init__.py index d7abb6b5d..f630ebf6f 100644 --- a/surfsense_backend/app/automations/triggers/__init__.py +++ b/surfsense_backend/app/automations/triggers/__init__.py @@ -17,4 +17,4 @@ __all__ = [ ] # Built-in triggers self-register at import time. -from . import schedule # noqa: E402, F401 +from . import schedule # noqa: F401 diff --git a/surfsense_backend/app/automations/triggers/schedule/__init__.py b/surfsense_backend/app/automations/triggers/schedule/__init__.py index 5587692b9..92f478aac 100644 --- a/surfsense_backend/app/automations/triggers/schedule/__init__.py +++ b/surfsense_backend/app/automations/triggers/schedule/__init__.py @@ -15,4 +15,4 @@ __all__ = [ ] # Side-effect: register on the triggers store. -from . import definition # noqa: E402, F401 +from . import definition # noqa: F401 diff --git a/surfsense_backend/app/automations/triggers/schedule/cron.py b/surfsense_backend/app/automations/triggers/schedule/cron.py index 7155bab33..a8401e4a3 100644 --- a/surfsense_backend/app/automations/triggers/schedule/cron.py +++ b/surfsense_backend/app/automations/triggers/schedule/cron.py @@ -32,6 +32,10 @@ def compute_next_fire_at(cron: str, timezone: str, *, after: datetime) -> dateti given timezone before evaluation so DST and IANA rules apply correctly. """ tz = ZoneInfo(timezone) - base = after.astimezone(tz) if after.tzinfo else after.replace(tzinfo=UTC).astimezone(tz) + base = ( + after.astimezone(tz) + if after.tzinfo + else after.replace(tzinfo=UTC).astimezone(tz) + ) nxt: datetime = croniter(cron, base).get_next(datetime) return nxt.astimezone(UTC) diff --git a/surfsense_backend/app/automations/triggers/schedule/params.py b/surfsense_backend/app/automations/triggers/schedule/params.py index 21da84f68..f3945a1b8 100644 --- a/surfsense_backend/app/automations/triggers/schedule/params.py +++ b/surfsense_backend/app/automations/triggers/schedule/params.py @@ -10,7 +10,9 @@ from .cron import InvalidCronError, validate_cron class ScheduleTriggerParams(BaseModel): model_config = ConfigDict(extra="forbid") - cron: str = Field(..., description="Five-field cron expression.", examples=["0 9 * * 1-5"]) + cron: str = Field( + ..., description="Five-field cron expression.", examples=["0 9 * * 1-5"] + ) timezone: str = Field(..., description="IANA timezone.", examples=["Africa/Kigali"]) @model_validator(mode="after") diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py index ac880ded5..fe2e53268 100644 --- a/surfsense_backend/app/db.py +++ b/surfsense_backend/app/db.py @@ -2605,7 +2605,6 @@ from app.automations.persistence import ( # noqa: E402, F401 AutomationTrigger, ) - engine = create_async_engine( DATABASE_URL, pool_size=30, diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index ef1c9312a..48a095456 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -1,5 +1,7 @@ from fastapi import APIRouter +from app.automations.api import router as automations_router + from .agent_action_log_route import router as agent_action_log_router from .agent_flags_route import router as agent_flags_router from .agent_permissions_route import router as agent_permissions_router @@ -7,7 +9,6 @@ from .agent_revert_route import router as agent_revert_router from .airtable_add_connector_route import ( router as airtable_add_connector_router, ) -from app.automations.api import router as automations_router from .chat_comments_routes import router as chat_comments_router from .circleback_webhook_route import router as circleback_webhook_router from .clickup_add_connector_route import router as clickup_add_connector_router diff --git a/surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py b/surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py index 0bbf4f0a5..b9cbf6506 100644 --- a/surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py +++ b/surfsense_backend/app/tasks/chat/streaming/context/deepagents_todos.py @@ -19,9 +19,7 @@ def extract_todos_from_deepagents(command_output: Any) -> dict: elif isinstance(command_output, dict): if "todos" in command_output: todos_data = command_output.get("todos", []) - elif "update" in command_output and isinstance( - command_output["update"], dict - ): + elif "update" in command_output and isinstance(command_output["update"], dict): todos_data = command_output["update"].get("todos", []) return {"todos": todos_data} diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py index cb20eb011..af496cee7 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/auto_pin.py @@ -69,17 +69,13 @@ async def resolve_initial_auto_pin( "pin.requires_image_input": requires_image_input, }, ) - return AutoPinResult( - llm_config_id=pinned.resolved_llm_config_id, error=None - ) + return AutoPinResult(llm_config_id=pinned.resolved_llm_config_id, error=None) except ValueError as pin_error: # The "no vision-capable cfg" path raises a ValueError whose message # we map to the friendly image-input SSE error so the user sees the # same message regardless of whether the gate fired in the resolver or # in ``llm_capability.assert_vision_capability_for_image_turn``. - is_vision_failure = ( - requires_image_input and "vision-capable" in str(pin_error) - ) + is_vision_failure = requires_image_input and "vision-capable" in str(pin_error) error_code = ( "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT" if is_vision_failure diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py index fb171c244..f508571b0 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py @@ -207,9 +207,7 @@ async def _resolve_mentions_for_query( try: chip_objs.append(MentionedDocumentInfo.model_validate(raw)) except Exception: - logger.debug( - "stream_new_chat: dropping malformed mention chip %r", raw - ) + logger.debug("stream_new_chat: dropping malformed mention chip %r", raw) resolved = await resolve_mentions( session, diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py index ff5a56eec..9f4e5d2d8 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/llm_capability.py @@ -48,9 +48,7 @@ def check_image_input_capability( return None model_label = agent_config.config_name or agent_config.model_name or "model" - ot.add_event( - "quota.denied", {"quota.code": "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT"} - ) + ot.add_event("quota.denied", {"quota.code": "MODEL_DOES_NOT_SUPPORT_IMAGE_INPUT"}) return ( ( f"The selected model ({model_label}) does not support " diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py index bca72b5ea..6d0853502 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py @@ -259,7 +259,8 @@ async def stream_new_chat( if needs_premium_quota(agent_config, user_id): premium_reservation = await reserve_premium( - agent_config=agent_config, user_id=user_id # type: ignore[arg-type] + agent_config=agent_config, + user_id=user_id, # type: ignore[arg-type] ) if not premium_reservation.allowed: ot.add_event("quota.denied", {"quota.code": "PREMIUM_QUOTA_EXHAUSTED"}) @@ -492,7 +493,9 @@ async def stream_new_chat( # --- Block 4: First SSE frames --- - for sse in iter_initial_frames(streaming_service, turn_id=stream_result.turn_id): + for sse in iter_initial_frames( + streaming_service, turn_id=stream_result.turn_id + ): yield sse # --- Block 5: Persistence join + message-id frames --- @@ -693,7 +696,9 @@ async def stream_new_chat( fallback_commit_search_space_id=search_space_id, fallback_commit_created_by_id=user_id, fallback_commit_filesystem_mode=( - filesystem_selection.mode if filesystem_selection else FilesystemMode.CLOUD + filesystem_selection.mode + if filesystem_selection + else FilesystemMode.CLOUD ), fallback_commit_thread_id=chat_id, runtime_context=runtime_context, @@ -715,11 +720,7 @@ async def stream_new_chat( title_emitted = True # Account for the case where the task completed but produced no # title — flip the flag anyway so we don't keep checking it. - if ( - title_task is not None - and title_task.done() - and not title_emitted - ): + if title_task is not None and title_task.done() and not title_emitted: title_emitted = True _perf_log.info( @@ -811,9 +812,7 @@ async def stream_new_chat( end_turn(str(chat_id)) if premium_reservation is not None and user_id: - await release_premium( - reservation=premium_reservation, user_id=user_id - ) + await release_premium(reservation=premium_reservation, user_id=user_id) await close_session_and_clear_ai_responding(session, chat_id) @@ -852,9 +851,9 @@ async def stream_new_chat( # Break circular refs held by the agent graph, tools, and LLM # wrappers so the GC can reclaim them in a single pass. - agent = llm = connector_service = None # noqa: F841 - input_state = stream_result = None # noqa: F841 - session = None # noqa: F841 + agent = llm = connector_service = None + input_state = stream_result = None + session = None run_gc_pass(log_prefix="stream_new_chat", chat_id=chat_id) close_chat_request_span( diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py index 1f11be1fe..cf1e8c3fb 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/runtime_context.py @@ -30,9 +30,7 @@ def build_new_chat_runtime_context( return SurfSenseContextSchema( search_space_id=search_space_id, mentioned_document_ids=list(mentioned_document_ids or []), - mentioned_folder_ids=list( - accepted_folder_ids or mentioned_folder_ids or [] - ), + mentioned_folder_ids=list(accepted_folder_ids or mentioned_folder_ids or []), request_id=request_id, turn_id=turn_id, ) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py index 11312110f..7db45941b 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/title_gen.py @@ -133,12 +133,8 @@ async def _generate_title( # inherited Azure endpoint — see ``provider_api_base`` for the # same bug repro on the image-gen / vision paths. raw_model = getattr(llm, "model", "") or "" - provider_prefix = ( - raw_model.split("/", 1)[0] if "/" in raw_model else None - ) - provider_value = ( - agent_config.provider if agent_config is not None else None - ) + provider_prefix = raw_model.split("/", 1)[0] if "/" in raw_model else None + provider_value = agent_config.provider if agent_config is not None else None title_api_base = resolve_api_base( provider=provider_value, provider_prefix=provider_prefix, diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py index b67ac987e..e1b95aa63 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/orchestrator.py @@ -15,14 +15,10 @@ building blocks under ``flows/shared/``. Mirrors ``stream_new_chat`` but: from __future__ import annotations import contextlib -import gc import logging -import sys import time -import uuid as _uuid from collections.abc import AsyncGenerator from functools import partial -from typing import Any from uuid import UUID import anyio @@ -32,7 +28,7 @@ from app.agents.new_chat.chat_deepagent import create_surfsense_deep_agent from app.agents.new_chat.filesystem_selection import FilesystemMode, FilesystemSelection from app.agents.new_chat.middleware.busy_mutex import end_turn from app.config import config as _app_config -from app.db import ChatVisibility, async_session_maker, shielded_async_session +from app.db import ChatVisibility, async_session_maker from app.observability import otel as ot from app.services.chat_session_state_service import set_ai_responding from app.services.new_streaming_service import VercelStreamingService @@ -89,7 +85,7 @@ from app.tasks.chat.streaming.flows.shared.terminal_error import ( ) from app.tasks.chat.streaming.shared.stream_result import StreamResult from app.tasks.chat.streaming.shared.utils import resume_step_prefix -from app.utils.perf import get_perf_logger, log_system_snapshot +from app.utils.perf import get_perf_logger logger = logging.getLogger(__name__) _perf_log = get_perf_logger() @@ -217,12 +213,11 @@ async def stream_resume_chat( if needs_premium_quota(agent_config, user_id): premium_reservation = await reserve_premium( - agent_config=agent_config, user_id=user_id # type: ignore[arg-type] + agent_config=agent_config, + user_id=user_id, # type: ignore[arg-type] ) if not premium_reservation.allowed: - ot.add_event( - "quota.denied", {"quota.code": "PREMIUM_QUOTA_EXHAUSTED"} - ) + ot.add_event("quota.denied", {"quota.code": "PREMIUM_QUOTA_EXHAUSTED"}) if requested_llm_config_id == 0: try: pinned_fb = await resolve_or_get_pinned_llm_config_id( @@ -396,7 +391,9 @@ async def stream_resume_chat( # --- First SSE frames --- - for sse in iter_initial_frames(streaming_service, turn_id=stream_result.turn_id): + for sse in iter_initial_frames( + streaming_service, turn_id=stream_result.turn_id + ): yield sse # --- Assistant-shell persistence + id frame --- @@ -517,7 +514,9 @@ async def stream_resume_chat( fallback_commit_search_space_id=search_space_id, fallback_commit_created_by_id=user_id, fallback_commit_filesystem_mode=( - filesystem_selection.mode if filesystem_selection else FilesystemMode.CLOUD + filesystem_selection.mode + if filesystem_selection + else FilesystemMode.CLOUD ), fallback_commit_thread_id=chat_id, runtime_context=runtime_context, @@ -589,9 +588,7 @@ async def stream_resume_chat( end_turn(str(chat_id)) if premium_reservation is not None and user_id: - await release_premium( - reservation=premium_reservation, user_id=user_id - ) + await release_premium(reservation=premium_reservation, user_id=user_id) await close_session_and_clear_ai_responding(session, chat_id) @@ -609,13 +606,11 @@ async def stream_resume_chat( if not busy_error_raised: with contextlib.suppress(Exception): end_turn(str(chat_id)) - _perf_log.info( - "[stream_resume] end_turn cleanup (chat_id=%s)", chat_id - ) + _perf_log.info("[stream_resume] end_turn cleanup (chat_id=%s)", chat_id) - agent = llm = connector_service = None # noqa: F841 - stream_result = None # noqa: F841 - session = None # noqa: F841 + agent = llm = connector_service = None + stream_result = None + session = None run_gc_pass(log_prefix="stream_resume", chat_id=chat_id) close_chat_request_span( diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py index 300fbc9bd..7f4f67aac 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/resume_chat/resume_routing.py @@ -47,9 +47,7 @@ async def build_resume_routing( slice_decisions_by_tool_call, ) - parent_state = await agent.aget_state( - {"configurable": {"thread_id": str(chat_id)}} - ) + parent_state = await agent.aget_state({"configurable": {"thread_id": str(chat_id)}}) pending = collect_pending_tool_calls(parent_state) _perf_log.info( "[hitl_route] resume_entry chat_id=%s decisions=%d pending_subagents=%d", diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py index d16f81ac7..be1f102f3 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/assistant_finalize.py @@ -49,9 +49,7 @@ async def finalize_assistant_message( was never assigned. """ if not ( - stream_result - and stream_result.turn_id - and stream_result.assistant_message_id + stream_result and stream_result.turn_id and stream_result.assistant_message_id ): return diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py index 8d425402f..f9454775e 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/finally_cleanup.py @@ -39,9 +39,7 @@ async def close_session_and_clear_ai_responding( async with shielded_async_session() as fresh_session: await clear_ai_responding(fresh_session, chat_id) except Exception: - logger.warning( - "Failed to clear AI responding state for thread %s", chat_id - ) + logger.warning("Failed to clear AI responding state for thread %s", chat_id) with contextlib.suppress(Exception): session.expunge_all() diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py index 0ec40d275..cbf44764c 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/premium_quota.py @@ -41,9 +41,7 @@ class PremiumReservation: allowed: bool -def needs_premium_quota( - agent_config: AgentConfig | None, user_id: str | None -) -> bool: +def needs_premium_quota(agent_config: AgentConfig | None, user_id: str | None) -> bool: return bool(agent_config is not None and user_id and agent_config.is_premium) @@ -61,8 +59,10 @@ async def reserve_premium( request_id = _uuid.uuid4().hex[:16] litellm_params = agent_config.litellm_params or {} base_model = ( - litellm_params.get("base_model") if isinstance(litellm_params, dict) else None - ) or agent_config.model_name or "" + (litellm_params.get("base_model") if isinstance(litellm_params, dict) else None) + or agent_config.model_name + or "" + ) reserve_amount_micros = estimate_call_reserve_micros( base_model=base_model, quota_reserve_tokens=agent_config.quota_reserve_tokens, diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py index 1e5169af1..74b9682ed 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/span.py @@ -6,8 +6,7 @@ import contextlib import sys from typing import Any, Literal -from app.observability import metrics as ot_metrics -from app.observability import otel as ot +from app.observability import metrics as ot_metrics, otel as ot def open_chat_request_span( diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py b/surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py index c9db2caf2..b305dba23 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/shared/terminal_error.py @@ -15,8 +15,7 @@ from collections.abc import Iterator from typing import Any, Literal from app.agents.new_chat.errors import BusyError -from app.observability import metrics as ot_metrics -from app.observability import otel as ot +from app.observability import metrics as ot_metrics, otel as ot from app.services.new_streaming_service import VercelStreamingService from app.tasks.chat.streaming.errors.classifier import classify_stream_exception from app.tasks.chat.streaming.errors.emitter import emit_stream_terminal_error diff --git a/surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py b/surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py index bd49d764c..aa6c74549 100644 --- a/surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py +++ b/surfsense_backend/tests/unit/automations/actions/agent_task/test_finalize.py @@ -72,7 +72,11 @@ def test_extract_returns_none_when_no_assistant_text_is_present() -> None: anything?" rather than guess whether ``""`` means silence or empty output. Empty-string contents are normalized to ``None`` too.""" no_ai = {"messages": [HumanMessage(content="just a question")]} - only_tools = {"messages": [AIMessage(content=[{"type": "tool_use", "name": "x", "input": {}}])]} + only_tools = { + "messages": [ + AIMessage(content=[{"type": "tool_use", "name": "x", "input": {}}]) + ] + } empty_string = {"messages": [AIMessage(content=" ")]} assert extract_final_assistant_message(no_ai) is None diff --git a/surfsense_backend/tests/unit/automations/runtime/test_retries.py b/surfsense_backend/tests/unit/automations/runtime/test_retries.py index f0f12ca59..05fd02ab6 100644 --- a/surfsense_backend/tests/unit/automations/runtime/test_retries.py +++ b/surfsense_backend/tests/unit/automations/runtime/test_retries.py @@ -33,7 +33,9 @@ async def test_with_retries_returns_result_and_attempts_one_on_first_success() - assert calls == 1 -async def test_with_retries_returns_attempt_count_when_succeeding_after_failures() -> None: +async def test_with_retries_returns_attempt_count_when_succeeding_after_failures() -> ( + None +): """A coroutine that fails twice then succeeds returns ``attempts=3`` (the actual attempt that produced the result). Locks the contract that the caller can distinguish first-try success from a recovery.""" diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py index c625b0ec9..d7b392a1d 100644 --- a/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py @@ -11,7 +11,9 @@ from app.automations.schemas.definition.plan_step import PlanStep pytestmark = pytest.mark.unit -def test_automation_definition_accepts_minimal_valid_input_with_sensible_defaults() -> None: +def test_automation_definition_accepts_minimal_valid_input_with_sensible_defaults() -> ( + None +): """A definition with just ``name`` + a one-step ``plan`` is valid and fills in the rest with safe defaults so users don't have to write out every section to get started.""" diff --git a/surfsense_backend/tests/unit/automations/templating/test_environment.py b/surfsense_backend/tests/unit/automations/templating/test_environment.py index ec1c0ee40..64850c9c5 100644 --- a/surfsense_backend/tests/unit/automations/templating/test_environment.py +++ b/surfsense_backend/tests/unit/automations/templating/test_environment.py @@ -32,7 +32,9 @@ def test_environment_finalizes_datetime_output_to_iso_string() -> None: when emitting ``inputs.fired_at`` and other datetime values.""" dt = datetime(2026, 5, 28, 14, 30, tzinfo=UTC) - assert render_template("{{ moment }}", {"moment": dt}) == "2026-05-28T14:30:00+00:00" + assert ( + render_template("{{ moment }}", {"moment": dt}) == "2026-05-28T14:30:00+00:00" + ) def test_environment_finalizes_none_output_to_empty_string() -> None: diff --git a/surfsense_backend/tests/unit/automations/test_definition_types.py b/surfsense_backend/tests/unit/automations/test_definition_types.py index 231e4fa97..2320b61d3 100644 --- a/surfsense_backend/tests/unit/automations/test_definition_types.py +++ b/surfsense_backend/tests/unit/automations/test_definition_types.py @@ -31,7 +31,7 @@ def test_action_definition_params_schema_reflects_params_model() -> None: name="N", description="D", params_model=_Topic, - build_handler=lambda _ctx: (lambda _p: {}), # type: ignore[arg-type,return-value] + build_handler=lambda _ctx: lambda _p: {}, # type: ignore[arg-type,return-value] ) schema = definition.params_schema diff --git a/surfsense_backend/tests/unit/automations/test_stores.py b/surfsense_backend/tests/unit/automations/test_stores.py index e54062d64..d005d7be7 100644 --- a/surfsense_backend/tests/unit/automations/test_stores.py +++ b/surfsense_backend/tests/unit/automations/test_stores.py @@ -29,7 +29,9 @@ class _Params(BaseModel): def _trigger(type_: str = "test_trigger") -> TriggerDefinition: - return TriggerDefinition(type=type_, description="Test trigger.", params_model=_Params) + return TriggerDefinition( + type=type_, description="Test trigger.", params_model=_Params + ) def _action(type_: str = "test_action") -> ActionDefinition: @@ -38,7 +40,7 @@ def _action(type_: str = "test_action") -> ActionDefinition: name="Test", description="Test action.", params_model=_Params, - build_handler=lambda _ctx: (lambda _p: {}), # type: ignore[arg-type,return-value] + build_handler=lambda _ctx: lambda _p: {}, # type: ignore[arg-type,return-value] ) @@ -112,4 +114,4 @@ def test_all_triggers_returns_defensive_snapshot( snapshot = all_triggers() snapshot.pop("snapshot_test") - assert get_trigger("snapshot_test") is not None \ No newline at end of file + assert get_trigger("snapshot_test") is not None diff --git a/surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py b/surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py index 261e51b18..5c7580823 100644 --- a/surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py +++ b/surfsense_backend/tests/unit/automations/triggers/schedule/test_cron.py @@ -45,8 +45,12 @@ def test_compute_next_fire_at_respects_dst_offset_change() -> None: winter_after = datetime(2026, 2, 15, 0, 0, tzinfo=UTC) summer_after = datetime(2026, 4, 15, 0, 0, tzinfo=UTC) - winter_fire = compute_next_fire_at("0 9 * * *", "America/New_York", after=winter_after) - summer_fire = compute_next_fire_at("0 9 * * *", "America/New_York", after=summer_after) + winter_fire = compute_next_fire_at( + "0 9 * * *", "America/New_York", after=winter_after + ) + summer_fire = compute_next_fire_at( + "0 9 * * *", "America/New_York", after=summer_after + ) assert winter_fire == datetime(2026, 2, 15, 14, 0, tzinfo=UTC) assert summer_fire == datetime(2026, 4, 15, 13, 0, tzinfo=UTC) diff --git a/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py b/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py index eb24b4df8..ff4ca30df 100644 --- a/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py +++ b/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py @@ -33,7 +33,6 @@ import pytest from app.agents.new_chat.context import SurfSenseContextSchema from app.services.new_streaming_service import VercelStreamingService - from app.tasks.chat.stream_new_chat import ( stream_new_chat as old_stream_new_chat, stream_resume_chat as old_stream_resume_chat, @@ -152,7 +151,13 @@ class _FakeSurfsenseDoc: "user_query, image_urls, docs, expected_title, expected_action", [ ("hello world", None, [], "Understanding your request", "Processing"), - ("", ["data:image/png;base64,AAA"], [], "Understanding your request", "Processing"), + ( + "", + ["data:image/png;base64,AAA"], + [], + "Understanding your request", + "Processing", + ), ("", None, [], "Understanding your request", "Processing"), ( "doc question", @@ -209,9 +214,10 @@ def test_initial_thinking_step_collapses_many_doc_names() -> None: def test_image_capability_passes_without_images() -> None: - assert check_image_input_capability( - user_image_data_urls=None, agent_config=None - ) is None + assert ( + check_image_input_capability(user_image_data_urls=None, agent_config=None) + is None + ) def test_image_capability_passes_when_capability_unknown() -> None: @@ -500,9 +506,7 @@ def test_can_recover_provider_rate_limit_rejects_non_rate_limit_exception() -> N def test_spawn_set_ai_responding_bg_noop_without_user_id() -> None: async def _run() -> set[asyncio.Task]: background: set[asyncio.Task] = set() - spawn_set_ai_responding_bg( - chat_id=1, user_id=None, background_tasks=background - ) + spawn_set_ai_responding_bg(chat_id=1, user_id=None, background_tasks=background) return background bg = asyncio.run(_run()) diff --git a/surfsense_web/app/(home)/free/page.tsx b/surfsense_web/app/(home)/free/page.tsx index 4512f3396..5cea9b6d2 100644 --- a/surfsense_web/app/(home)/free/page.tsx +++ b/surfsense_web/app/(home)/free/page.tsx @@ -221,10 +221,7 @@ export default async function FreeHubPage() { <Separator className="my-12 max-w-4xl mx-auto" /> {/* In-content ad: above the model table */} - <aside - aria-label="Advertisement" - className="max-w-4xl mx-auto mb-8 min-h-[100px]" - > + <aside aria-label="Advertisement" className="max-w-4xl mx-auto mb-8 min-h-[100px]"> <AdUnit slot={ADSENSE_SLOTS.freeHubInContent} /> </aside> @@ -353,10 +350,7 @@ export default async function FreeHubPage() { <Separator className="my-12 max-w-4xl mx-auto" /> {/* In-content ad: after CTA, before FAQ */} - <aside - aria-label="Advertisement" - className="max-w-3xl mx-auto my-8 min-h-[100px]" - > + <aside aria-label="Advertisement" className="max-w-3xl mx-auto my-8 min-h-[100px]"> <AdUnit slot={ADSENSE_SLOTS.freeHubBeforeFaq} /> </aside> diff --git a/surfsense_web/app/(home)/privacy/page.tsx b/surfsense_web/app/(home)/privacy/page.tsx index 22833ae4a..cc7f64bee 100644 --- a/surfsense_web/app/(home)/privacy/page.tsx +++ b/surfsense_web/app/(home)/privacy/page.tsx @@ -37,9 +37,9 @@ export default function PrivacyPolicy() { </p> <p className="mt-4"> By accessing or using the Service, you acknowledge that you have read and understood - this Privacy Policy. If you do not agree with our policies and practices, do not use - the Service. We may modify this policy from time to time; material changes will be - reflected by updating the "Last updated" date above. + this Privacy Policy. If you do not agree with our policies and practices, do not use the + Service. We may modify this policy from time to time; material changes will be reflected + by updating the "Last updated" date above. </p> </section> @@ -71,9 +71,9 @@ export default function PrivacyPolicy() { Notion, Confluence, GitHub, and others) under the scopes you authorize. </li> <li> - <strong>Billing Data</strong> includes information necessary to process payments - (such as transaction identifiers and credit balances). Card details are handled by - our payment processor and are not stored on our servers. + <strong>Billing Data</strong> includes information necessary to process payments (such + as transaction identifiers and credit balances). Card details are handled by our + payment processor and are not stored on our servers. </li> <li> <strong>Technical Data</strong> includes internet protocol (IP) address, browser type @@ -126,8 +126,8 @@ export default function PrivacyPolicy() { incidents. </li> <li> - To communicate with you about product updates, security notices, support requests, - and (with your consent where required) marketing. + To communicate with you about product updates, security notices, support requests, and + (with your consent where required) marketing. </li> <li> To serve and measure advertising on pages where ads are shown (currently, our free @@ -141,8 +141,8 @@ export default function PrivacyPolicy() { <h2 className="text-2xl font-semibold mb-4">4. Cookies and Tracking Technologies</h2> <p> We and our partners use cookies, local storage, and similar technologies to operate the - Service, remember your preferences, measure usage, and serve advertising. The - categories include: + Service, remember your preferences, measure usage, and serve advertising. The categories + include: </p> <ul className="list-disc pl-6 my-4 space-y-2"> <li> @@ -179,9 +179,9 @@ export default function PrivacyPolicy() { </p> <ul className="list-disc pl-6 my-4 space-y-2"> <li> - Google, as a third-party vendor, uses cookies (including the DoubleClick DART - cookie) to serve ads to you based on your visits to our Service and other websites - on the Internet. + Google, as a third-party vendor, uses cookies (including the DoubleClick DART cookie) + to serve ads to you based on your visits to our Service and other websites on the + Internet. </li> <li> Google's use of advertising cookies enables it and its partners to serve ads to you @@ -195,14 +195,12 @@ export default function PrivacyPolicy() { <a href="https://www.youronlinechoices.com/">youronlinechoices.com</a> (EU). </li> <li> - For users in the European Economic Area, the United Kingdom, and Switzerland, we - use a Google-certified Consent Management Platform to obtain your consent for - personalized advertising before such cookies are set. You may change or withdraw - your consent at any time through the consent banner. - </li> - <li> - We do not knowingly serve personalized advertising to children. See Section 11. + For users in the European Economic Area, the United Kingdom, and Switzerland, we use a + Google-certified Consent Management Platform to obtain your consent for personalized + advertising before such cookies are set. You may change or withdraw your consent at + any time through the consent banner. </li> + <li>We do not knowingly serve personalized advertising to children. See Section 11.</li> </ul> <p className="mt-4"> For more information about how Google uses data when you use our Service, see{" "} @@ -217,8 +215,8 @@ export default function PrivacyPolicy() { <h2 className="text-2xl font-semibold mb-4">6. Data Security</h2> <p> We implement technical and organizational measures designed to protect your personal - data against accidental loss, unauthorized access, alteration, and disclosure. Access - to personal data is limited to personnel who need it to operate the Service. + data against accidental loss, unauthorized access, alteration, and disclosure. Access to + personal data is limited to personnel who need it to operate the Service. </p> <p className="mt-4"> No system can be guaranteed to be fully secure. We cannot guarantee that personal data @@ -232,10 +230,10 @@ export default function PrivacyPolicy() { <p> We retain personal data only for as long as necessary to provide the Service and to comply with our legal, accounting, and reporting obligations. Account data is retained - for the life of your account; you can request deletion at any time. Aggregated data - that no longer identifies you may be retained indefinitely for analytics and product - improvement purposes. Anonymous chat sessions on our free pages are not retained in - any user-linked database. + for the life of your account; you can request deletion at any time. Aggregated data that + no longer identifies you may be retained indefinitely for analytics and product + improvement purposes. Anonymous chat sessions on our free pages are not retained in any + user-linked database. </p> </section> @@ -243,8 +241,7 @@ export default function PrivacyPolicy() { <h2 className="text-2xl font-semibold mb-4">8. Third-Party Services</h2> <p> We rely on the following categories of third-party processors and providers to operate - the Service. Each is bound by its own privacy policy, which we encourage you to - review: + the Service. Each is bound by its own privacy policy, which we encourage you to review: </p> <ul className="list-disc pl-6 my-4 space-y-2"> <li> @@ -261,9 +258,9 @@ export default function PrivacyPolicy() { <strong>Advertising</strong>: Google AdSense (see Section 5). </li> <li> - <strong>Large language model providers</strong>: OpenAI, Anthropic, Google, and - other LLM providers process the prompts and content you submit to the Service in - order to generate responses. + <strong>Large language model providers</strong>: OpenAI, Anthropic, Google, and other + LLM providers process the prompts and content you submit to the Service in order to + generate responses. </li> <li> <strong>Integration providers</strong>: When you explicitly connect a third-party @@ -278,9 +275,7 @@ export default function PrivacyPolicy() { </section> <section className="mb-8"> - <h2 className="text-2xl font-semibold mb-4"> - 9. Your Legal Rights (Including GDPR) - </h2> + <h2 className="text-2xl font-semibold mb-4">9. Your Legal Rights (Including GDPR)</h2> <p> Subject to applicable law, you have the following rights in relation to your personal data: @@ -314,17 +309,17 @@ export default function PrivacyPolicy() { </p> <ul className="list-disc pl-6 my-4 space-y-2"> <li> - The right to know what categories of personal information we have collected about - you and how it is used and shared. + The right to know what categories of personal information we have collected about you + and how it is used and shared. </li> <li>The right to delete personal information we have collected from you.</li> <li>The right to correct inaccurate personal information.</li> <li> The right to opt out of the "sale" or "sharing" of personal information for cross-context behavioral advertising. We do not sell personal data; however, - advertising cookies set by Google AdSense may be considered "sharing" under - California law. To opt out, you can use the consent controls described in Section 5 - or enable a Global Privacy Control (GPC) signal in your browser, which we honor. + advertising cookies set by Google AdSense may be considered "sharing" under California + law. To opt out, you can use the consent controls described in Section 5 or enable a + Global Privacy Control (GPC) signal in your browser, which we honor. </li> <li>The right not to be discriminated against for exercising your privacy rights.</li> </ul> @@ -337,33 +332,32 @@ export default function PrivacyPolicy() { <h2 className="text-2xl font-semibold mb-4">11. Children's Privacy</h2> <p> The Service is not directed to children under 13 (or under 16 in the EEA, UK, and - Switzerland). We do not knowingly collect personal data from children. If you believe - a child has provided us with personal data, please contact us and we will take steps - to delete it. We do not knowingly serve personalized advertising to children. + Switzerland). We do not knowingly collect personal data from children. If you believe a + child has provided us with personal data, please contact us and we will take steps to + delete it. We do not knowingly serve personalized advertising to children. </p> </section> <section className="mb-8"> <h2 className="text-2xl font-semibold mb-4">12. Changes to This Policy</h2> <p> - We may update this Privacy Policy from time to time to reflect changes in our - practices, technology, legal requirements, or for other operational reasons. When we - make material changes, we will update the "Last updated" date at the top of this page - and, where appropriate, provide additional notice (such as an in-product notification - or email). Your continued use of the Service after the updated policy becomes - effective constitutes your acceptance of the revised policy. + We may update this Privacy Policy from time to time to reflect changes in our practices, + technology, legal requirements, or for other operational reasons. When we make material + changes, we will update the "Last updated" date at the top of this page and, where + appropriate, provide additional notice (such as an in-product notification or email). + Your continued use of the Service after the updated policy becomes effective constitutes + your acceptance of the revised policy. </p> </section> <section className="mb-8"> <h2 className="text-2xl font-semibold mb-4">13. Contact Us</h2> <p> - If you have questions about this Privacy Policy or our privacy practices, or if you - want to exercise any of your rights, please contact us at: + If you have questions about this Privacy Policy or our privacy practices, or if you want + to exercise any of your rights, please contact us at: </p> <p className="mt-2"> - <strong>Email:</strong>{" "} - <a href="mailto:rohan@surfsense.com">rohan@surfsense.com</a> + <strong>Email:</strong> <a href="mailto:rohan@surfsense.com">rohan@surfsense.com</a> </p> </section> </div> diff --git a/surfsense_web/app/api/zero/query/route.ts b/surfsense_web/app/api/zero/query/route.ts index 8caac9cd4..0e64c932f 100644 --- a/surfsense_web/app/api/zero/query/route.ts +++ b/surfsense_web/app/api/zero/query/route.ts @@ -1,10 +1,10 @@ import { mustGetQuery } from "@rocicorp/zero"; import { handleQueryRequest } from "@rocicorp/zero/server"; import { NextResponse } from "next/server"; +import { BACKEND_URL } from "@/lib/env-config"; import type { Context } from "@/types/zero"; import { queries } from "@/zero/queries"; import { schema } from "@/zero/schema"; -import { BACKEND_URL } from "@/lib/env-config"; const backendURL = BACKEND_URL; diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx index 219552a1a..6504af5a4 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx @@ -16,10 +16,7 @@ interface AutomationEditContentProps { * structure but gates on ``canUpdate`` instead of ``canRead``: a user who * can read but not update is bounced to the access-denied panel. */ -export function AutomationEditContent({ - searchSpaceId, - automationId, -}: AutomationEditContentProps) { +export function AutomationEditContent({ searchSpaceId, automationId }: AutomationEditContentProps) { const perms = useAutomationPermissions(); const validId = Number.isInteger(automationId) && automationId > 0; const { data: automation, isLoading, error } = useAutomation(validId ? automationId : undefined); diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx index 86b355838..9b950608e 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx @@ -9,10 +9,7 @@ import { JsonView } from "@/components/json-view"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Spinner } from "@/components/ui/spinner"; -import { - type Automation, - automationUpdateRequest, -} from "@/contracts/types/automation.types"; +import { type Automation, automationUpdateRequest } from "@/contracts/types/automation.types"; interface AutomationEditFormProps { automation: Automation; diff --git a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx index 6cd95a79c..06a069a4c 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx @@ -69,11 +69,11 @@ import { documentsApiService } from "@/lib/apis/documents-api.service"; import { getBearerToken } from "@/lib/auth-utils"; import { type ChatFlow, classifyChatError } from "@/lib/chat/chat-error-classifier"; import { tagPreAcceptSendFailure, toHttpResponseError } from "@/lib/chat/chat-request-errors"; +import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; import { convertToThreadMessage, reconcileInterruptedAssistantMessages, } from "@/lib/chat/message-utils"; -import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; import { isPodcastGenerating, looksLikePodcastRequest, @@ -110,6 +110,7 @@ import { extractUserTurnForNewChatApi, type NewChatUserImagePayload, } from "@/lib/chat/user-turn-api-parts"; +import { BACKEND_URL } from "@/lib/env-config"; import { NotFoundError } from "@/lib/error"; import { trackChatBlocked, @@ -119,7 +120,7 @@ import { trackChatResponseReceived, } from "@/lib/posthog/events"; import Loading from "../loading"; -import { BACKEND_URL } from "@/lib/env-config"; + const MobileEditorPanel = dynamic( () => import("@/components/editor-panel/editor-panel").then((m) => ({ @@ -1977,14 +1978,12 @@ export default function NewChatPage() { mentioned_folder_ids: regenerateFolderIds.length > 0 ? regenerateFolderIds : undefined, mentioned_connector_ids: regenerateConnectors.length > 0 ? regenerateConnectors.map((d) => d.id) : undefined, - mentioned_connectors: - regenerateConnectors.length > 0 ? regenerateConnectors : undefined, + mentioned_connectors: regenerateConnectors.length > 0 ? regenerateConnectors : undefined, // Full mention metadata for the regenerate-specific // source list. Only meaningful for edit (the BE only // re-persists a user row when ``user_query`` is set); // reload reuses the original turn's mentioned_documents. - mentioned_documents: - sourceMentionedDocs.length > 0 ? sourceMentionedDocs : undefined, + mentioned_documents: sourceMentionedDocs.length > 0 ? sourceMentionedDocs : undefined, }; if (isEdit) { requestBody.user_images = editExtras?.userImages ?? []; diff --git a/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx index 17c1dd121..3bc2459c1 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/team/team-content.tsx @@ -31,7 +31,7 @@ import { deleteMemberMutationAtom, updateMemberMutationAtom, } from "@/atoms/members/members-mutation.atoms"; -import { membersAtom, myAccessAtom, canPerform } from "@/atoms/members/members-query.atoms"; +import { canPerform, membersAtom, myAccessAtom } from "@/atoms/members/members-query.atoms"; import { AlertDialog, AlertDialogAction, diff --git a/surfsense_web/atoms/members/members-query.atoms.ts b/surfsense_web/atoms/members/members-query.atoms.ts index f8e4b2cf6..c1f507ff5 100644 --- a/surfsense_web/atoms/members/members-query.atoms.ts +++ b/surfsense_web/atoms/members/members-query.atoms.ts @@ -42,11 +42,11 @@ export const myAccessAtom = atomWithQuery((get) => { /** * Helper function to check if the current user has a specific permission. - * + * * @param access - The access object from useAtomValue(myAccessAtom) * @param permission - The permission string to check * @returns boolean indicating if the user has the permission - * + * * @example * const access = useAtomValue(myAccessAtom); * if (canPerform(access, 'manage_members')) { ... } @@ -63,10 +63,10 @@ export function canPerform( /** * Hook wrapper for canPerform that reads from myAccessAtom internally. * Use this if you want to avoid calling useAtomValue(myAccessAtom) separately. - * + * * @param permission - The permission string to check * @returns boolean indicating if the user has the permission - * + * * @example * const canManageMembers = usePermissionGate('manage_members'); */ diff --git a/surfsense_web/components/assistant-ui/assistant-message.tsx b/surfsense_web/components/assistant-ui/assistant-message.tsx index f6c91e8bf..fd24600c2 100644 --- a/surfsense_web/components/assistant-ui/assistant-message.tsx +++ b/surfsense_web/components/assistant-ui/assistant-message.tsx @@ -13,8 +13,8 @@ import { CheckIcon, ClipboardPaste, CopyIcon, - DownloadIcon, Dot, + DownloadIcon, ExternalLink, Globe, MessageCircleReply, diff --git a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/obsidian-connect-form.tsx b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/obsidian-connect-form.tsx index 01b86a538..a9231d846 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/obsidian-connect-form.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connect-forms/components/obsidian-connect-form.tsx @@ -6,14 +6,13 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { EnumConnectorName } from "@/contracts/enums/connector"; import { useApiKey } from "@/hooks/use-api-key"; +import { BACKEND_URL } from "@/lib/env-config"; import { getConnectorBenefits } from "../connector-benefits"; import type { ConnectFormProps } from "../index"; -import { BACKEND_URL } from "@/lib/env-config"; const PLUGIN_RELEASES_URL = "https://github.com/MODSetter/SurfSense/releases?q=obsidian&expanded=true"; - /** * Obsidian connect form for the plugin-only architecture. * diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/circleback-config.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/circleback-config.tsx index fe6724ed8..4de8500a6 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/circleback-config.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/components/circleback-config.tsx @@ -9,8 +9,8 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { authenticatedFetch } from "@/lib/auth-utils"; -import type { ConnectorConfigProps } from "../index"; import { BACKEND_URL } from "@/lib/env-config"; +import type { ConnectorConfigProps } from "../index"; export interface CirclebackConfigProps extends ConnectorConfigProps { onNameChange?: (name: string) => void; } diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx index 3e9e9bb27..2b86daf65 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx @@ -23,6 +23,7 @@ import { LIVE_CONNECTOR_TYPES } from "../../constants/connector-constants"; import { getConnectorDisplayName } from "../../tabs/all-connectors-tab"; import { MCPServiceConfig } from "../components/mcp-service-config"; import { getConnectorConfigComponent } from "../index"; + const VISION_LLM_CONNECTOR_TYPES = new Set<SearchSourceConnector["connector_type"]>([ EnumConnectorName.GOOGLE_DRIVE_CONNECTOR, EnumConnectorName.COMPOSIO_GOOGLE_DRIVE_CONNECTOR, diff --git a/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx index 27e102d7e..05b684397 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/views/connector-accounts-list-view.tsx @@ -18,6 +18,7 @@ import { cn } from "@/lib/utils"; import { LIVE_CONNECTOR_TYPES } from "../constants/connector-constants"; import { useConnectorStatus } from "../hooks/use-connector-status"; import { getConnectorDisplayName } from "../tabs/all-connectors-tab"; + interface ConnectorAccountsListViewProps { connectorType: string; connectorTitle: string; diff --git a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx index c0d9d9212..52e015c56 100644 --- a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx +++ b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx @@ -97,7 +97,12 @@ interface InlineMentionEditorProps { onActionClose?: () => void; onSubmit?: () => void; onChange?: (text: string, docs: MentionedDocument[]) => void; - onDocumentRemove?: (docId: number, docType?: string, kind?: MentionKind, connectorType?: string) => void; + onDocumentRemove?: ( + docId: number, + docType?: string, + kind?: MentionKind, + connectorType?: string + ) => void; onKeyDown?: (e: React.KeyboardEvent) => void; disabled?: boolean; className?: string; @@ -171,9 +176,10 @@ const MentionElement: FC<PlateElementProps<MentionElementNode>> = ({ {isFolder ? ( <FolderIcon className="h-3 w-3" /> ) : isConnector ? ( - getConnectorIcon(element.connector_type ?? element.document_type ?? "UNKNOWN", "h-3 w-3") ?? ( - <PlugIcon className="h-3 w-3" /> - ) + (getConnectorIcon( + element.connector_type ?? element.document_type ?? "UNKNOWN", + "h-3 w-3" + ) ?? <PlugIcon className="h-3 w-3" />) ) : ( getConnectorIcon(element.document_type ?? "UNKNOWN", "h-3 w-3") )} @@ -357,7 +363,11 @@ function getSelectionAnchorRect(root: HTMLElement | null): SuggestionAnchorRect const rect = range.getClientRects()[0] ?? range.getBoundingClientRect(); if (rect.width > 0 || rect.height > 0) return rectToAnchor(rect); - if (range.collapsed && range.startContainer.nodeType === Node.TEXT_NODE && range.startOffset > 0) { + if ( + range.collapsed && + range.startContainer.nodeType === Node.TEXT_NODE && + range.startOffset > 0 + ) { const fallbackRange = range.cloneRange(); fallbackRange.setStart(range.startContainer, range.startOffset - 1); fallbackRange.setEnd(range.startContainer, range.startOffset); diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 5c5f99940..9abcfbb49 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -68,11 +68,6 @@ import { import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; import { UserMessage } from "@/components/assistant-ui/user-message"; import { ComposerSuggestionPopoverContent } from "@/components/new-chat/composer-suggestion-popup"; -import { - DocumentMentionPicker, - promoteRecentMention, - type DocumentMentionPickerRef, -} from "../new-chat/document-mention-picker"; import { PromptPicker, type PromptPickerRef } from "@/components/new-chat/prompt-picker"; import { Avatar, AvatarFallback, AvatarGroup } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; @@ -112,6 +107,11 @@ import { captureDisplayToPngDataUrl } from "@/lib/chat/display-media-capture"; import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; import { slideoutOpenedTickAtom } from "@/lib/layout-events"; import { cn } from "@/lib/utils"; +import { + DocumentMentionPicker, + type DocumentMentionPickerRef, + promoteRecentMention, +} from "../new-chat/document-mention-picker"; const COMPOSER_PLACEHOLDER = "Ask anything, type / for prompts, type @ to mention docs"; @@ -601,21 +601,24 @@ const Composer: FC = () => { } }, []); - const handleActionTrigger = useCallback((trigger: SuggestionTriggerInfo) => { - const anchorPoint = getComposerSuggestionAnchorPoint( - trigger.anchorRect, - clipboardInitialText ? "bottom" : "top" - ); - if (!anchorPoint) { - setShowPromptPicker(false); - setActionQuery(""); - setSuggestionAnchorPoint(null); - return; - } - setSuggestionAnchorPoint((current) => current ?? anchorPoint); - setShowPromptPicker(true); - setActionQuery(trigger.query); - }, [clipboardInitialText]); + const handleActionTrigger = useCallback( + (trigger: SuggestionTriggerInfo) => { + const anchorPoint = getComposerSuggestionAnchorPoint( + trigger.anchorRect, + clipboardInitialText ? "bottom" : "top" + ); + if (!anchorPoint) { + setShowPromptPicker(false); + setActionQuery(""); + setSuggestionAnchorPoint(null); + return; + } + setSuggestionAnchorPoint((current) => current ?? anchorPoint); + setShowPromptPicker(true); + setActionQuery(trigger.query); + }, + [clipboardInitialText] + ); const handleActionClose = useCallback(() => { if (showPromptPicker) { @@ -754,7 +757,12 @@ const Composer: FC = () => { ]); const handleDocumentRemove = useCallback( - (docId: number, docType?: string, kind?: "doc" | "folder" | "connector", connectorType?: string) => { + ( + docId: number, + docType?: string, + kind?: "doc" | "folder" | "connector", + connectorType?: string + ) => { setMentionedDocuments((prev) => { const removedKey = getMentionDocKey({ id: docId, @@ -768,27 +776,30 @@ const Composer: FC = () => { [setMentionedDocuments] ); - const handleDocumentsMention = useCallback((mentions: MentionedDocumentInfo[]) => { - const parsedSearchSpaceId = Number(search_space_id); - const editorMentionedDocs = editorRef.current?.getMentionedDocuments() ?? []; - const editorDocKeys = new Set(editorMentionedDocs.map((doc) => getMentionDocKey(doc))); + const handleDocumentsMention = useCallback( + (mentions: MentionedDocumentInfo[]) => { + const parsedSearchSpaceId = Number(search_space_id); + const editorMentionedDocs = editorRef.current?.getMentionedDocuments() ?? []; + const editorDocKeys = new Set(editorMentionedDocs.map((doc) => getMentionDocKey(doc))); - for (const mention of mentions) { - const key = getMentionDocKey(mention); - if (editorDocKeys.has(key)) continue; - editorRef.current?.insertMentionChip(mention); - if (Number.isFinite(parsedSearchSpaceId)) { - promoteRecentMention(parsedSearchSpaceId, mention); + for (const mention of mentions) { + const key = getMentionDocKey(mention); + if (editorDocKeys.has(key)) continue; + editorRef.current?.insertMentionChip(mention); + if (Number.isFinite(parsedSearchSpaceId)) { + promoteRecentMention(parsedSearchSpaceId, mention); + } + // Track within the loop so a duplicate-in-batch can't double-insert. + editorDocKeys.add(key); } - // Track within the loop so a duplicate-in-batch can't double-insert. - editorDocKeys.add(key); - } - // Atom is reconciled by ``handleEditorChange`` via the editor's - // onChange — no second write path here. - setMentionQuery(""); - setSuggestionAnchorPoint(null); - }, [search_space_id]); + // Atom is reconciled by ``handleEditorChange`` via the editor's + // onChange — no second write path here. + setMentionQuery(""); + setSuggestionAnchorPoint(null); + }, + [search_space_id] + ); useEffect(() => { const editor = editorRef.current; diff --git a/surfsense_web/components/assistant-ui/user-message.tsx b/surfsense_web/components/assistant-ui/user-message.tsx index b30db5f69..5c90dce55 100644 --- a/surfsense_web/components/assistant-ui/user-message.tsx +++ b/surfsense_web/components/assistant-ui/user-message.tsx @@ -104,9 +104,9 @@ const UserTextPart: FC = () => { const icon = isFolder ? ( <FolderIcon className="size-3.5" /> ) : isConnector ? ( - getConnectorIcon(segment.doc.connector_type, "size-3.5") ?? ( + (getConnectorIcon(segment.doc.connector_type, "size-3.5") ?? ( <Plug className="size-3.5" /> - ) + )) ) : ( getConnectorIcon(segment.doc.document_type ?? "UNKNOWN", "size-3.5") ); @@ -123,7 +123,9 @@ const UserTextPart: FC = () => { : segment.doc.title } onClick={ - isFolder || isConnector ? undefined : () => handleOpenDoc(segment.doc.id, segment.doc.title) + isFolder || isConnector + ? undefined + : () => handleOpenDoc(segment.doc.id, segment.doc.title) } className="mx-0.5" /> diff --git a/surfsense_web/components/editor-panel/editor-panel.tsx b/surfsense_web/components/editor-panel/editor-panel.tsx index 2baffaf0f..534ff9daa 100644 --- a/surfsense_web/components/editor-panel/editor-panel.tsx +++ b/surfsense_web/components/editor-panel/editor-panel.tsx @@ -34,6 +34,7 @@ import { useElectronAPI } from "@/hooks/use-platform"; import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils"; import { inferMonacoLanguageFromPath } from "@/lib/editor-language"; import { BACKEND_URL } from "@/lib/env-config"; + const PlateEditor = dynamic( () => import("@/components/editor/plate-editor").then((m) => ({ default: m.PlateEditor })), { ssr: false, loading: () => <EditorPanelSkeleton /> } diff --git a/surfsense_web/components/free-chat/anonymous-chat.tsx b/surfsense_web/components/free-chat/anonymous-chat.tsx index 28ee1f6f0..aff58f7bc 100644 --- a/surfsense_web/components/free-chat/anonymous-chat.tsx +++ b/surfsense_web/components/free-chat/anonymous-chat.tsx @@ -6,11 +6,12 @@ import { Button } from "@/components/ui/button"; import type { AnonModel, AnonQuotaResponse } from "@/contracts/types/anonymous-chat.types"; import { anonymousChatApiService } from "@/lib/apis/anonymous-chat-api.service"; import { readSSEStream } from "@/lib/chat/streaming-state"; +import { BACKEND_URL } from "@/lib/env-config"; import { trackAnonymousChatMessageSent } from "@/lib/posthog/events"; import { cn } from "@/lib/utils"; import { QuotaBar } from "./quota-bar"; import { QuotaWarningBanner } from "./quota-warning-banner"; -import { BACKEND_URL } from "@/lib/env-config"; + interface Message { id: string; role: "user" | "assistant"; @@ -80,19 +81,16 @@ export function AnonymousChat({ model }: AnonymousChatProps) { content: m.content, })); - const response = await fetch( - `${BACKEND_URL}/api/v1/public/anon-chat/stream`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ - model_slug: modelSlug, - messages: chatHistory, - }), - signal: controller.signal, - } - ); + const response = await fetch(`${BACKEND_URL}/api/v1/public/anon-chat/stream`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "include", + body: JSON.stringify({ + model_slug: modelSlug, + messages: chatHistory, + }), + signal: controller.signal, + }); if (!response.ok) { if (response.status === 429) { diff --git a/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx b/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx index 0aed0db61..b90a3b2a9 100644 --- a/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx +++ b/surfsense_web/components/layout/ui/icon-rail/SearchSpaceAvatar.tsx @@ -193,11 +193,7 @@ export function SearchSpaceAvatar({ // If delete or settings handlers are provided, expose them through a dropdown menu. if (onDelete || onSettings) { - const trigger = ( - <DropdownMenuTrigger asChild> - {avatarButton(true)} - </DropdownMenuTrigger> - ); + const trigger = <DropdownMenuTrigger asChild>{avatarButton(true)}</DropdownMenuTrigger>; return ( <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}> diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx index ca90ba9b9..757ee2fc2 100644 --- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx @@ -78,11 +78,11 @@ import { foldersApiService } from "@/lib/apis/folders-api.service"; import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service"; import { authenticatedFetch } from "@/lib/auth-utils"; import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; +import { BACKEND_URL } from "@/lib/env-config"; import { uploadFolderScan } from "@/lib/folder-sync-upload"; import { getSupportedExtensionsSet } from "@/lib/supported-extensions"; import { queries } from "@/zero/queries/index"; import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel"; -import { BACKEND_URL } from "@/lib/env-config"; const DesktopLocalTabContent = dynamic( () => import("./DesktopLocalTabContent").then((mod) => mod.DesktopLocalTabContent), diff --git a/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx b/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx index ef51eee3c..34cf707b0 100644 --- a/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx +++ b/surfsense_web/components/layout/ui/tabs/DocumentTabContent.tsx @@ -11,6 +11,7 @@ import { Button } from "@/components/ui/button"; import { Spinner } from "@/components/ui/spinner"; import { authenticatedFetch, getBearerToken, redirectToLogin } from "@/lib/auth-utils"; import { BACKEND_URL } from "@/lib/env-config"; + const LARGE_DOCUMENT_THRESHOLD = 2 * 1024 * 1024; // 2MB interface DocumentContent { diff --git a/surfsense_web/components/new-chat/composer-suggestion-popup.tsx b/surfsense_web/components/new-chat/composer-suggestion-popup.tsx index 3fdf48875..7a30f487b 100644 --- a/surfsense_web/components/new-chat/composer-suggestion-popup.tsx +++ b/surfsense_web/components/new-chat/composer-suggestion-popup.tsx @@ -117,7 +117,10 @@ const ComposerSuggestionItem = React.forwardRef< )); ComposerSuggestionItem.displayName = "ComposerSuggestionItem"; -function ComposerSuggestionSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) { +function ComposerSuggestionSeparator({ + className, + ...props +}: React.ComponentProps<typeof Separator>) { return ( <div className={cn("my-0.5 px-2.5", className)}> <Separator className="bg-popover-border" {...props} /> diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index c26e51922..c2a0f1665 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -2,6 +2,7 @@ import { useQuery as useZeroQuery } from "@rocicorp/zero/react"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; import { BookOpen, ChevronLeft, @@ -22,7 +23,6 @@ import { useState, } from "react"; import type { MentionedDocumentInfo } from "@/atoms/chat/mentioned-documents.atom"; -import { useAtomValue } from "jotai"; import { connectorsAtom } from "@/atoms/connectors/connector-query.atoms"; import { getConnectorTitle } from "@/components/assistant-ui/connector-popup/constants/connector-constants"; import { getConnectorDisplayName } from "@/components/assistant-ui/connector-popup/tabs/all-connectors-tab"; @@ -178,7 +178,9 @@ function useDebounced<T>(value: T, delay = DEBOUNCE_MS) { return debounced; } -function makeDocMention(doc: Pick<Document, "id" | "title" | "document_type">): MentionedDocumentInfo { +function makeDocMention( + doc: Pick<Document, "id" | "title" | "document_type"> +): MentionedDocumentInfo { return { id: doc.id, title: doc.title, @@ -187,9 +189,10 @@ function makeDocMention(doc: Pick<Document, "id" | "title" | "document_type">): }; } -function makeFolderMention( - folder: { id: number; title: string } -): Extract<MentionedDocumentInfo, { kind: "folder" }> { +function makeFolderMention(folder: { + id: number; + title: string; +}): Extract<MentionedDocumentInfo, { kind: "folder" }> { return { id: folder.id, title: folder.title, @@ -319,24 +322,24 @@ export const DocumentMentionPicker = forwardRef< useEffect(() => { if (currentPage !== 0) return; - const combinedDocs: Pick<Document, "id" | "title" | "document_type">[] = []; + const combinedDocs: Pick<Document, "id" | "title" | "document_type">[] = []; - if (surfsenseDocs?.items) { - for (const doc of surfsenseDocs.items) { - combinedDocs.push({ - id: doc.id, - title: doc.title, - document_type: "SURFSENSE_DOCS", - }); - } + if (surfsenseDocs?.items) { + for (const doc of surfsenseDocs.items) { + combinedDocs.push({ + id: doc.id, + title: doc.title, + document_type: "SURFSENSE_DOCS", + }); } + } - if (titleSearchResults?.items) { - combinedDocs.push(...titleSearchResults.items); - setHasMore(titleSearchResults.has_more); - } + if (titleSearchResults?.items) { + combinedDocs.push(...titleSearchResults.items); + setHasMore(titleSearchResults.has_more); + } - setAccumulatedDocuments(filterBySearchTerm(combinedDocs)); + setAccumulatedDocuments(filterBySearchTerm(combinedDocs)); }, [titleSearchResults, surfsenseDocs, currentPage, filterBySearchTerm]); const loadNextPage = useCallback(async () => { @@ -352,9 +355,11 @@ export const DocumentMentionPicker = forwardRef< page_size: PAGE_SIZE, ...(isSearchValid ? { title: debouncedSearch.trim() } : {}), }; - const response: SearchDocumentTitlesResponse = await documentsApiService.searchDocumentTitles({ - queryParams, - }); + const response: SearchDocumentTitlesResponse = await documentsApiService.searchDocumentTitles( + { + queryParams, + } + ); setAccumulatedDocuments((prev) => [...prev, ...response.items]); setHasMore(response.has_more); @@ -431,7 +436,13 @@ export const DocumentMentionPicker = forwardRef< ) .filter((mention): mention is MentionedDocumentInfo => mention !== null) .slice(0, RECENTS_LIMIT), - [activeConnectors, hasHydratedRecentDocs, recentMentions, recentValidationDocuments, zeroFolders] + [ + activeConnectors, + hasHydratedRecentDocs, + recentMentions, + recentValidationDocuments, + zeroFolders, + ] ); const selectedKeys = useMemo( @@ -460,47 +471,46 @@ export const DocumentMentionPicker = forwardRef< [visibleRecentMentions, selectedKeys] ); - const rootNodes = useMemo<ComposerSuggestionNode<ResourceNodeValue>[]>( - () => { - const nodes: ComposerSuggestionNode<ResourceNodeValue>[] = [...recentRootNodes]; - if (showSurfsenseDocsRoot) { - nodes.push({ - id: "surfsense-docs", - label: "SurfSense Docs", - subtitle: "Browse product documentation", - icon: <BookOpen className="size-4" />, - type: "branch", - value: { kind: "view", view: { kind: "surfsense-docs" } }, - }); - } - nodes.push( - { - id: "files-folders", - label: "Files & Folders", - subtitle: "Browse your knowledge base", - icon: <Files className="size-4" />, - type: "branch", - value: { kind: "view", view: { kind: "files-folders" } }, - }, - { - id: "connectors", - label: "Connectors", - subtitle: activeConnectors.length - ? "Choose the exact account for tool use" - : "No connected accounts yet", + const rootNodes = useMemo<ComposerSuggestionNode<ResourceNodeValue>[]>(() => { + const nodes: ComposerSuggestionNode<ResourceNodeValue>[] = [...recentRootNodes]; + if (showSurfsenseDocsRoot) { + nodes.push({ + id: "surfsense-docs", + label: "SurfSense Docs", + subtitle: "Browse product documentation", + icon: <BookOpen className="size-4" />, + type: "branch", + value: { kind: "view", view: { kind: "surfsense-docs" } }, + }); + } + nodes.push( + { + id: "files-folders", + label: "Files & Folders", + subtitle: "Browse your knowledge base", + icon: <Files className="size-4" />, + type: "branch", + value: { kind: "view", view: { kind: "files-folders" } }, + }, + { + id: "connectors", + label: "Connectors", + subtitle: activeConnectors.length + ? "Choose the exact account for tool use" + : "No connected accounts yet", icon: <Unplug className="size-4" />, - type: "branch", - disabled: activeConnectors.length === 0, - value: { kind: "view", view: { kind: "connectors" } }, - } - ); - return nodes; - }, - [activeConnectors.length, recentRootNodes, showSurfsenseDocsRoot] - ); + type: "branch", + disabled: activeConnectors.length === 0, + value: { kind: "view", view: { kind: "connectors" } }, + } + ); + return nodes; + }, [activeConnectors.length, recentRootNodes, showSurfsenseDocsRoot]); const searchNodes = useMemo<ComposerSuggestionNode<ResourceNodeValue>[]>(() => { - const searchLower = (isSingleCharSearch ? deferredSearch : debouncedSearch).trim().toLowerCase(); + const searchLower = (isSingleCharSearch ? deferredSearch : debouncedSearch) + .trim() + .toLowerCase(); const docNodes = actualDocuments.map((doc) => { const mention = makeDocMention(doc); return { @@ -619,7 +629,9 @@ export const DocumentMentionPicker = forwardRef< id: getMentionDocKey(mention), label: getConnectorDisplayName(connector.name), subtitle: `${view.title} account`, - icon: getConnectorIcon(connector.connector_type, "size-4") ?? <Unplug className="size-4" />, + icon: getConnectorIcon(connector.connector_type, "size-4") ?? ( + <Unplug className="size-4" /> + ), type: "item" as const, disabled: selectedKeys.has(getMentionDocKey(mention)), value: { kind: "mention" as const, mention }, @@ -733,7 +745,7 @@ export const DocumentMentionPicker = forwardRef< icon={ <span className="-ml-0.5 flex size-4.5 items-center justify-center"> <ChevronLeft className="size-3.5" /> - </span> + </span> } > <span className="flex-1 truncate">{title}</span> @@ -759,7 +771,7 @@ export const DocumentMentionPicker = forwardRef< return ( <Fragment key={node.id}> {showRecentsSeparator ? <ComposerSuggestionSeparator /> : null} - <ComposerSuggestionItem + <ComposerSuggestionItem ref={navigator.getItemRef(index)} icon={node.icon} selected={index === navigator.highlightedIndex} @@ -776,11 +788,11 @@ export const DocumentMentionPicker = forwardRef< {node.subtitle} </span> ) : null} - </span> + </span> {node.type === "branch" ? ( <ChevronRight className="size-3.5 shrink-0 text-muted-foreground" /> ) : null} - </ComposerSuggestionItem> + </ComposerSuggestionItem> </Fragment> ); })} diff --git a/surfsense_web/components/new-chat/prompt-picker.tsx b/surfsense_web/components/new-chat/prompt-picker.tsx index 986a5d608..65bf0a889 100644 --- a/surfsense_web/components/new-chat/prompt-picker.tsx +++ b/surfsense_web/components/new-chat/prompt-picker.tsx @@ -129,7 +129,9 @@ export const PromptPicker = forwardRef<PromptPickerRef, PromptPickerProps>(funct {isLoading ? ( <ComposerSuggestionSkeleton rows={8} mobileRows={8} /> ) : isError ? ( - <ComposerSuggestionMessage variant="destructive">Failed to load prompts</ComposerSuggestionMessage> + <ComposerSuggestionMessage variant="destructive"> + Failed to load prompts + </ComposerSuggestionMessage> ) : filtered.length === 0 ? ( <ComposerSuggestionMessage>No matching prompts</ComposerSuggestionMessage> ) : ( diff --git a/surfsense_web/components/settings/general-settings-manager.tsx b/surfsense_web/components/settings/general-settings-manager.tsx index 23398ad4d..a308acfad 100644 --- a/surfsense_web/components/settings/general-settings-manager.tsx +++ b/surfsense_web/components/settings/general-settings-manager.tsx @@ -12,9 +12,9 @@ import { Label } from "@/components/ui/label"; import { Skeleton } from "@/components/ui/skeleton"; import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service"; import { authenticatedFetch } from "@/lib/auth-utils"; +import { BACKEND_URL } from "@/lib/env-config"; import { cacheKeys } from "@/lib/query-client/cache-keys"; import { Spinner } from "../ui/spinner"; -import { BACKEND_URL } from "@/lib/env-config"; interface GeneralSettingsManagerProps { searchSpaceId: number; diff --git a/surfsense_web/components/settings/prompt-config-manager.tsx b/surfsense_web/components/settings/prompt-config-manager.tsx index 71cfcb971..997749a2a 100644 --- a/surfsense_web/components/settings/prompt-config-manager.tsx +++ b/surfsense_web/components/settings/prompt-config-manager.tsx @@ -20,10 +20,7 @@ interface PromptConfigManagerProps { } export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps) { - const { - data: searchSpace, - isLoading: loading, - } = useQuery({ + const { data: searchSpace, isLoading: loading } = useQuery({ queryKey: cacheKeys.searchSpaces.detail(searchSpaceId.toString()), queryFn: () => searchSpacesApiService.getSearchSpace({ id: searchSpaceId }), enabled: !!searchSpaceId, @@ -56,8 +53,7 @@ export function PromptConfigManager({ searchSpaceId }: PromptConfigManagerProps) }); toast.success("System instructions saved successfully"); } catch (error: unknown) { - const message = - error instanceof Error ? error.message : "Failed to save system instructions"; + const message = error instanceof Error ? error.message : "Failed to save system instructions"; console.error("Error saving system instructions:", error); toast.error(message); } diff --git a/surfsense_web/components/tool-ui/generate-podcast.tsx b/surfsense_web/components/tool-ui/generate-podcast.tsx index 866c0082d..2a62785e8 100644 --- a/surfsense_web/components/tool-ui/generate-podcast.tsx +++ b/surfsense_web/components/tool-ui/generate-podcast.tsx @@ -16,6 +16,7 @@ import { baseApiService } from "@/lib/apis/base-api.service"; import { authenticatedFetch } from "@/lib/auth-utils"; import { clearActivePodcastTaskId, setActivePodcastTaskId } from "@/lib/chat/podcast-state"; import { BACKEND_URL } from "@/lib/env-config"; + /** * Zod schemas for runtime validation */ @@ -193,10 +194,10 @@ function PodcastPlayer({ } else { // Authenticated view - fetch audio and details in parallel const [audioResponse, details] = await Promise.all([ - authenticatedFetch( - `${BACKEND_URL}/api/v1/podcasts/${podcastId}/audio`, - { method: "GET", signal: controller.signal } - ), + authenticatedFetch(`${BACKEND_URL}/api/v1/podcasts/${podcastId}/audio`, { + method: "GET", + signal: controller.signal, + }), baseApiService.get<unknown>(`/api/v1/podcasts/${podcastId}`), ]); diff --git a/surfsense_web/components/tool-ui/video-presentation/generate-video-presentation.tsx b/surfsense_web/components/tool-ui/video-presentation/generate-video-presentation.tsx index 00f9db23f..1db8dabb0 100644 --- a/surfsense_web/components/tool-ui/video-presentation/generate-video-presentation.tsx +++ b/surfsense_web/components/tool-ui/video-presentation/generate-video-presentation.tsx @@ -10,6 +10,7 @@ import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { Button } from "@/components/ui/button"; import { baseApiService } from "@/lib/apis/base-api.service"; import { authenticatedFetch } from "@/lib/auth-utils"; +import { BACKEND_URL } from "@/lib/env-config"; import { compileCheck, compileToComponent } from "@/lib/remotion/compile-check"; import { FPS } from "@/lib/remotion/constants"; import { @@ -19,7 +20,6 @@ import { type CompiledSlide, } from "./combined-player"; import { getPptxExportErrorToast, getVideoDownloadErrorToast } from "./errors"; -import { BACKEND_URL } from "@/lib/env-config"; const GenerateVideoPresentationArgsSchema = z.object({ source_content: z.string(), diff --git a/surfsense_web/hooks/use-search-source-connectors.ts b/surfsense_web/hooks/use-search-source-connectors.ts index bc1ec49b5..ad0db3de6 100644 --- a/surfsense_web/hooks/use-search-source-connectors.ts +++ b/surfsense_web/hooks/use-search-source-connectors.ts @@ -107,9 +107,7 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?: setError(null); // Build URL with optional search_space_id query parameter - const url = new URL( - `${BACKEND_URL}/api/v1/search-source-connectors` - ); + const url = new URL(`${BACKEND_URL}/api/v1/search-source-connectors`); if (spaceId !== undefined) { url.searchParams.append("search_space_id", spaceId.toString()); } @@ -169,9 +167,7 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?: ) => { try { // Add search_space_id as a query parameter - const url = new URL( - `${BACKEND_URL}/api/v1/search-source-connectors` - ); + const url = new URL(`${BACKEND_URL}/api/v1/search-source-connectors`); url.searchParams.append("search_space_id", spaceId.toString()); const response = await authenticatedFetch(url.toString(), { @@ -283,9 +279,7 @@ export const useSearchSourceConnectors = (lazy: boolean = false, searchSpaceId?: } const response = await authenticatedFetch( - `${ - BACKEND_URL - }/api/v1/search-source-connectors/${connectorId}/index?${params.toString()}`, + `${BACKEND_URL}/api/v1/search-source-connectors/${connectorId}/index?${params.toString()}`, { method: "POST", headers: { "Content-Type": "application/json" }, diff --git a/surfsense_web/lib/auth-utils.ts b/surfsense_web/lib/auth-utils.ts index 645a6d1ba..b7dab7717 100644 --- a/surfsense_web/lib/auth-utils.ts +++ b/surfsense_web/lib/auth-utils.ts @@ -2,6 +2,7 @@ * Authentication utilities for handling token expiration and redirects */ import { BACKEND_URL } from "@/lib/env-config"; + const REDIRECT_PATH_KEY = "surfsense_redirect_path"; const BEARER_TOKEN_KEY = "surfsense_bearer_token"; const REFRESH_TOKEN_KEY = "surfsense_refresh_token"; diff --git a/surfsense_web/lib/posthog/events.ts b/surfsense_web/lib/posthog/events.ts index 687d589f9..a584f9b6f 100644 --- a/surfsense_web/lib/posthog/events.ts +++ b/surfsense_web/lib/posthog/events.ts @@ -1,6 +1,6 @@ +import type { ChatErrorKind, ChatErrorSeverity, ChatFlow } from "@/lib/chat/chat-error-classifier"; import type { ConnectorTelemetryMeta } from "@/lib/connector-telemetry"; import { getConnectorTelemetryMeta } from "@/lib/connector-telemetry"; -import type { ChatErrorKind, ChatErrorSeverity, ChatFlow } from "@/lib/chat/chat-error-classifier"; /** * PostHog Analytics Event Definitions From c601a9b102d491f7a4b7125ca0df80922289deca Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" <vermarohanfinal@gmail.com> Date: Thu, 28 May 2026 19:22:54 -0700 Subject: [PATCH 155/198] fix: biome errs --- surfsense_web/components/ads/ad-unit.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/surfsense_web/components/ads/ad-unit.tsx b/surfsense_web/components/ads/ad-unit.tsx index 5f5860607..cc5848a00 100644 --- a/surfsense_web/components/ads/ad-unit.tsx +++ b/surfsense_web/components/ads/ad-unit.tsx @@ -52,7 +52,8 @@ export function AdUnit({ // sets data-adsbygoogle-status="done" once it has filled a slot. if (el.getAttribute("data-adsbygoogle-status")) return; try { - (window.adsbygoogle = window.adsbygoogle || []).push({}); + window.adsbygoogle = window.adsbygoogle || []; + window.adsbygoogle.push({}); } catch { // AdSense throws if pushed before the script has loaded or on // duplicate pushes. The script processes pending pushes when it From d013617bf60e5528a384386505d31fe557be0a93 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" <vermarohanfinal@gmail.com> Date: Thu, 28 May 2026 21:26:32 -0700 Subject: [PATCH 156/198] feat(automations): added UI and improved mentions - Added support for @-mentions in agent tasks, allowing users to reference documents, folders, and connectors directly in their queries. - Updated `run_agent_task` to resolve mentions and include them in the context passed to the agent. - Introduced new parameters in `AgentTaskActionParams` for handling mentioned document and connector IDs. - Refactored the automation edit and new components to utilize the new `AutomationBuilderForm` for a more streamlined user experience. - Removed deprecated JSON forms to simplify the automation creation process. --- .../automations/actions/agent_task/factory.py | 5 + .../automations/actions/agent_task/invoke.py | 132 ++++- .../automations/actions/agent_task/params.py | 31 ++ .../edit/automation-edit-content.tsx | 10 +- .../edit/components/automation-edit-form.tsx | 118 ----- .../components/automation-edit-header.tsx | 31 ++ .../components/builder/advanced-section.tsx | 129 +++++ .../builder/automation-builder-form.tsx | 459 ++++++++++++++++++ .../components/builder/basics-section.tsx | 42 ++ .../components/builder/builder-summary.tsx | 96 ++++ .../components/builder/form-field.tsx | 42 ++ .../components/builder/json-mode-panel.tsx | 51 ++ .../components/builder/mention-task-input.tsx | 258 ++++++++++ .../components/builder/schedule-section.tsx | 275 +++++++++++ .../components/builder/task-item.tsx | 136 ++++++ .../components/builder/task-list.tsx | 65 +++ .../components/builder/timezone-combobox.tsx | 71 +++ .../components/builder/unattended-toggle.tsx | 47 ++ .../new/automation-new-content.tsx | 12 +- .../new/components/automation-json-form.tsx | 98 ---- .../new/components/automation-new-header.tsx | 7 +- .../new-chat/document-mention-picker.tsx | 24 +- .../lib/automations/builder-schema.ts | 456 +++++++++++++++++ .../lib/automations/default-template.ts | 44 -- .../lib/automations/schedule-builder.ts | 132 +++++ 25 files changed, 2490 insertions(+), 281 deletions(-) delete mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-header.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/advanced-section.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/basics-section.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/builder-summary.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/form-field.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/json-mode-panel.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/schedule-section.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-item.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-list.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/timezone-combobox.tsx create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/unattended-toggle.tsx delete mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx create mode 100644 surfsense_web/lib/automations/builder-schema.ts delete mode 100644 surfsense_web/lib/automations/default-template.ts create mode 100644 surfsense_web/lib/automations/schedule-builder.ts diff --git a/surfsense_backend/app/automations/actions/agent_task/factory.py b/surfsense_backend/app/automations/actions/agent_task/factory.py index 18a408e13..dec75dce8 100644 --- a/surfsense_backend/app/automations/actions/agent_task/factory.py +++ b/surfsense_backend/app/automations/actions/agent_task/factory.py @@ -18,6 +18,11 @@ def build_handler(ctx: ActionContext) -> ActionHandler: ctx=ctx, query=validated.query, auto_approve_all=validated.auto_approve_all, + mentioned_document_ids=validated.mentioned_document_ids, + mentioned_folder_ids=validated.mentioned_folder_ids, + mentioned_connector_ids=validated.mentioned_connector_ids, + mentioned_connectors=validated.mentioned_connectors, + mentioned_documents=validated.mentioned_documents, ) return handle diff --git a/surfsense_backend/app/automations/actions/agent_task/invoke.py b/surfsense_backend/app/automations/actions/agent_task/invoke.py index 6cc92b232..fa02d263f 100644 --- a/surfsense_backend/app/automations/actions/agent_task/invoke.py +++ b/surfsense_backend/app/automations/actions/agent_task/invoke.py @@ -8,9 +8,13 @@ from typing import Any from langchain_core.messages import HumanMessage from langgraph.types import Command +from sqlalchemy.ext.asyncio import AsyncSession from app.agents.multi_agent_chat import create_multi_agent_chat_deep_agent +from app.agents.new_chat.context import SurfSenseContextSchema +from app.agents.new_chat.mention_resolver import resolve_mentions, substitute_in_text from app.db import ChatVisibility, async_session_maker +from app.schemas.new_chat import MentionedDocumentInfo from ..types import ActionContext from .auto_decide import build_auto_decisions @@ -22,17 +26,118 @@ from .finalize import extract_final_assistant_message _MAX_RESUMES = 50 +def _build_connector_block(connectors: list[dict[str, Any]]) -> str | None: + """Render the ``<mentioned_connectors>`` context block (same shape as chat). + + Mirrors ``stream_new_chat`` so the agent gets the exact connector accounts + the user picked. Returns ``None`` when nothing renders. + """ + lines: list[str] = [] + for connector in connectors: + connector_id = connector.get("id") + connector_type = connector.get("connector_type") or connector.get( + "document_type" + ) + account_name = connector.get("account_name") or connector.get("title") + if connector_id is None or connector_type is None: + continue + lines.append( + f' - connector_id={connector_id}, connector_type="{connector_type}", ' + f'account_name="{account_name or ""}"' + ) + if not lines: + return None + return ( + "<mentioned_connectors>\n" + "The user selected these exact connector accounts with @. " + "These entries are selection metadata, not retrieved connector content. " + "When a connector-backed tool needs an account, use the matching " + "connector_id from this list if the tool supports connector_id:\n" + + "\n".join(lines) + + "\n</mentioned_connectors>" + ) + + +async def _resolve_mention_context( + session: AsyncSession, + *, + search_space_id: int, + query: str, + mentioned_document_ids: list[int] | None, + mentioned_folder_ids: list[int] | None, + mentioned_connector_ids: list[int] | None, + mentioned_connectors: list[MentionedDocumentInfo] | None, + mentioned_documents: list[MentionedDocumentInfo] | None, +) -> tuple[str, SurfSenseContextSchema | None]: + """Resolve @-mentions into a rewritten query + per-invocation context. + + Automation always runs in cloud filesystem mode, so we mirror the chat + ``new_chat`` flow: substitute ``@title`` tokens with canonical + ``/documents/...`` paths, prepend a ``<mentioned_connectors>`` block, and + build a ``SurfSenseContextSchema`` that ``KnowledgePriorityMiddleware`` + reads via ``runtime.context``. Returns ``(query, None)`` unchanged when + there are no mentions. + """ + has_mentions = bool( + mentioned_document_ids + or mentioned_folder_ids + or mentioned_connector_ids + or mentioned_connectors + or mentioned_documents + ) + if not has_mentions: + return query, None + + resolved = await resolve_mentions( + session, + search_space_id=search_space_id, + mentioned_documents=mentioned_documents, + mentioned_document_ids=mentioned_document_ids, + mentioned_folder_ids=mentioned_folder_ids, + ) + agent_query = substitute_in_text(query, resolved.token_to_path) + + # ``SurfSenseContextSchema.mentioned_connectors`` is typed ``list[dict]`` and + # the connector block reads dicts, so dump the pydantic chips once. + connector_dicts = [c.model_dump() for c in (mentioned_connectors or [])] + connector_block = _build_connector_block(connector_dicts) + if connector_block: + agent_query = f"{connector_block}\n\n<user_query>{agent_query}</user_query>" + + runtime_context = SurfSenseContextSchema( + search_space_id=search_space_id, + mentioned_document_ids=list( + resolved.mentioned_document_ids or (mentioned_document_ids or []) + ), + mentioned_folder_ids=list( + resolved.mentioned_folder_ids or (mentioned_folder_ids or []) + ), + mentioned_connector_ids=list(mentioned_connector_ids or []), + mentioned_connectors=connector_dicts, + ) + return agent_query, runtime_context + + async def run_agent_task( *, ctx: ActionContext, query: str, auto_approve_all: bool, + mentioned_document_ids: list[int] | None = None, + mentioned_folder_ids: list[int] | None = None, + mentioned_connector_ids: list[int] | None = None, + mentioned_connectors: list[MentionedDocumentInfo] | None = None, + mentioned_documents: list[MentionedDocumentInfo] | None = None, ) -> dict[str, Any]: """Invoke multi_agent_chat for one rendered query and return its outcome. Opens its own DB session so the executor's bookkeeping session isn't tied up for the entire invocation. The LangGraph ``thread_id`` (a fresh UUID) is returned as ``agent_session_id`` for later inspection. + + @-mentions (files / folders / connectors) chosen in the task input are + resolved the same way the chat flow does and forwarded to the agent via the + per-invocation ``context`` so they actually scope retrieval. """ agent_session_id = str(uuid.uuid4()) user_id = str(ctx.creator_user_id) if ctx.creator_user_id else None @@ -55,12 +160,24 @@ async def run_agent_task( agent_config=deps.agent_config, firecrawl_api_key=deps.firecrawl_api_key, thread_visibility=ChatVisibility.PRIVATE, + mentioned_document_ids=mentioned_document_ids, + ) + + agent_query, runtime_context = await _resolve_mention_context( + agent_session, + search_space_id=ctx.search_space_id, + query=query, + mentioned_document_ids=mentioned_document_ids, + mentioned_folder_ids=mentioned_folder_ids, + mentioned_connector_ids=mentioned_connector_ids, + mentioned_connectors=mentioned_connectors, + mentioned_documents=mentioned_documents, ) request_id = f"automation:{ctx.run_id}:{ctx.step_id}" turn_id = f"{request_id}:{int(time.time() * 1000)}" input_state: dict[str, Any] = { - "messages": [HumanMessage(content=query)], + "messages": [HumanMessage(content=agent_query)], "search_space_id": ctx.search_space_id, "request_id": request_id, "turn_id": turn_id, @@ -73,8 +190,17 @@ async def run_agent_task( }, "recursion_limit": 10_000, } + if runtime_context is not None: + runtime_context.request_id = request_id + runtime_context.turn_id = turn_id - result = await agent.ainvoke(input_state, config=config) + # The compiled graph declares ``context_schema=SurfSenseContextSchema``; + # mentions only reach ``KnowledgePriorityMiddleware`` via ``context=``. + invoke_kwargs: dict[str, Any] = {"config": config} + if runtime_context is not None: + invoke_kwargs["context"] = runtime_context + + result = await agent.ainvoke(input_state, **invoke_kwargs) resumes = 0 while True: @@ -87,7 +213,7 @@ async def run_agent_task( ) lg_resume_map, routed = build_auto_decisions(state, decision) config["configurable"]["surfsense_resume_value"] = routed - result = await agent.ainvoke(Command(resume=lg_resume_map), config=config) + result = await agent.ainvoke(Command(resume=lg_resume_map), **invoke_kwargs) resumes += 1 return { diff --git a/surfsense_backend/app/automations/actions/agent_task/params.py b/surfsense_backend/app/automations/actions/agent_task/params.py index b0e99a78b..ad6f35edb 100644 --- a/surfsense_backend/app/automations/actions/agent_task/params.py +++ b/surfsense_backend/app/automations/actions/agent_task/params.py @@ -4,6 +4,8 @@ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field +from app.schemas.new_chat import MentionedDocumentInfo + class AgentTaskActionParams(BaseModel): """Run a multi_agent_chat turn from an automation step.""" @@ -19,3 +21,32 @@ class AgentTaskActionParams(BaseModel): default=False, description="If true, every HITL approval is auto-approved; otherwise rejected.", ) + + # @-mention references chosen in the task input. Mirror the ``new_chat`` + # request fields (minus SurfSense product docs) so the run can scope + # retrieval to the user's selected files / folders / connectors. All + # optional and additive; a task with no mentions behaves as before. + mentioned_document_ids: list[int] | None = Field( + default=None, + description="Knowledge-base document IDs the task references with @.", + ) + mentioned_folder_ids: list[int] | None = Field( + default=None, + description="Knowledge-base folder IDs the task references with @.", + ) + mentioned_connector_ids: list[int] | None = Field( + default=None, + description="Concrete connector account IDs the task references with @.", + ) + mentioned_connectors: list[MentionedDocumentInfo] | None = Field( + default=None, + description="Display/context metadata for the @-mentioned connector accounts.", + ) + mentioned_documents: list[MentionedDocumentInfo] | None = Field( + default=None, + description=( + "Chip metadata (id, title, kind, ...) for every @-mention so the " + "run can resolve titles to virtual paths and substitute them in " + "the query." + ), + ) diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx index 6504af5a4..2c9db217d 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/automation-edit-content.tsx @@ -1,10 +1,11 @@ "use client"; import { ShieldAlert } from "lucide-react"; import { useAutomation } from "@/hooks/use-automation"; +import { AutomationBuilderForm } from "../../components/builder/automation-builder-form"; import { useAutomationPermissions } from "../../hooks/use-automation-permissions"; import { AutomationDetailLoading } from "../components/automation-detail-loading"; import { AutomationNotFound } from "../components/automation-not-found"; -import { AutomationEditForm } from "./components/automation-edit-form"; +import { AutomationEditHeader } from "./components/automation-edit-header"; interface AutomationEditContentProps { searchSpaceId: number; @@ -49,5 +50,10 @@ export function AutomationEditContent({ searchSpaceId, automationId }: Automatio return <AutomationNotFound searchSpaceId={searchSpaceId} error={error} />; } - return <AutomationEditForm automation={automation} searchSpaceId={searchSpaceId} />; + return ( + <> + <AutomationEditHeader automation={automation} searchSpaceId={searchSpaceId} /> + <AutomationBuilderForm mode="edit" searchSpaceId={searchSpaceId} automation={automation} /> + </> + ); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx deleted file mode 100644 index 9b950608e..000000000 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-form.tsx +++ /dev/null @@ -1,118 +0,0 @@ -"use client"; -import { useAtomValue } from "jotai"; -import { AlertCircle, ArrowLeft, Save } from "lucide-react"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { useState } from "react"; -import { updateAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; -import { JsonView } from "@/components/json-view"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Spinner } from "@/components/ui/spinner"; -import { type Automation, automationUpdateRequest } from "@/contracts/types/automation.types"; - -interface AutomationEditFormProps { - automation: Automation; - searchSpaceId: number; -} - -/** - * Edit-existing-automation form. Surfaces the four mutable fields - * (name, description, status, definition) as one editable JSON tree; - * triggers stay on the detail page where they have their own management - * UI. Validates with the same Zod schema the API expects, then PATCHes - * the changed shape back. - */ -export function AutomationEditForm({ automation, searchSpaceId }: AutomationEditFormProps) { - const router = useRouter(); - const { mutateAsync: updateAutomation, isPending } = useAtomValue(updateAutomationMutationAtom); - const detailHref = `/dashboard/${searchSpaceId}/automations/${automation.id}`; - - const [value, setValue] = useState(() => ({ - name: automation.name, - description: automation.description ?? null, - status: automation.status, - definition: automation.definition, - })); - const [issues, setIssues] = useState<string[]>([]); - - async function handleSave() { - setIssues([]); - const result = automationUpdateRequest.safeParse(value); - if (!result.success) { - setIssues( - result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`) - ); - return; - } - try { - await updateAutomation({ automationId: automation.id, patch: result.data }); - router.push(detailHref); - } catch (err) { - setIssues([(err as Error).message ?? "Update failed"]); - } - } - - return ( - <> - <div className="space-y-3"> - <Button asChild variant="ghost" size="sm" className="-ml-2 h-auto px-2 py-1"> - <Link href={detailHref} className="text-xs text-muted-foreground"> - <ArrowLeft className="mr-1.5 h-3.5 w-3.5" /> - Back to automation - </Link> - </Button> - <div> - <h1 className="text-xl md:text-2xl font-semibold text-foreground break-words"> - Edit automation - </h1> - <p className="text-sm text-muted-foreground mt-1">{automation.name}</p> - </div> - </div> - - <Card className="border-border/60 bg-accent"> - <CardHeader className="pb-4"> - <CardTitle className="text-base font-semibold">Definition</CardTitle> - </CardHeader> - <CardContent className="space-y-4"> - <div className="rounded-md border border-input bg-background px-3 py-2 max-h-[36rem] overflow-auto"> - <JsonView - src={value} - editable - onChange={(next) => setValue(next as typeof value)} - collapsed={false} - /> - </div> - - {issues.length > 0 && ( - <div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2"> - <div className="flex items-center gap-1.5 text-xs font-medium text-destructive mb-1.5"> - <AlertCircle className="h-3.5 w-3.5" aria-hidden /> - {issues.length === 1 ? "1 issue" : `${issues.length} issues`} - </div> - <ul className="space-y-0.5 text-xs text-destructive list-disc list-inside"> - {issues.map((issue) => ( - <li key={issue}>{issue}</li> - ))} - </ul> - </div> - )} - - <div className="flex items-center justify-end gap-2"> - <Button asChild type="button" variant="ghost" size="sm"> - <Link href={detailHref}>Cancel</Link> - </Button> - <Button type="button" onClick={handleSave} disabled={isPending} size="sm"> - {isPending ? ( - <Spinner size="xs" className="mr-2" /> - ) : ( - <Save className="mr-2 h-4 w-4" /> - )} - Save changes - </Button> - </div> - </CardContent> - </Card> - </> - ); -} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-header.tsx new file mode 100644 index 000000000..6b2a31822 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/[automation_id]/edit/components/automation-edit-header.tsx @@ -0,0 +1,31 @@ +"use client"; +import { ArrowLeft } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import type { Automation } from "@/contracts/types/automation.types"; + +interface AutomationEditHeaderProps { + automation: Automation; + searchSpaceId: number; +} + +export function AutomationEditHeader({ automation, searchSpaceId }: AutomationEditHeaderProps) { + const detailHref = `/dashboard/${searchSpaceId}/automations/${automation.id}`; + + return ( + <div className="space-y-3"> + <Button asChild variant="ghost" size="sm" className="-ml-2 h-auto px-2 py-1"> + <Link href={detailHref} className="text-xs text-muted-foreground"> + <ArrowLeft className="mr-1.5 h-3.5 w-3.5" /> + Back to automation + </Link> + </Button> + <div> + <h1 className="text-xl md:text-2xl font-semibold text-foreground wrap-break-word"> + Edit automation + </h1> + <p className="text-sm text-muted-foreground mt-1">{automation.name}</p> + </div> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/advanced-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/advanced-section.tsx new file mode 100644 index 000000000..740f199af --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/advanced-section.tsx @@ -0,0 +1,129 @@ +"use client"; +import { useState } from "react"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import type { BuilderExecution } from "@/lib/automations/builder-schema"; +import { Field } from "./form-field"; + +interface AdvancedSectionProps { + execution: BuilderExecution; + tags: string[]; + onExecutionChange: (patch: Partial<BuilderExecution>) => void; + onTagsChange: (tags: string[]) => void; +} + +const BACKOFF_OPTIONS: ReadonlyArray<{ value: BuilderExecution["retryBackoff"]; label: string }> = [ + { value: "exponential", label: "Exponential" }, + { value: "linear", label: "Linear" }, + { value: "none", label: "None" }, +]; + +const CONCURRENCY_OPTIONS: ReadonlyArray<{ + value: BuilderExecution["concurrency"]; + label: string; +}> = [ + { value: "drop_if_running", label: "Skip if already running" }, + { value: "queue", label: "Queue the next run" }, + { value: "always", label: "Always run" }, +]; + +function clampInt(raw: string, min: number, fallback: number): number { + const value = Number.parseInt(raw, 10); + if (Number.isNaN(value)) return fallback; + return Math.max(min, value); +} + +export function AdvancedSection({ + execution, + tags, + onExecutionChange, + onTagsChange, +}: AdvancedSectionProps) { + const [tagsText, setTagsText] = useState(tags.join(", ")); + + function commitTags(text: string) { + const next = text + .split(",") + .map((tag) => tag.trim()) + .filter(Boolean); + onTagsChange(next); + } + + return ( + <div className="space-y-4"> + <div className="grid grid-cols-1 gap-3 sm:grid-cols-2"> + <Field label="Timeout (seconds)" hint="Wall-clock cap for the whole run."> + <Input + type="number" + min={1} + value={execution.timeoutSeconds} + onChange={(e) => + onExecutionChange({ timeoutSeconds: clampInt(e.target.value, 1, 600) }) + } + /> + </Field> + <Field label="Max retries" hint="Per-step retry budget."> + <Input + type="number" + min={0} + value={execution.maxRetries} + onChange={(e) => onExecutionChange({ maxRetries: clampInt(e.target.value, 0, 2) })} + /> + </Field> + <Field label="Retry backoff"> + <Select + value={execution.retryBackoff} + onValueChange={(value) => + onExecutionChange({ retryBackoff: value as BuilderExecution["retryBackoff"] }) + } + > + <SelectTrigger className="w-full"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + {BACKOFF_OPTIONS.map((option) => ( + <SelectItem key={option.value} value={option.value}> + {option.label} + </SelectItem> + ))} + </SelectContent> + </Select> + </Field> + <Field label="If already running"> + <Select + value={execution.concurrency} + onValueChange={(value) => + onExecutionChange({ concurrency: value as BuilderExecution["concurrency"] }) + } + > + <SelectTrigger className="w-full"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + {CONCURRENCY_OPTIONS.map((option) => ( + <SelectItem key={option.value} value={option.value}> + {option.label} + </SelectItem> + ))} + </SelectContent> + </Select> + </Field> + </div> + + <Field label="Tags" hint="Comma-separated. Optional."> + <Input + value={tagsText} + placeholder="research, weekly" + onChange={(e) => setTagsText(e.target.value)} + onBlur={(e) => commitTags(e.target.value)} + /> + </Field> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx new file mode 100644 index 000000000..1fd37cd3d --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx @@ -0,0 +1,459 @@ +"use client"; +import { useAtomValue } from "jotai"; +import { Code2, LayoutList, Save } from "lucide-react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useMemo, useState } from "react"; +import type { z } from "zod"; +import { + addTriggerMutationAtom, + createAutomationMutationAtom, + removeTriggerMutationAtom, + updateAutomationMutationAtom, + updateTriggerMutationAtom, +} from "@/atoms/automations/automations-mutation.atoms"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Spinner } from "@/components/ui/spinner"; +import { + type Automation, + automationCreateRequest, + automationUpdateRequest, +} from "@/contracts/types/automation.types"; +import { + type BuilderForm, + buildCreatePayload, + builderFormSchema, + buildScheduleTrigger, + buildUpdatePayload, + createEmptyForm, + formFromAutomation, + type HydratableTrigger, + hydrateForm, +} from "@/lib/automations/builder-schema"; +import { cn } from "@/lib/utils"; +import { AdvancedSection } from "./advanced-section"; +import { BasicsSection } from "./basics-section"; +import { BuilderSummary } from "./builder-summary"; +import { JsonModePanel } from "./json-mode-panel"; +import { ScheduleSection } from "./schedule-section"; +import { TaskList } from "./task-list"; +import { UnattendedToggle } from "./unattended-toggle"; + +interface AutomationBuilderFormProps { + mode: "create" | "edit"; + searchSpaceId: number; + /** Required in edit mode; seeds the form and trigger reconciliation. */ + automation?: Automation; +} + +type Mode = "form" | "json"; + +function mapFormErrors(error: z.ZodError): Record<string, string> { + const out: Record<string, string> = {}; + for (const issue of error.issues) { + const path = issue.path; + let key: string; + if (path[0] === "tasks" && typeof path[1] === "number") key = `tasks.${path[1]}.query`; + else if (path[0] === "schedule") key = "schedule"; + else key = String(path[0] ?? "_root"); + if (!out[key]) out[key] = issue.message; + } + return out; +} + +export function AutomationBuilderForm({ + mode, + searchSpaceId, + automation, +}: AutomationBuilderFormProps) { + const router = useRouter(); + const { mutateAsync: createAutomation } = useAtomValue(createAutomationMutationAtom); + const { mutateAsync: updateAutomation } = useAtomValue(updateAutomationMutationAtom); + const { mutateAsync: addTrigger } = useAtomValue(addTriggerMutationAtom); + const { mutateAsync: updateTrigger } = useAtomValue(updateTriggerMutationAtom); + const { mutateAsync: removeTrigger } = useAtomValue(removeTriggerMutationAtom); + + // Initial state: create starts empty in form mode; edit hydrates, falling + // back to JSON mode when the definition can't be represented in the form. + const initial = useMemo(() => { + if (mode === "edit" && automation) { + const result = formFromAutomation(automation); + if (result.formable) { + return { mode: "form" as Mode, form: result.form, notice: undefined }; + } + return { + mode: "json" as Mode, + form: createEmptyForm(), + notice: `This automation ${result.reason}, which the form can't show. Edit it as JSON below.`, + }; + } + return { mode: "form" as Mode, form: createEmptyForm(), notice: undefined }; + }, [mode, automation]); + + const [activeMode, setActiveMode] = useState<Mode>(initial.mode); + const [form, setForm] = useState<BuilderForm>(initial.form); + const [errors, setErrors] = useState<Record<string, string>>({}); + const [rootError, setRootError] = useState<string | null>(null); + + const [jsonValue, setJsonValue] = useState<Record<string, unknown>>(() => + initial.mode === "json" ? jsonFromAutomation(automation) : {} + ); + const [jsonIssues, setJsonIssues] = useState<string[]>([]); + const [jsonNotice, setJsonNotice] = useState<string | undefined>(initial.notice); + + const [submitting, setSubmitting] = useState(false); + + const cancelHref = + mode === "edit" && automation + ? `/dashboard/${searchSpaceId}/automations/${automation.id}` + : `/dashboard/${searchSpaceId}/automations`; + + function patchForm(patch: Partial<BuilderForm>) { + setForm((prev) => ({ ...prev, ...patch })); + } + + function jsonFromCurrentForm(): Record<string, unknown> { + if (mode === "edit" && automation) { + return { ...buildUpdatePayload(form), status: automation.status }; + } + const { search_space_id: _ignored, ...rest } = buildCreatePayload(form, searchSpaceId); + return rest; + } + + function switchToJson() { + setJsonValue(jsonFromCurrentForm()); + setJsonIssues([]); + setJsonNotice(undefined); + setActiveMode("json"); + } + + function switchToForm() { + const result = tryJsonToForm(); + if (result.ok) { + setForm(result.form); + setErrors({}); + setRootError(null); + setActiveMode("form"); + return; + } + setJsonIssues(result.issues); + setJsonNotice(result.notice); + } + + function tryJsonToForm(): + | { ok: true; form: BuilderForm } + | { ok: false; issues: string[]; notice?: string } { + // Read the raw tree defensively rather than strict-validating: an + // incomplete JSON edit should still round-trip into the form, where the + // form's own validation enforces completeness on submit. + const definition = jsonValue.definition; + if (!definition || typeof definition !== "object") { + return { ok: false, issues: [], notice: "Add a definition before switching to the form." }; + } + + const name = + typeof jsonValue.name === "string" + ? jsonValue.name + : mode === "edit" && automation + ? automation.name + : ""; + const description = typeof jsonValue.description === "string" ? jsonValue.description : null; + const triggers = + mode === "edit" && automation + ? (automation.triggers ?? []) + : extractTriggers(jsonValue.triggers); + + const h = hydrateForm(name, description, definition, triggers); + return h.formable + ? { ok: true, form: h.form } + : { ok: false, issues: [], notice: `Can't show in the form: it ${h.reason}.` }; + } + + function validateForm(): Record<string, string> | null { + const result = builderFormSchema.safeParse(form); + const next = result.success ? {} : mapFormErrors(result.error); + + // The schedule model fields aren't deeply validated by the schema. + if (form.schedule?.mode === "preset") { + const m = form.schedule.model; + if (m.frequency === "weekly" && m.daysOfWeek.length === 0) { + next.schedule = "Pick at least one day for the weekly schedule"; + } + } else if (form.schedule?.mode === "cron" && !form.schedule.cron.trim()) { + next.schedule = "Enter a schedule expression"; + } + + return Object.keys(next).length > 0 ? next : null; + } + + async function reconcileTriggers(automationId: number) { + const desired = buildScheduleTrigger(form); + const existing = (automation?.triggers ?? [])[0]; + if (!existing && desired) { + await addTrigger({ automationId, payload: desired }); + } else if (existing && !desired) { + await removeTrigger({ automationId, triggerId: existing.id }); + } else if (existing && desired) { + await updateTrigger({ + automationId, + triggerId: existing.id, + patch: { params: desired.params, enabled: desired.enabled }, + }); + } + } + + async function submitForm() { + setRootError(null); + const formErrors = validateForm(); + if (formErrors) { + setErrors(formErrors); + return; + } + setErrors({}); + + setSubmitting(true); + try { + if (mode === "edit" && automation) { + const payload = buildUpdatePayload(form); + const parsed = automationUpdateRequest.safeParse(payload); + if (!parsed.success) { + setRootError(zodIssueList(parsed.error).join("; ")); + return; + } + await updateAutomation({ automationId: automation.id, patch: parsed.data }); + await reconcileTriggers(automation.id); + router.push(`/dashboard/${searchSpaceId}/automations/${automation.id}`); + } else { + const payload = buildCreatePayload(form, searchSpaceId); + const parsed = automationCreateRequest.safeParse(payload); + if (!parsed.success) { + setRootError(zodIssueList(parsed.error).join("; ")); + return; + } + const created = await createAutomation(parsed.data); + router.push(`/dashboard/${searchSpaceId}/automations/${created.id}`); + } + } catch (err) { + setRootError((err as Error).message ?? "Submit failed"); + } finally { + setSubmitting(false); + } + } + + async function submitJson() { + setJsonIssues([]); + setSubmitting(true); + try { + if (mode === "edit" && automation) { + const parsed = automationUpdateRequest.safeParse(jsonValue); + if (!parsed.success) { + setJsonIssues(zodIssueList(parsed.error)); + return; + } + await updateAutomation({ automationId: automation.id, patch: parsed.data }); + router.push(`/dashboard/${searchSpaceId}/automations/${automation.id}`); + } else { + const parsed = automationCreateRequest.safeParse({ + ...jsonValue, + search_space_id: searchSpaceId, + }); + if (!parsed.success) { + setJsonIssues(zodIssueList(parsed.error)); + return; + } + const created = await createAutomation(parsed.data); + router.push(`/dashboard/${searchSpaceId}/automations/${created.id}`); + } + } catch (err) { + setJsonIssues([(err as Error).message ?? "Submit failed"]); + } finally { + setSubmitting(false); + } + } + + const submitLabel = mode === "edit" ? "Save changes" : "Create automation"; + + return ( + <div className="space-y-4"> + <div className="flex items-center justify-end"> + <div className="inline-flex rounded-md border border-border/60 p-0.5"> + <ModeButton + active={activeMode === "form"} + icon={LayoutList} + label="Form" + onClick={() => (activeMode === "form" ? undefined : switchToForm())} + /> + <ModeButton + active={activeMode === "json"} + icon={Code2} + label="Edit as JSON" + onClick={() => (activeMode === "json" ? undefined : switchToJson())} + /> + </div> + </div> + + {activeMode === "json" ? ( + <Card className="border-border/60 bg-accent"> + <CardContent className="pt-6"> + <JsonModePanel + value={jsonValue} + issues={jsonIssues} + notice={jsonNotice} + onChange={setJsonValue} + /> + </CardContent> + </Card> + ) : ( + <div className="grid grid-cols-1 gap-4 lg:grid-cols-3"> + <div className="space-y-4 lg:col-span-2"> + <Card className="border-border/60 bg-accent"> + <CardHeader className="pb-3"> + <CardTitle className="text-sm font-semibold">Basics</CardTitle> + </CardHeader> + <CardContent> + <BasicsSection + name={form.name} + description={form.description} + errors={errors} + onChange={patchForm} + /> + </CardContent> + </Card> + + <Card className="border-border/60 bg-accent"> + <CardHeader className="pb-3"> + <CardTitle className="text-sm font-semibold">Tasks</CardTitle> + </CardHeader> + <CardContent className="space-y-4"> + <TaskList + tasks={form.tasks} + errors={errors} + searchSpaceId={searchSpaceId} + onChange={(tasks) => patchForm({ tasks })} + /> + <UnattendedToggle + checked={form.unattended} + onChange={(unattended) => patchForm({ unattended })} + /> + </CardContent> + </Card> + + <Card className="border-border/60 bg-accent"> + <CardHeader className="pb-3"> + <CardTitle className="text-sm font-semibold">Schedule</CardTitle> + </CardHeader> + <CardContent> + <ScheduleSection + schedule={form.schedule} + timezone={form.timezone} + errors={errors} + onScheduleChange={(schedule) => patchForm({ schedule })} + onTimezoneChange={(timezone) => patchForm({ timezone })} + /> + </CardContent> + </Card> + + <Card className="border-border/60 bg-accent"> + <CardHeader className="pb-3"> + <CardTitle className="text-sm font-semibold">Settings</CardTitle> + </CardHeader> + <CardContent> + <AdvancedSection + execution={form.execution} + tags={form.tags} + onExecutionChange={(patch) => + patchForm({ execution: { ...form.execution, ...patch } }) + } + onTagsChange={(tags) => patchForm({ tags })} + /> + </CardContent> + </Card> + </div> + + <div className="lg:col-span-1"> + <Card className="border-border/60 bg-accent lg:sticky lg:top-4"> + <CardHeader className="pb-3"> + <CardTitle className="text-sm font-semibold">Summary</CardTitle> + </CardHeader> + <CardContent> + <BuilderSummary form={form} /> + </CardContent> + </Card> + </div> + </div> + )} + + {rootError && <p className="text-right text-xs text-destructive">{rootError}</p>} + + <div className="flex items-center justify-end gap-2"> + <Button asChild type="button" variant="ghost" size="sm"> + <Link href={cancelHref}>Cancel</Link> + </Button> + <Button + type="button" + size="sm" + disabled={submitting} + onClick={() => (activeMode === "json" ? submitJson() : submitForm())} + > + {submitting ? <Spinner size="xs" className="mr-2" /> : <Save className="mr-2 h-4 w-4" />} + {submitLabel} + </Button> + </div> + </div> + ); +} + +function ModeButton({ + active, + icon: Icon, + label, + onClick, +}: { + active: boolean; + icon: typeof Code2; + label: string; + onClick: () => void; +}) { + return ( + <button + type="button" + onClick={onClick} + className={cn( + "inline-flex items-center gap-1.5 rounded-[5px] px-2.5 py-1 text-xs font-medium transition-colors", + active + ? "bg-background text-foreground shadow-sm" + : "text-muted-foreground hover:text-foreground" + )} + > + <Icon className="h-3.5 w-3.5" /> + {label} + </button> + ); +} + +function extractTriggers(raw: unknown): HydratableTrigger[] { + if (!Array.isArray(raw)) return []; + return raw.map((entry) => { + const obj = entry && typeof entry === "object" ? (entry as Record<string, unknown>) : {}; + return { + type: typeof obj.type === "string" ? obj.type : "", + params: + obj.params && typeof obj.params === "object" ? (obj.params as Record<string, unknown>) : {}, + }; + }); +} + +function zodIssueList(error: z.ZodError): string[] { + return error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`); +} + +function jsonFromAutomation(automation: Automation | undefined): Record<string, unknown> { + if (!automation) return {}; + return { + name: automation.name, + description: automation.description ?? null, + status: automation.status, + definition: automation.definition, + }; +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/basics-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/basics-section.tsx new file mode 100644 index 000000000..fdc9f4526 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/basics-section.tsx @@ -0,0 +1,42 @@ +"use client"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Field } from "./form-field"; + +interface BasicsSectionProps { + name: string; + description: string | null; + errors: Record<string, string>; + onChange: (patch: { name?: string; description?: string | null }) => void; +} + +export function BasicsSection({ name, description, errors, onChange }: BasicsSectionProps) { + return ( + <div className="space-y-4"> + <Field label="Name" htmlFor="automation-name" required error={errors.name}> + <Input + id="automation-name" + value={name} + maxLength={200} + placeholder="Weekly competitor digest" + onChange={(e) => onChange({ name: e.target.value })} + /> + </Field> + + <Field + label="Description" + htmlFor="automation-description" + hint="Optional. A short note about what this automation is for." + error={errors.description} + > + <Textarea + id="automation-description" + value={description ?? ""} + rows={2} + placeholder="Summarize what changed and email me the highlights." + onChange={(e) => onChange({ description: e.target.value })} + /> + </Field> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/builder-summary.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/builder-summary.tsx new file mode 100644 index 000000000..21a77cb5f --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/builder-summary.tsx @@ -0,0 +1,96 @@ +"use client"; +import { CalendarClock, CheckCircle2, ListOrdered, type LucideIcon, XCircle } from "lucide-react"; +import { type BuilderForm, scheduleToCron } from "@/lib/automations/builder-schema"; +import { describeCron } from "@/lib/automations/describe-cron"; + +interface BuilderSummaryProps { + form: BuilderForm; +} + +/** + * Live, read-only mirror of what will be created. Mirrors the layout of the + * chat ``AutomationDraftPreview`` so the two creation paths feel consistent. + */ +export function BuilderSummary({ form }: BuilderSummaryProps) { + const scheduleLabel = form.schedule + ? `${describeCron(scheduleToCron(form.schedule))} · ${form.timezone}` + : "No schedule — won't run automatically"; + + return ( + <div className="space-y-4 text-sm"> + <div className="space-y-1"> + <p className="font-medium text-foreground">{form.name.trim() || "Untitled automation"}</p> + {form.description?.trim() && ( + <p className="text-xs text-muted-foreground">{form.description.trim()}</p> + )} + </div> + + <Section icon={CalendarClock} label="Schedule"> + <p className="text-xs text-foreground">{scheduleLabel}</p> + </Section> + + <Section + icon={ListOrdered} + label={`Tasks · ${form.tasks.length} step${form.tasks.length === 1 ? "" : "s"}`} + > + <ol className="space-y-1.5 text-xs"> + {form.tasks.map((task, index) => ( + <li key={task.id} className="flex items-start gap-2"> + <span className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-muted text-[10px] font-medium text-muted-foreground shrink-0 mt-0.5"> + {index + 1} + </span> + <span className="min-w-0 flex-1 space-y-1"> + <span className="block text-foreground line-clamp-2"> + {task.query.trim() || ( + <span className="text-muted-foreground">No instructions yet</span> + )} + </span> + {task.mentions.length > 0 && ( + <span className="flex flex-wrap gap-1"> + {task.mentions.map((mention) => ( + <span + key={`${mention.kind}:${mention.id}`} + className="inline-flex max-w-[140px] items-center truncate rounded bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium text-primary/70" + > + @{mention.title} + </span> + ))} + </span> + )} + </span> + </li> + ))} + </ol> + </Section> + + <div className="flex items-center gap-1.5 text-xs text-muted-foreground"> + {form.unattended ? ( + <CheckCircle2 className="h-3.5 w-3.5 text-emerald-500" aria-hidden /> + ) : ( + <XCircle className="h-3.5 w-3.5" aria-hidden /> + )} + {form.unattended ? "Runs without approval prompts" : "Will reject approval prompts"} + </div> + </div> + ); +} + +function Section({ + icon: Icon, + label, + children, +}: { + icon: LucideIcon; + label: string; + children: React.ReactNode; +}) { + return ( + <div className="space-y-1.5"> + <div className="flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground uppercase tracking-wider"> + <Icon className="h-3 w-3" aria-hidden /> + {label} + </div> + {children} + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/form-field.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/form-field.tsx new file mode 100644 index 000000000..222efd9c6 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/form-field.tsx @@ -0,0 +1,42 @@ +"use client"; +import { AlertCircle } from "lucide-react"; +import type { ReactNode } from "react"; +import { Label } from "@/components/ui/label"; +import { cn } from "@/lib/utils"; + +interface FieldProps { + label?: string; + htmlFor?: string; + hint?: string; + error?: string; + required?: boolean; + className?: string; + children: ReactNode; +} + +/** + * Label + control + (hint | inline error) stack shared by every builder + * section. Keeps spacing and error styling consistent so individual sections + * stay focused on their inputs. + */ +export function Field({ label, htmlFor, hint, error, required, className, children }: FieldProps) { + return ( + <div className={cn("space-y-1.5", className)}> + {label && ( + <Label htmlFor={htmlFor} className="text-xs font-medium text-foreground"> + {label} + {required && <span className="text-muted-foreground">*</span>} + </Label> + )} + {children} + {error ? ( + <p className="flex items-center gap-1 text-xs text-destructive"> + <AlertCircle className="h-3 w-3 shrink-0" aria-hidden /> + {error} + </p> + ) : hint ? ( + <p className="text-xs text-muted-foreground">{hint}</p> + ) : null} + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/json-mode-panel.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/json-mode-panel.tsx new file mode 100644 index 000000000..1f25f8a61 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/json-mode-panel.tsx @@ -0,0 +1,51 @@ +"use client"; +import { AlertCircle } from "lucide-react"; +import { JsonView } from "@/components/json-view"; + +interface JsonModePanelProps { + value: Record<string, unknown>; + issues: string[]; + notice?: string; + onChange: (next: Record<string, unknown>) => void; +} + +/** + * Raw-JSON escape hatch. Edits the same payload the form produces; the + * orchestrator validates it against the contract schema on submit. Shown when + * the user opts into "Edit as JSON" or when an existing definition uses + * features the form can't represent. + */ +export function JsonModePanel({ value, issues, notice, onChange }: JsonModePanelProps) { + return ( + <div className="space-y-4"> + {notice && ( + <div className="rounded-md border border-amber-500/40 bg-amber-500/5 px-3 py-2 text-xs text-amber-700 dark:text-amber-400"> + {notice} + </div> + )} + + <div className="rounded-md border border-input bg-background px-3 py-2 max-h-144 overflow-auto"> + <JsonView + src={value} + editable + onChange={(next) => onChange(next as Record<string, unknown>)} + collapsed={false} + /> + </div> + + {issues.length > 0 && ( + <div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2"> + <div className="flex items-center gap-1.5 text-xs font-medium text-destructive mb-1.5"> + <AlertCircle className="h-3.5 w-3.5" aria-hidden /> + {issues.length === 1 ? "1 issue" : `${issues.length} issues`} + </div> + <ul className="space-y-0.5 text-xs text-destructive list-disc list-inside"> + {issues.map((issue) => ( + <li key={issue}>{issue}</li> + ))} + </ul> + </div> + )} + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx new file mode 100644 index 000000000..312454056 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx @@ -0,0 +1,258 @@ +"use client"; + +import { useCallback, useEffect, useRef, useState } from "react"; +import type { MentionedDocumentInfo } from "@/atoms/chat/mentioned-documents.atom"; +import { + InlineMentionEditor, + type InlineMentionEditorRef, + type MentionChipInput, + type MentionedDocument, + type SuggestionAnchorRect, + type SuggestionTriggerInfo, +} from "@/components/assistant-ui/inline-mention-editor"; +import { ComposerSuggestionPopoverContent } from "@/components/new-chat/composer-suggestion-popup"; +import { + DocumentMentionPicker, + type DocumentMentionPickerRef, +} from "@/components/new-chat/document-mention-picker"; +import { Popover, PopoverAnchor } from "@/components/ui/popover"; +import { getMentionDocKey } from "@/lib/chat/mention-doc-key"; +import { cn } from "@/lib/utils"; + +interface MentionTaskInputProps { + searchSpaceId: number; + value: string; + mentions: MentionedDocumentInfo[]; + onChange: (text: string, mentions: MentionedDocumentInfo[]) => void; + placeholder?: string; + disabled?: boolean; +} + +type AnchorPoint = { left: number; top: number }; + +// Mirror of thread.tsx's getComposerSuggestionAnchorPoint -- kept local so the +// chat composer stays untouched. +function getAnchorPoint(rect: SuggestionAnchorRect | null): AnchorPoint | null { + if (!rect) return null; + return { left: rect.left, top: rect.bottom }; +} + +/** Project the editor's chip shape into the canonical mention info union. */ +function toMentionInfo(doc: MentionedDocument): MentionedDocumentInfo { + if (doc.kind === "connector") { + return { + id: doc.id, + title: doc.title, + kind: "connector", + connector_type: doc.connector_type ?? "UNKNOWN", + account_name: doc.account_name ?? doc.title, + }; + } + if (doc.kind === "folder") { + return { id: doc.id, title: doc.title, kind: "folder" }; + } + return { + id: doc.id, + title: doc.title, + document_type: doc.document_type ?? "UNKNOWN", + kind: "doc", + }; +} + +/** Project a mention info into the editor's chip-insertion shape. */ +function toChipInput(mention: MentionedDocumentInfo): MentionChipInput { + if (mention.kind === "connector") { + return { + id: mention.id, + title: mention.title, + kind: "connector", + connector_type: mention.connector_type, + account_name: mention.account_name, + }; + } + if (mention.kind === "folder") { + return { id: mention.id, title: mention.title, kind: "folder" }; + } + return { + id: mention.id, + title: mention.title, + kind: "doc", + document_type: mention.document_type, + }; +} + +function removeFirstToken(text: string, token: string): string { + const index = text.indexOf(token); + if (index === -1) return text; + return text.slice(0, index) + text.slice(index + token.length); +} + +/** + * Task input that reuses the chat ``@`` mention experience -- the same + * ``InlineMentionEditor`` + ``DocumentMentionPicker`` as the composer, minus + * SurfSense product docs. The editor is the source of truth while mounted; + * ``onChange`` reports both the plain text (chips rendered as ``@Title``) and + * the structured mention list so the builder can persist IDs for the run. + */ +export function MentionTaskInput({ + searchSpaceId, + value, + mentions, + onChange, + placeholder, + disabled, +}: MentionTaskInputProps) { + const editorRef = useRef<InlineMentionEditorRef>(null); + const pickerRef = useRef<DocumentMentionPickerRef>(null); + + const [showPopover, setShowPopover] = useState(false); + const [mentionQuery, setMentionQuery] = useState(""); + const [anchorPoint, setAnchorPoint] = useState<AnchorPoint | null>(null); + + // One-shot hydration of existing mentions into real chips. ``initialText`` + // seeds the literal ``@Title`` text; here we strip those tokens and + // re-insert them as chips so the editor reports the structured docs (and + // editing can't silently drop the mention IDs). Position isn't preserved + // on re-hydration -- chips append after the remaining prose. + const didHydrateRef = useRef(false); + useEffect(() => { + if (didHydrateRef.current) return; + didHydrateRef.current = true; + if (mentions.length === 0) return; + const editor = editorRef.current; + if (!editor) return; + + let baseText = value; + for (const mention of mentions) { + baseText = removeFirstToken(baseText, `@${mention.title}`); + } + baseText = baseText.replace(/[ \t]{2,}/g, " ").trim(); + editor.setText(baseText); + for (const mention of mentions) { + editor.insertMentionChip(toChipInput(mention), { removeTriggerText: false }); + } + }, [mentions, value]); + + const closePopover = useCallback(() => { + setShowPopover(false); + setMentionQuery(""); + setAnchorPoint(null); + }, []); + + const handleEditorChange = useCallback( + (text: string, docs: MentionedDocument[]) => { + onChange(text, docs.map(toMentionInfo)); + }, + [onChange] + ); + + const handleMentionTrigger = useCallback((trigger: SuggestionTriggerInfo) => { + const point = getAnchorPoint(trigger.anchorRect); + if (!point) { + setShowPopover(false); + setMentionQuery(""); + setAnchorPoint(null); + return; + } + setAnchorPoint((current) => current ?? point); + setShowPopover(true); + setMentionQuery(trigger.query); + }, []); + + const handleMentionClose = useCallback(() => { + setShowPopover((open) => { + if (open) { + setMentionQuery(""); + setAnchorPoint(null); + } + return false; + }); + }, []); + + const handlePopoverOpenChange = useCallback((open: boolean) => { + setShowPopover(open); + if (!open) { + setMentionQuery(""); + setAnchorPoint(null); + } + }, []); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (!showPopover) return; + if (e.key === "ArrowDown") { + e.preventDefault(); + pickerRef.current?.moveDown(); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + pickerRef.current?.moveUp(); + } else if (e.key === "Enter") { + e.preventDefault(); + pickerRef.current?.selectHighlighted(); + } else if (e.key === "Escape") { + e.preventDefault(); + if (pickerRef.current?.goBack()) return; + closePopover(); + } + }, + [showPopover, closePopover] + ); + + const handleSelection = useCallback( + (picked: MentionedDocumentInfo[]) => { + const editor = editorRef.current; + const existing = new Set( + (editor?.getMentionedDocuments() ?? []).map((doc) => getMentionDocKey(doc)) + ); + for (const mention of picked) { + const key = getMentionDocKey(mention); + if (existing.has(key)) continue; + editor?.insertMentionChip(toChipInput(mention)); + existing.add(key); + } + closePopover(); + }, + [closePopover] + ); + + return ( + <div + className={cn( + "border-popover-border focus-within:border-ring focus-within:ring-ring/50 dark:bg-input/30 min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs transition-[color,box-shadow] focus-within:ring-[3px]", + disabled && "cursor-not-allowed opacity-50" + )} + > + <Popover open={showPopover} onOpenChange={handlePopoverOpenChange}> + {anchorPoint ? ( + <> + <PopoverAnchor + className="pointer-events-none fixed size-0" + style={{ left: anchorPoint.left, top: anchorPoint.top }} + /> + <ComposerSuggestionPopoverContent side="bottom"> + <DocumentMentionPicker + ref={pickerRef} + searchSpaceId={searchSpaceId} + includeSurfsenseDocs={false} + onSelectionChange={handleSelection} + onDone={closePopover} + initialSelectedDocuments={mentions} + externalSearch={mentionQuery} + /> + </ComposerSuggestionPopoverContent> + </> + ) : null} + </Popover> + <InlineMentionEditor + ref={editorRef} + initialText={value} + placeholder={placeholder ?? "Type @ to reference files, folders, or connectors"} + disabled={disabled} + onChange={handleEditorChange} + onMentionTrigger={handleMentionTrigger} + onMentionClose={handleMentionClose} + onKeyDown={handleKeyDown} + /> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/schedule-section.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/schedule-section.tsx new file mode 100644 index 000000000..401b4f5cb --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/schedule-section.tsx @@ -0,0 +1,275 @@ +"use client"; +import { CalendarClock, CalendarOff, Plus, X } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { type BuilderSchedule, scheduleToCron } from "@/lib/automations/builder-schema"; +import { describeCron } from "@/lib/automations/describe-cron"; +import { + DEFAULT_SCHEDULE, + FREQUENCY_OPTIONS, + fromCron, + type ScheduleFrequency, + type ScheduleModel, + toCron, + WEEKDAY_OPTIONS, +} from "@/lib/automations/schedule-builder"; +import { cn } from "@/lib/utils"; +import { Field } from "./form-field"; +import { TimezoneCombobox } from "./timezone-combobox"; + +interface ScheduleSectionProps { + schedule: BuilderSchedule | null; + timezone: string; + errors: Record<string, string>; + onScheduleChange: (schedule: BuilderSchedule | null) => void; + onTimezoneChange: (timezone: string) => void; +} + +function pad(value: number): string { + return value.toString().padStart(2, "0"); +} + +export function ScheduleSection({ + schedule, + timezone, + errors, + onScheduleChange, + onTimezoneChange, +}: ScheduleSectionProps) { + if (schedule === null) { + return ( + <div className="rounded-lg border border-dashed border-border/60 bg-muted/20 px-4 py-6 text-center"> + <CalendarOff className="mx-auto h-7 w-7 text-muted-foreground" aria-hidden /> + <p className="mt-2 text-sm text-foreground">No schedule</p> + <p className="mt-0.5 text-xs text-muted-foreground"> + This automation won't run automatically until you add one. + </p> + <Button + type="button" + variant="outline" + size="sm" + className="mt-3" + onClick={() => onScheduleChange({ mode: "preset", model: { ...DEFAULT_SCHEDULE } })} + > + <Plus className="mr-1.5 h-4 w-4" /> + Add a schedule + </Button> + </div> + ); + } + + const cron = scheduleToCron(schedule); + const label = describeCron(cron); + + return ( + <div className="space-y-3"> + <div className="flex items-start justify-between gap-3 rounded-md border border-border/60 bg-background px-3 py-2"> + <div className="flex items-center gap-2 text-sm min-w-0"> + <CalendarClock className="h-4 w-4 shrink-0 text-muted-foreground" aria-hidden /> + <span className="font-medium text-foreground truncate">{label}</span> + <span className="text-muted-foreground shrink-0">· {timezone}</span> + </div> + <Button + type="button" + variant="ghost" + size="icon" + className="h-6 w-6 shrink-0 text-muted-foreground hover:text-destructive" + aria-label="Remove schedule" + onClick={() => onScheduleChange(null)} + > + <X className="h-4 w-4" /> + </Button> + </div> + + {schedule.mode === "preset" ? ( + <PresetEditor + model={schedule.model} + onChange={(model) => onScheduleChange({ mode: "preset", model })} + onSwitchToCron={() => onScheduleChange({ mode: "cron", cron: toCron(schedule.model) })} + /> + ) : ( + <CronEditor + cron={schedule.cron} + error={errors.schedule} + onChange={(value) => onScheduleChange({ mode: "cron", cron: value })} + onSwitchToPreset={() => + onScheduleChange({ + mode: "preset", + model: fromCron(schedule.cron) ?? { ...DEFAULT_SCHEDULE }, + }) + } + /> + )} + + <Field label="Timezone"> + <TimezoneCombobox value={timezone} onChange={onTimezoneChange} /> + </Field> + </div> + ); +} + +interface PresetEditorProps { + model: ScheduleModel; + onChange: (model: ScheduleModel) => void; + onSwitchToCron: () => void; +} + +function PresetEditor({ model, onChange, onSwitchToCron }: PresetEditorProps) { + const weeklyNoDays = model.frequency === "weekly" && model.daysOfWeek.length === 0; + + return ( + <div className="space-y-3"> + <div className="grid grid-cols-1 gap-3 sm:grid-cols-2"> + <Field label="Frequency"> + <Select + value={model.frequency} + onValueChange={(value) => onChange({ ...model, frequency: value as ScheduleFrequency })} + > + <SelectTrigger className="w-full"> + <SelectValue /> + </SelectTrigger> + <SelectContent> + {FREQUENCY_OPTIONS.map((option) => ( + <SelectItem key={option.value} value={option.value}> + {option.label} + </SelectItem> + ))} + </SelectContent> + </Select> + </Field> + + {model.frequency === "hourly" ? ( + <Field label="At minute"> + <Input + type="number" + min={0} + max={59} + value={model.minute} + onChange={(e) => onChange({ ...model, minute: clampInt(e.target.value, 0, 59) })} + /> + </Field> + ) : ( + <Field label="At time"> + <Input + type="time" + value={`${pad(model.hour)}:${pad(model.minute)}`} + onChange={(e) => { + const [h, m] = e.target.value.split(":"); + onChange({ + ...model, + hour: clampInt(h, 0, 23), + minute: clampInt(m, 0, 59), + }); + }} + /> + </Field> + )} + </div> + + {model.frequency === "weekly" && ( + <Field label="On days" error={weeklyNoDays ? "Pick at least one day" : undefined}> + <div className="flex flex-wrap gap-1.5"> + {WEEKDAY_OPTIONS.map((day) => { + const active = model.daysOfWeek.includes(day.value); + return ( + <button + key={day.value} + type="button" + aria-pressed={active} + onClick={() => + onChange({ ...model, daysOfWeek: toggleDay(model.daysOfWeek, day.value) }) + } + className={cn( + "rounded-md border px-2.5 py-1 text-xs font-medium transition-colors", + active + ? "border-primary bg-primary text-primary-foreground" + : "border-border/60 bg-background text-muted-foreground hover:bg-muted" + )} + > + {day.short} + </button> + ); + })} + </div> + </Field> + )} + + {model.frequency === "monthly" && ( + <Field label="Day of month" hint={"1\u201331."}> + <Input + type="number" + min={1} + max={31} + value={model.dayOfMonth} + onChange={(e) => onChange({ ...model, dayOfMonth: clampInt(e.target.value, 1, 31) })} + className="w-24" + /> + </Field> + )} + + <button + type="button" + onClick={onSwitchToCron} + className="text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline" + > + Advanced: enter a schedule expression + </button> + </div> + ); +} + +interface CronEditorProps { + cron: string; + error?: string; + onChange: (cron: string) => void; + onSwitchToPreset: () => void; +} + +function CronEditor({ cron, error, onChange, onSwitchToPreset }: CronEditorProps) { + const trimmed = cron.trim(); + const label = trimmed ? describeCron(trimmed) : null; + + return ( + <div className="space-y-2"> + <Field + label="Schedule expression" + hint="Five-field cron, e.g. 0 9 * * 1-5 (minute hour day month weekday)." + error={error} + > + <Input + value={cron} + placeholder="0 9 * * 1-5" + className="font-mono" + onChange={(e) => onChange(e.target.value)} + /> + </Field> + {label && label !== trimmed && <p className="text-xs text-muted-foreground">Runs: {label}</p>} + <button + type="button" + onClick={onSwitchToPreset} + className="text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline" + > + Use the simple picker + </button> + </div> + ); +} + +function clampInt(raw: string, min: number, max: number): number { + const value = Number.parseInt(raw, 10); + if (Number.isNaN(value)) return min; + return Math.min(max, Math.max(min, value)); +} + +function toggleDay(days: number[], value: number): number[] { + return days.includes(value) + ? days.filter((day) => day !== value) + : [...days, value].sort((a, b) => a - b); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-item.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-item.tsx new file mode 100644 index 000000000..55b9ea406 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-item.tsx @@ -0,0 +1,136 @@ +"use client"; +import { ChevronDown, ChevronUp, Trash2 } from "lucide-react"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import type { BuilderTask } from "@/lib/automations/builder-schema"; +import { Field } from "./form-field"; +import { MentionTaskInput } from "./mention-task-input"; + +interface TaskItemProps { + index: number; + total: number; + task: BuilderTask; + searchSpaceId: number; + error?: string; + onChange: (patch: Partial<BuilderTask>) => void; + onMoveUp: () => void; + onMoveDown: () => void; + onRemove: () => void; +} + +function parseOptionalInt(raw: string): number | null { + const trimmed = raw.trim(); + if (trimmed === "") return null; + const value = Number.parseInt(trimmed, 10); + return Number.isNaN(value) ? null : value; +} + +export function TaskItem({ + index, + total, + task, + searchSpaceId, + error, + onChange, + onMoveUp, + onMoveDown, + onRemove, +}: TaskItemProps) { + return ( + <div className="rounded-lg border border-border/60 bg-background p-3 space-y-3"> + <div className="flex items-center justify-between gap-2"> + <span className="inline-flex items-center gap-2 text-xs font-medium text-muted-foreground"> + <span className="inline-flex h-5 w-5 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-foreground"> + {index + 1} + </span> + Task {index + 1} + </span> + <div className="flex items-center gap-0.5"> + <Button + type="button" + variant="ghost" + size="icon" + className="h-7 w-7 text-muted-foreground" + disabled={index === 0} + aria-label="Move task up" + onClick={onMoveUp} + > + <ChevronUp className="h-4 w-4" /> + </Button> + <Button + type="button" + variant="ghost" + size="icon" + className="h-7 w-7 text-muted-foreground" + disabled={index === total - 1} + aria-label="Move task down" + onClick={onMoveDown} + > + <ChevronDown className="h-4 w-4" /> + </Button> + <Button + type="button" + variant="ghost" + size="icon" + className="h-7 w-7 text-muted-foreground hover:text-destructive" + disabled={total === 1} + aria-label="Remove task" + onClick={onRemove} + > + <Trash2 className="h-4 w-4" /> + </Button> + </div> + </div> + + <Field + error={error} + hint="Type @ to reference files, folders, or connectors for extra context." + > + <MentionTaskInput + searchSpaceId={searchSpaceId} + value={task.query} + mentions={task.mentions} + placeholder="What should the agent do? e.g. Summarize new docs in @Marketing since the last run." + onChange={(query, mentions) => onChange({ query, mentions })} + /> + </Field> + + <Accordion type="single" collapsible> + <AccordionItem value="advanced" className="border-b-0"> + <AccordionTrigger className="py-1.5 text-xs text-muted-foreground hover:no-underline"> + Advanced + </AccordionTrigger> + <AccordionContent className="pb-1"> + <div className="grid grid-cols-2 gap-3"> + <Field label="Max retries" hint="Leave blank to use the default."> + <Input + type="number" + min={0} + max={10} + value={task.maxRetries ?? ""} + placeholder="default" + onChange={(e) => onChange({ maxRetries: parseOptionalInt(e.target.value) })} + /> + </Field> + <Field label="Timeout (seconds)" hint="Leave blank to use the default."> + <Input + type="number" + min={1} + value={task.timeoutSeconds ?? ""} + placeholder="default" + onChange={(e) => onChange({ timeoutSeconds: parseOptionalInt(e.target.value) })} + /> + </Field> + </div> + </AccordionContent> + </AccordionItem> + </Accordion> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-list.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-list.tsx new file mode 100644 index 000000000..41a53542f --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/task-list.tsx @@ -0,0 +1,65 @@ +"use client"; +import { Plus } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { type BuilderTask, emptyTask } from "@/lib/automations/builder-schema"; +import { TaskItem } from "./task-item"; + +interface TaskListProps { + tasks: BuilderTask[]; + errors: Record<string, string>; + searchSpaceId: number; + onChange: (tasks: BuilderTask[]) => void; +} + +/** + * Ordered list of agent tasks. Steps run sequentially in the order shown. + * Reordering is done with up/down buttons to avoid a drag-and-drop dependency. + */ +export function TaskList({ tasks, errors, searchSpaceId, onChange }: TaskListProps) { + function updateAt(index: number, patch: Partial<BuilderTask>) { + onChange(tasks.map((task, i) => (i === index ? { ...task, ...patch } : task))); + } + + function removeAt(index: number) { + onChange(tasks.filter((_, i) => i !== index)); + } + + function move(index: number, direction: -1 | 1) { + const target = index + direction; + if (target < 0 || target >= tasks.length) return; + const next = [...tasks]; + [next[index], next[target]] = [next[target], next[index]]; + onChange(next); + } + + return ( + <div className="space-y-3"> + {tasks.map((task, index) => ( + <TaskItem + key={task.id} + index={index} + total={tasks.length} + task={task} + searchSpaceId={searchSpaceId} + error={errors[`tasks.${index}.query`]} + onChange={(patch) => updateAt(index, patch)} + onMoveUp={() => move(index, -1)} + onMoveDown={() => move(index, 1)} + onRemove={() => removeAt(index)} + /> + ))} + + {errors.tasks && <p className="text-xs text-destructive">{errors.tasks}</p>} + + <Button + type="button" + variant="outline" + size="sm" + onClick={() => onChange([...tasks, emptyTask()])} + > + <Plus className="mr-1.5 h-4 w-4" /> + Add task + </Button> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/timezone-combobox.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/timezone-combobox.tsx new file mode 100644 index 000000000..bc3b97542 --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/timezone-combobox.tsx @@ -0,0 +1,71 @@ +"use client"; +import { Check, ChevronsUpDown } from "lucide-react"; +import { useMemo, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { getTimezones } from "@/lib/automations/builder-schema"; +import { cn } from "@/lib/utils"; + +interface TimezoneComboboxProps { + value: string; + onChange: (value: string) => void; +} + +/** + * Searchable IANA timezone picker. The full ``Intl.supportedValuesOf`` list is + * long, so it lives behind a Command search instead of a flat Select. + */ +export function TimezoneCombobox({ value, onChange }: TimezoneComboboxProps) { + const [open, setOpen] = useState(false); + const timezones = useMemo(() => getTimezones(), []); + + return ( + <Popover open={open} onOpenChange={setOpen}> + <PopoverTrigger asChild> + <Button + type="button" + variant="outline" + role="combobox" + aria-expanded={open} + className="w-full justify-between font-normal" + > + <span className="truncate">{value || "Select timezone"}</span> + <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> + </Button> + </PopoverTrigger> + <PopoverContent className="w-[--radix-popover-trigger-width] p-0" align="start"> + <Command> + <CommandInput placeholder="Search timezone..." /> + <CommandList> + <CommandEmpty>No timezone found.</CommandEmpty> + <CommandGroup> + {timezones.map((tz) => ( + <CommandItem + key={tz} + value={tz} + onSelect={() => { + onChange(tz); + setOpen(false); + }} + > + <Check + className={cn("mr-2 h-4 w-4", value === tz ? "opacity-100" : "opacity-0")} + /> + {tz} + </CommandItem> + ))} + </CommandGroup> + </CommandList> + </Command> + </PopoverContent> + </Popover> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/unattended-toggle.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/unattended-toggle.tsx new file mode 100644 index 000000000..ba665445f --- /dev/null +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/unattended-toggle.tsx @@ -0,0 +1,47 @@ +"use client"; +import { Info } from "lucide-react"; +import { Switch } from "@/components/ui/switch"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; + +interface UnattendedToggleProps { + checked: boolean; + onChange: (checked: boolean) => void; +} + +/** + * Maps to ``auto_approve_all`` on every agent task. Automations run with no one + * watching, so this defaults ON; turning it off means any approval prompt the + * agent raises is rejected and the step can stall. + */ +export function UnattendedToggle({ checked, onChange }: UnattendedToggleProps) { + return ( + <div className="flex items-start justify-between gap-3 rounded-lg border border-border/60 bg-background px-3 py-3"> + <div className="space-y-0.5 min-w-0"> + <div className="flex items-center gap-1.5"> + <span className="text-sm font-medium text-foreground"> + Run without asking for approvals + </span> + <Tooltip> + <TooltipTrigger asChild> + <button type="button" aria-label="More info" className="text-muted-foreground"> + <Info className="h-3.5 w-3.5" /> + </button> + </TooltipTrigger> + <TooltipContent className="max-w-xs"> + Automations run unattended. With this off, any approval the agent asks for is + rejected, which can stall a step. + </TooltipContent> + </Tooltip> + </div> + <p className="text-xs text-muted-foreground"> + Auto-approve actions the agent would normally pause to confirm. + </p> + </div> + <Switch + checked={checked} + onCheckedChange={onChange} + aria-label="Run without asking for approvals" + /> + </div> + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx index f03b3f4c8..0c983aaf8 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx @@ -1,7 +1,7 @@ "use client"; import { ShieldAlert } from "lucide-react"; +import { AutomationBuilderForm } from "../components/builder/automation-builder-form"; import { useAutomationPermissions } from "../hooks/use-automation-permissions"; -import { AutomationJsonForm } from "./components/automation-json-form"; import { AutomationNewHeader } from "./components/automation-new-header"; interface AutomationNewContentProps { @@ -9,10 +9,10 @@ interface AutomationNewContentProps { } /** - * Orchestrator for the raw-JSON create route. Gates on - * ``automations:create`` so users who can't create don't even see the - * form; same panel as the detail page's access-denied state for - * consistency. + * Orchestrator for the create route. Gates on ``automations:create`` so users + * who can't create don't even see the form; same panel as the detail page's + * access-denied state for consistency. The builder defaults to the friendly + * form with a raw-JSON escape hatch. */ export function AutomationNewContent({ searchSpaceId }: AutomationNewContentProps) { const perms = useAutomationPermissions(); @@ -36,7 +36,7 @@ export function AutomationNewContent({ searchSpaceId }: AutomationNewContentProp return ( <> <AutomationNewHeader searchSpaceId={searchSpaceId} /> - <AutomationJsonForm searchSpaceId={searchSpaceId} /> + <AutomationBuilderForm mode="create" searchSpaceId={searchSpaceId} /> </> ); } diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx deleted file mode 100644 index 94b608b8f..000000000 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-json-form.tsx +++ /dev/null @@ -1,98 +0,0 @@ -"use client"; -import { useAtomValue } from "jotai"; -import { AlertCircle, FileJson, Save } from "lucide-react"; -import { useRouter } from "next/navigation"; -import { useState } from "react"; -import { createAutomationMutationAtom } from "@/atoms/automations/automations-mutation.atoms"; -import { JsonView } from "@/components/json-view"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Spinner } from "@/components/ui/spinner"; -import { automationCreateRequest } from "@/contracts/types/automation.types"; -import { DEFAULT_AUTOMATION_TEMPLATE } from "@/lib/automations/default-template"; - -interface AutomationJsonFormProps { - searchSpaceId: number; -} - -/** - * Raw-JSON create form. Lets power users skip the chat drafter when they - * already know the shape they want. Flow: - * edit tree → inject search_space_id → Zod validate → POST → navigate - * - * ``search_space_id`` is injected here rather than required in the edited - * tree — the user shouldn't have to know their numeric id, and it keeps - * the template copy-paste-friendly across search spaces. - */ -export function AutomationJsonForm({ searchSpaceId }: AutomationJsonFormProps) { - const router = useRouter(); - const { mutateAsync: createAutomation, isPending } = useAtomValue(createAutomationMutationAtom); - const [value, setValue] = useState<Record<string, unknown>>( - () => DEFAULT_AUTOMATION_TEMPLATE as Record<string, unknown> - ); - const [issues, setIssues] = useState<string[]>([]); - - async function handleSubmit() { - setIssues([]); - - const payload = { ...value, search_space_id: searchSpaceId }; - const result = automationCreateRequest.safeParse(payload); - if (!result.success) { - setIssues( - result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`) - ); - return; - } - - try { - const created = await createAutomation(result.data); - router.push(`/dashboard/${searchSpaceId}/automations/${created.id}`); - } catch (err) { - setIssues([(err as Error).message ?? "Submit failed"]); - } - } - - const hasIssues = issues.length > 0; - - return ( - <Card className="border-border/60 bg-accent"> - <CardHeader className="pb-4"> - <CardTitle className="text-base font-semibold inline-flex items-center gap-2"> - <FileJson className="h-4 w-4 text-muted-foreground" aria-hidden /> - Definition + triggers - </CardTitle> - </CardHeader> - <CardContent className="space-y-4"> - <div className="rounded-md border border-input bg-background px-3 py-2 max-h-[32rem] overflow-auto"> - <JsonView - src={value} - editable - onChange={(next) => setValue(next as Record<string, unknown>)} - collapsed={false} - /> - </div> - - {hasIssues && ( - <div className="rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2"> - <div className="flex items-center gap-1.5 text-xs font-medium text-destructive mb-1.5"> - <AlertCircle className="h-3.5 w-3.5" aria-hidden /> - {issues.length === 1 ? "1 issue" : `${issues.length} issues`} - </div> - <ul className="space-y-0.5 text-xs text-destructive list-disc list-inside"> - {issues.map((issue) => ( - <li key={issue}>{issue}</li> - ))} - </ul> - </div> - )} - - <div className="flex items-center justify-end gap-2"> - <Button type="button" onClick={handleSubmit} disabled={isPending} size="sm"> - {isPending ? <Spinner size="xs" className="mr-2" /> : <Save className="mr-2 h-4 w-4" />} - Create automation - </Button> - </div> - </CardContent> - </Card> - ); -} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx index aef2744d5..ccfbbc9fa 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/components/automation-new-header.tsx @@ -22,12 +22,9 @@ export function AutomationNewHeader({ searchSpaceId }: AutomationNewHeaderProps) <div className="flex items-start justify-between gap-4 flex-wrap"> <div className="space-y-1"> - <h1 className="text-xl md:text-2xl font-semibold text-foreground"> - New automation · raw JSON - </h1> + <h1 className="text-xl md:text-2xl font-semibold text-foreground">New automation</h1> <p className="text-sm text-muted-foreground max-w-2xl"> - Paste an ``AutomationCreate`` payload and submit. Validated against the schema before - save. Prefer natural language? Use chat instead. + Set up a task and a schedule. Prefer natural language? Use chat instead. </p> </div> <Button asChild variant="outline" size="sm"> diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index c2a0f1665..8e3fd4ca8 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -57,6 +57,13 @@ interface DocumentMentionPickerProps { onDone: () => void; initialSelectedDocuments?: MentionedDocumentInfo[]; externalSearch?: string; + /** + * Whether to surface the "SurfSense Docs" (product documentation) branch + * and include those docs in search results. Defaults to ``true`` so the + * chat composer is unchanged; callers like the automation task input pass + * ``false`` to reference only the user's own knowledge base + connectors. + */ + includeSurfsenseDocs?: boolean; } const PAGE_SIZE = 20; @@ -228,7 +235,14 @@ export const DocumentMentionPicker = forwardRef< DocumentMentionPickerRef, DocumentMentionPickerProps >(function DocumentMentionPicker( - { searchSpaceId, onSelectionChange, onDone, initialSelectedDocuments = [], externalSearch = "" }, + { + searchSpaceId, + onSelectionChange, + onDone, + initialSelectedDocuments = [], + externalSearch = "", + includeSurfsenseDocs = true, + }, ref ) { const search = externalSearch; @@ -307,7 +321,7 @@ export const DocumentMentionPicker = forwardRef< queryFn: ({ signal }) => documentsApiService.getSurfsenseDocs({ queryParams: surfsenseDocsQueryParams }, signal), staleTime: 3 * 60 * 1000, - enabled: !hasSearch || isSearchValid, + enabled: includeSurfsenseDocs && (!hasSearch || isSearchValid), placeholderData: keepPreviousData, }); @@ -324,7 +338,7 @@ export const DocumentMentionPicker = forwardRef< if (currentPage !== 0) return; const combinedDocs: Pick<Document, "id" | "title" | "document_type">[] = []; - if (surfsenseDocs?.items) { + if (includeSurfsenseDocs && surfsenseDocs?.items) { for (const doc of surfsenseDocs.items) { combinedDocs.push({ id: doc.id, @@ -340,7 +354,7 @@ export const DocumentMentionPicker = forwardRef< } setAccumulatedDocuments(filterBySearchTerm(combinedDocs)); - }, [titleSearchResults, surfsenseDocs, currentPage, filterBySearchTerm]); + }, [titleSearchResults, surfsenseDocs, currentPage, filterBySearchTerm, includeSurfsenseDocs]); const loadNextPage = useCallback(async () => { if (isLoadingMore || !hasMore) return; @@ -449,7 +463,7 @@ export const DocumentMentionPicker = forwardRef< () => new Set(initialSelectedDocuments.map((d) => getMentionDocKey(d))), [initialSelectedDocuments] ); - const showSurfsenseDocsRoot = surfsenseDocsList.length > 0; + const showSurfsenseDocsRoot = includeSurfsenseDocs && surfsenseDocsList.length > 0; const selectMention = useCallback( (mention: MentionedDocumentInfo) => { diff --git a/surfsense_web/lib/automations/builder-schema.ts b/surfsense_web/lib/automations/builder-schema.ts new file mode 100644 index 000000000..a6ed08c09 --- /dev/null +++ b/surfsense_web/lib/automations/builder-schema.ts @@ -0,0 +1,456 @@ +/** + * The form builder's own data model plus the mappers that bridge it to the + * backend contract (``automation.types.ts``). + * + * The builder deliberately exposes a *subset* of the full automation + * definition: a name, one or more natural-language agent tasks, a single + * schedule, and a few execution knobs. Anything richer (goal, per-step + * ``when`` predicates, ``inputs`` schema, ``on_failure`` steps, multiple or + * non-schedule triggers, custom metadata) is not representable here, so on + * edit we detect it and bounce the user to raw-JSON mode rather than silently + * dropping their data. ``goal`` is the one exception: it is carried through + * invisibly so the common drafter-produced automation stays form-editable. + */ + +import { z } from "zod"; +import type { MentionedDocumentInfo } from "@/atoms/chat/mentioned-documents.atom"; +import { + type Automation, + type AutomationCreateRequest, + type AutomationDefinition, + type AutomationUpdateRequest, + execution as executionContract, + type TriggerCreateRequest, +} from "@/contracts/types/automation.types"; +import { DEFAULT_SCHEDULE, fromCron, type ScheduleModel, toCron } from "./schedule-builder"; + +const EXECUTION_DEFAULTS = executionContract.parse({}); + +// --------------------------------------------------------------------------- +// Form model +// --------------------------------------------------------------------------- + +export const builderTaskSchema = z.object({ + /** Client-side identity for stable React keys across reorder; not persisted. */ + id: z.string(), + query: z.string().trim().min(1, "Describe what the agent should do"), + /** + * Files / folders / connectors @-mentioned in the query. Mirrors the chat + * composer's mention list and is forwarded to the run as step params so the + * agent scopes retrieval to them. The query text already carries ``@Title`` + * for each; this is the structured side-channel of IDs. + */ + mentions: z.array(z.custom<MentionedDocumentInfo>()), + maxRetries: z.number().int().min(0).max(10).nullable(), + timeoutSeconds: z.number().int().positive().max(86_400).nullable(), +}); +export type BuilderTask = z.infer<typeof builderTaskSchema>; + +export const builderScheduleSchema = z.discriminatedUnion("mode", [ + z.object({ + mode: z.literal("preset"), + model: z.custom<ScheduleModel>(), + }), + z.object({ + mode: z.literal("cron"), + cron: z.string().trim().min(1, "Enter a schedule expression"), + }), +]); +export type BuilderSchedule = z.infer<typeof builderScheduleSchema>; + +export const builderExecutionSchema = z.object({ + timeoutSeconds: z.number().int().positive().max(86_400), + maxRetries: z.number().int().min(0).max(10), + retryBackoff: z.enum(["exponential", "linear", "none"]), + concurrency: z.enum(["drop_if_running", "queue", "always"]), +}); +export type BuilderExecution = z.infer<typeof builderExecutionSchema>; + +export const builderFormSchema = z.object({ + name: z.string().trim().min(1, "Give your automation a name").max(200), + description: z.string().trim().max(2000).nullable(), + tasks: z.array(builderTaskSchema).min(1, "Add at least one task"), + unattended: z.boolean(), + schedule: builderScheduleSchema.nullable(), + timezone: z.string().min(1), + execution: builderExecutionSchema, + tags: z.array(z.string()), + /** Carried through from an edited definition so we don't drop it. */ + goal: z.string().nullable(), +}); +export type BuilderForm = z.infer<typeof builderFormSchema>; + +// --------------------------------------------------------------------------- +// Defaults / construction +// --------------------------------------------------------------------------- + +export function getDefaultTimezone(): string { + try { + return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC"; + } catch { + return "UTC"; + } +} + +export function getTimezones(): string[] { + try { + const supported = ( + Intl as unknown as { supportedValuesOf?: (key: string) => string[] } + ).supportedValuesOf?.("timeZone"); + if (supported && supported.length > 0) return supported; + } catch { + // fall through + } + return ["UTC", getDefaultTimezone()]; +} + +function newId(): string { + try { + return crypto.randomUUID(); + } catch { + return `task_${Math.random().toString(36).slice(2)}`; + } +} + +export function emptyTask(): BuilderTask { + return { id: newId(), query: "", mentions: [], maxRetries: null, timeoutSeconds: null }; +} + +export function createEmptyForm(): BuilderForm { + return { + name: "", + description: null, + tasks: [emptyTask()], + unattended: true, + schedule: { mode: "preset", model: { ...DEFAULT_SCHEDULE } }, + timezone: getDefaultTimezone(), + execution: { + timeoutSeconds: EXECUTION_DEFAULTS.timeout_seconds, + maxRetries: EXECUTION_DEFAULTS.max_retries, + retryBackoff: EXECUTION_DEFAULTS.retry_backoff, + concurrency: EXECUTION_DEFAULTS.concurrency, + }, + tags: [], + goal: null, + }; +} + +/** The cron string a schedule resolves to, regardless of preset/raw mode. */ +export function scheduleToCron(schedule: BuilderSchedule): string { + return schedule.mode === "preset" ? toCron(schedule.model) : schedule.cron.trim(); +} + +// --------------------------------------------------------------------------- +// Form -> contract payloads +// --------------------------------------------------------------------------- + +/** + * Project a task's @-mentions into the ``agent_task`` param fields the backend + * understands (the same names the chat ``new_chat`` request uses, minus + * SurfSense docs). Returns an empty object when there are no mentions so the + * params stay clean. ``mentioned_documents`` carries full chip metadata so the + * run can resolve titles/paths and the form can round-trip the chips back. + */ +function mentionParams(mentions: MentionedDocumentInfo[]): Record<string, unknown> { + if (mentions.length === 0) return {}; + const documentIds: number[] = []; + const folderIds: number[] = []; + const connectorIds: number[] = []; + const connectors: MentionedDocumentInfo[] = []; + for (const mention of mentions) { + if (mention.kind === "folder") { + folderIds.push(mention.id); + } else if (mention.kind === "connector") { + connectorIds.push(mention.id); + connectors.push(mention); + } else { + documentIds.push(mention.id); + } + } + const out: Record<string, unknown> = { mentioned_documents: mentions }; + if (documentIds.length > 0) out.mentioned_document_ids = documentIds; + if (folderIds.length > 0) out.mentioned_folder_ids = folderIds; + if (connectorIds.length > 0) { + out.mentioned_connector_ids = connectorIds; + out.mentioned_connectors = connectors; + } + return out; +} + +function buildPlan(form: BuilderForm) { + return form.tasks.map((task, index) => { + const step: Record<string, unknown> = { + step_id: `step_${index + 1}`, + action: "agent_task", + params: { + query: task.query.trim(), + auto_approve_all: form.unattended, + ...mentionParams(task.mentions), + }, + }; + if (task.maxRetries !== null) step.max_retries = task.maxRetries; + if (task.timeoutSeconds !== null) step.timeout_seconds = task.timeoutSeconds; + return step; + }); +} + +function buildDefinition(form: BuilderForm): AutomationDefinition { + return { + schema_version: "1.0", + name: form.name.trim(), + goal: form.goal, + // Triggers are attached at the top level of the create payload, not in + // the definition; the in-definition list stays empty. + triggers: [], + plan: buildPlan(form), + execution: { + timeout_seconds: form.execution.timeoutSeconds, + max_retries: form.execution.maxRetries, + retry_backoff: form.execution.retryBackoff, + concurrency: form.execution.concurrency, + on_failure: [], + }, + metadata: { tags: form.tags }, + } as unknown as AutomationDefinition; +} + +/** The desired schedule trigger for this form, or ``null`` if none. */ +export function buildScheduleTrigger(form: BuilderForm): TriggerCreateRequest | null { + if (!form.schedule) return null; + return { + type: "schedule", + params: { cron: scheduleToCron(form.schedule), timezone: form.timezone }, + static_inputs: {}, + enabled: true, + }; +} + +export function buildCreatePayload( + form: BuilderForm, + searchSpaceId: number +): AutomationCreateRequest { + const trigger = buildScheduleTrigger(form); + return { + search_space_id: searchSpaceId, + name: form.name.trim(), + description: form.description?.trim() ? form.description.trim() : null, + definition: buildDefinition(form), + triggers: trigger ? [trigger] : [], + }; +} + +export function buildUpdatePayload(form: BuilderForm): AutomationUpdateRequest { + return { + name: form.name.trim(), + description: form.description?.trim() ? form.description.trim() : null, + definition: buildDefinition(form), + }; +} + +// --------------------------------------------------------------------------- +// Contract -> form (edit hydration with safe fallback) +// --------------------------------------------------------------------------- + +export type HydrateResult = + | { formable: true; form: BuilderForm } + | { formable: false; reason: string }; + +/** A trigger as seen by the hydrator: both ``Trigger`` and ``TriggerCreateRequest`` fit. */ +export interface HydratableTrigger { + type: string; + params: Record<string, unknown>; +} + +const BACKOFF_VALUES = ["exponential", "linear", "none"] as const; +const CONCURRENCY_VALUES = ["drop_if_running", "queue", "always"] as const; + +function asRecord(value: unknown): Record<string, unknown> { + return value && typeof value === "object" ? (value as Record<string, unknown>) : {}; +} + +/** Best-effort projection of a stored ``mentioned_documents`` entry into a chip. */ +function coerceMention(raw: unknown): MentionedDocumentInfo | null { + const o = asRecord(raw); + if (typeof o.id !== "number" || typeof o.title !== "string") return null; + if (o.kind === "folder") { + return { id: o.id, title: o.title, kind: "folder" }; + } + if (o.kind === "connector") { + if (typeof o.connector_type !== "string" || typeof o.account_name !== "string") return null; + return { + id: o.id, + title: o.title, + kind: "connector", + connector_type: o.connector_type, + account_name: o.account_name, + }; + } + return { + id: o.id, + title: o.title, + kind: "doc", + document_type: typeof o.document_type === "string" ? o.document_type : "UNKNOWN", + }; +} + +/** + * Rebuild a task's mention chips from step params. Returns ``null`` when the + * step carries mention IDs that aren't backed by usable ``mentioned_documents`` + * metadata (e.g. hand-edited JSON), so the caller can fall back to JSON mode + * rather than silently dropping those IDs on the next save. + */ +function mentionsFromParams(params: Record<string, unknown>): MentionedDocumentInfo[] | null { + const rawList = Array.isArray(params.mentioned_documents) ? params.mentioned_documents : []; + const mentions: MentionedDocumentInfo[] = []; + for (const raw of rawList) { + const mention = coerceMention(raw); + if (mention) mentions.push(mention); + } + + const haveByKind = { + doc: new Set(mentions.filter((m) => m.kind === "doc").map((m) => m.id)), + folder: new Set(mentions.filter((m) => m.kind === "folder").map((m) => m.id)), + connector: new Set(mentions.filter((m) => m.kind === "connector").map((m) => m.id)), + }; + const idChecks: Array<[unknown, Set<number>]> = [ + [params.mentioned_document_ids, haveByKind.doc], + [params.mentioned_folder_ids, haveByKind.folder], + [params.mentioned_connector_ids, haveByKind.connector], + ]; + for (const [arr, have] of idChecks) { + if (!Array.isArray(arr)) continue; + for (const id of arr) { + if (typeof id === "number" && !have.has(id)) return null; + } + } + return mentions; +} + +/** + * Core projection of a definition + triggers into the builder form. Returns + * ``formable: false`` whenever something can't be represented, so the caller + * can drop into raw-JSON mode without losing data. Shared by the edit + * hydrator and the JSON-mode round-trip. + * + * The definition is read defensively (``unknown``) so a partially edited JSON + * tree can still round-trip into the form; completeness is enforced by the + * form's own validation at submit time, not here. + */ +export function hydrateForm( + name: string, + description: string | null, + def: unknown, + triggers: HydratableTrigger[] +): HydrateResult { + const d = asRecord(def); + + if (d.inputs) { + return { formable: false, reason: "uses an inputs schema" }; + } + + const exec = asRecord(d.execution); + const onFailure = Array.isArray(exec.on_failure) ? exec.on_failure : []; + if (onFailure.length > 0) { + return { formable: false, reason: "has on-failure steps" }; + } + + const metadata = asRecord(d.metadata); + const extraMetadataKeys = Object.keys(metadata).filter((key) => key !== "tags"); + if (extraMetadataKeys.length > 0) { + return { formable: false, reason: "has custom metadata" }; + } + + const plan = Array.isArray(d.plan) ? d.plan : []; + const tasks: BuilderTask[] = []; + let unattended = true; + for (const rawStep of plan) { + const step = asRecord(rawStep); + if (step.action !== "agent_task") { + return { formable: false, reason: `uses the "${String(step.action)}" action` }; + } + if (step.when) { + return { formable: false, reason: "uses conditional steps" }; + } + const params = asRecord(step.params); + const query = typeof params.query === "string" ? params.query : ""; + // auto_approve_all is a single global toggle in the form; if any step is + // explicitly false we surface the toggle as off. + if (params.auto_approve_all === false) unattended = false; + const mentions = mentionsFromParams(params); + if (mentions === null) { + return { formable: false, reason: "references mentions without metadata" }; + } + tasks.push({ + id: newId(), + query, + mentions, + maxRetries: typeof step.max_retries === "number" ? step.max_retries : null, + timeoutSeconds: typeof step.timeout_seconds === "number" ? step.timeout_seconds : null, + }); + } + if (tasks.length === 0) { + return { formable: false, reason: "has no steps" }; + } + + if (triggers.length > 1) { + return { formable: false, reason: "has multiple triggers" }; + } + const trigger = triggers[0]; + let schedule: BuilderSchedule | null = null; + let timezone = getDefaultTimezone(); + if (trigger) { + if (trigger.type !== "schedule") { + return { formable: false, reason: `has a "${trigger.type}" trigger` }; + } + const cron = typeof trigger.params?.cron === "string" ? trigger.params.cron : ""; + timezone = typeof trigger.params?.timezone === "string" ? trigger.params.timezone : timezone; + const model = fromCron(cron); + schedule = model ? { mode: "preset", model } : { mode: "cron", cron }; + } + + const retryBackoff = BACKOFF_VALUES.includes(exec.retry_backoff as never) + ? (exec.retry_backoff as BuilderExecution["retryBackoff"]) + : EXECUTION_DEFAULTS.retry_backoff; + const concurrency = CONCURRENCY_VALUES.includes(exec.concurrency as never) + ? (exec.concurrency as BuilderExecution["concurrency"]) + : EXECUTION_DEFAULTS.concurrency; + const tags = Array.isArray(metadata.tags) + ? metadata.tags.filter((tag): tag is string => typeof tag === "string") + : []; + + return { + formable: true, + form: { + name, + description: description ?? null, + tasks, + unattended, + schedule, + timezone, + execution: { + timeoutSeconds: + typeof exec.timeout_seconds === "number" + ? exec.timeout_seconds + : EXECUTION_DEFAULTS.timeout_seconds, + maxRetries: + typeof exec.max_retries === "number" ? exec.max_retries : EXECUTION_DEFAULTS.max_retries, + retryBackoff, + concurrency, + }, + tags, + goal: typeof d.goal === "string" ? d.goal : null, + }, + }; +} + +/** + * Project an existing automation into the builder form for editing. + */ +export function formFromAutomation(automation: Automation): HydrateResult { + return hydrateForm( + automation.name, + automation.description ?? null, + automation.definition, + automation.triggers ?? [] + ); +} diff --git a/surfsense_web/lib/automations/default-template.ts b/surfsense_web/lib/automations/default-template.ts deleted file mode 100644 index 8963992cb..000000000 --- a/surfsense_web/lib/automations/default-template.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Minimal valid ``AutomationCreate`` skeleton used to seed the raw-JSON - * create form. ``search_space_id`` is omitted on purpose — the form - * injects it from the route so users never have to know their id. - * - * The shape matches the Pydantic ``AutomationCreate`` model less the - * search_space_id field; Zod validates the merged payload before submit. - */ -export const DEFAULT_AUTOMATION_TEMPLATE = { - name: "My automation", - description: null, - definition: { - name: "My automation", - goal: null, - plan: [ - { - step_id: "step_1", - action: "agent_task", - params: { - query: "Summarize new docs added to folder 12 since the last run.", - }, - }, - ], - execution: { - timeout_seconds: 600, - max_retries: 2, - retry_backoff: "exponential", - concurrency: "drop_if_running", - on_failure: [], - }, - metadata: { tags: [] }, - }, - triggers: [ - { - type: "schedule", - params: { - cron: "0 9 * * 1-5", - timezone: "UTC", - }, - static_inputs: {}, - enabled: true, - }, - ], -} as const; diff --git a/surfsense_web/lib/automations/schedule-builder.ts b/surfsense_web/lib/automations/schedule-builder.ts new file mode 100644 index 000000000..37f4cfa14 --- /dev/null +++ b/surfsense_web/lib/automations/schedule-builder.ts @@ -0,0 +1,132 @@ +/** + * Bidirectional bridge between a friendly schedule model and the 5-field cron + * expression the backend ``schedule`` trigger expects (see + * ``app/automations/triggers/schedule/params.py``). + * + * The form builder never asks users to type cron. They pick a frequency + time + * (+ days), which ``toCron`` compiles. On edit we ``fromCron`` an existing + * expression back into the model; anything we don't recognize returns ``null`` + * so the caller can fall back to a raw-cron escape hatch instead of silently + * losing the user's schedule. + * + * The recognized patterns are intentionally the same family that + * ``describe-cron.ts`` humanizes, keeping the picker and the label in sync. + */ + +export type ScheduleFrequency = "hourly" | "daily" | "weekdays" | "weekly" | "monthly"; + +export interface ScheduleModel { + frequency: ScheduleFrequency; + /** 0-23. Ignored for ``hourly``. */ + hour: number; + /** 0-59. */ + minute: number; + /** 0 (Sun) - 6 (Sat). Used by ``weekly``. */ + daysOfWeek: number[]; + /** 1-31. Used by ``monthly``. */ + dayOfMonth: number; +} + +/** Sunday-first, matching cron's 0-6 day-of-week numbering. */ +export const WEEKDAY_OPTIONS: ReadonlyArray<{ value: number; short: string; long: string }> = [ + { value: 1, short: "Mon", long: "Monday" }, + { value: 2, short: "Tue", long: "Tuesday" }, + { value: 3, short: "Wed", long: "Wednesday" }, + { value: 4, short: "Thu", long: "Thursday" }, + { value: 5, short: "Fri", long: "Friday" }, + { value: 6, short: "Sat", long: "Saturday" }, + { value: 0, short: "Sun", long: "Sunday" }, +]; + +export const FREQUENCY_OPTIONS: ReadonlyArray<{ value: ScheduleFrequency; label: string }> = [ + { value: "hourly", label: "Every hour" }, + { value: "daily", label: "Every day" }, + { value: "weekdays", label: "Every weekday (Mon\u2013Fri)" }, + { value: "weekly", label: "Specific days of the week" }, + { value: "monthly", label: "Once a month" }, +]; + +export const DEFAULT_SCHEDULE: ScheduleModel = { + frequency: "weekdays", + hour: 9, + minute: 0, + daysOfWeek: [1], + dayOfMonth: 1, +}; + +function isInt(value: string): boolean { + return /^\d+$/.test(value); +} + +function clamp(value: number, min: number, max: number): number { + if (Number.isNaN(value)) return min; + return Math.min(max, Math.max(min, value)); +} + +/** Compile a schedule model into a 5-field cron expression. */ +export function toCron(model: ScheduleModel): string { + const minute = clamp(model.minute, 0, 59); + const hour = clamp(model.hour, 0, 23); + + switch (model.frequency) { + case "hourly": + return `${minute} * * * *`; + case "daily": + return `${minute} ${hour} * * *`; + case "weekdays": + return `${minute} ${hour} * * 1-5`; + case "weekly": { + const days = [...new Set(model.daysOfWeek)].sort((a, b) => a - b); + // Guard against an empty selection producing an invalid cron. + const dow = days.length > 0 ? days.join(",") : "1"; + return `${minute} ${hour} * * ${dow}`; + } + case "monthly": + return `${minute} ${hour} ${clamp(model.dayOfMonth, 1, 31)} * *`; + } +} + +/** + * Parse a 5-field cron expression back into a schedule model. Returns ``null`` + * for anything outside the recognized pattern family so callers can fall back + * to the raw-cron field. + */ +export function fromCron(cron: string): ScheduleModel | null { + const parts = cron.trim().split(/\s+/); + if (parts.length !== 5) return null; + + const [minute, hour, dom, month, dow] = parts; + + // Hourly: "M * * * *" + if (month === "*" && dom === "*" && dow === "*" && hour === "*" && isInt(minute)) { + return { ...DEFAULT_SCHEDULE, frequency: "hourly", minute: Number(minute) }; + } + + // Everything below requires concrete minute + hour. + if (!isInt(minute) || !isInt(hour)) return null; + + const base = { hour: Number(hour), minute: Number(minute) }; + + // Daily: "M H * * *" + if (month === "*" && dom === "*" && dow === "*") { + return { ...DEFAULT_SCHEDULE, ...base, frequency: "daily" }; + } + + // Weekdays: "M H * * 1-5" + if (month === "*" && dom === "*" && dow === "1-5") { + return { ...DEFAULT_SCHEDULE, ...base, frequency: "weekdays" }; + } + + // Weekly: "M H * * 1,3,5" + if (month === "*" && dom === "*" && /^[0-6](,[0-6])*$/.test(dow)) { + const daysOfWeek = [...new Set(dow.split(",").map(Number))].sort((a, b) => a - b); + return { ...DEFAULT_SCHEDULE, ...base, frequency: "weekly", daysOfWeek }; + } + + // Monthly: "M H D * *" + if (month === "*" && dow === "*" && isInt(dom)) { + return { ...DEFAULT_SCHEDULE, ...base, frequency: "monthly", dayOfMonth: Number(dom) }; + } + + return null; +} From 6b76f8c138eea5a9f0c7d102310286bcc62f2c75 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" <vermarohanfinal@gmail.com> Date: Thu, 28 May 2026 21:29:24 -0700 Subject: [PATCH 157/198] refactor(automations): update icons and button labels in empty state and header components - Replaced the FileJson icon with SquarePen in both AutomationsEmptyState and AutomationsHeader components. - Updated button label from "Create via JSON" to "Create manually" for clarity in the automation creation process. --- .../automations/components/automations-empty-state.tsx | 6 +++--- .../automations/components/automations-header.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx index 83fa52fa8..cc54c5e94 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx @@ -1,5 +1,5 @@ "use client"; -import { FileJson, MessageSquarePlus, Workflow } from "lucide-react"; +import { MessageSquarePlus, SquarePen, Workflow } from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; @@ -35,8 +35,8 @@ export function AutomationsEmptyState({ searchSpaceId, canCreate }: AutomationsE </Button> <Button asChild variant="outline"> <Link href={`/dashboard/${searchSpaceId}/automations/new`}> - <FileJson className="mr-2 h-4 w-4" /> - Create via JSON + <SquarePen className="mr-2 h-4 w-4" /> + Create manually </Link> </Button> </div> diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx index 544c6b7ac..8d5fab033 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx @@ -1,5 +1,5 @@ "use client"; -import { FileJson, MessageSquarePlus } from "lucide-react"; +import { MessageSquarePlus, SquarePen } from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; @@ -42,8 +42,8 @@ export function AutomationsHeader({ <div className="flex items-center gap-2"> <Button asChild size="sm" variant="outline"> <Link href={`/dashboard/${searchSpaceId}/automations/new`}> - <FileJson className="mr-2 h-4 w-4" /> - Create via JSON + <SquarePen className="mr-2 h-4 w-4" /> + Create manually </Link> </Button> <Button asChild size="sm"> From 9b9e6828c7bb5f28da0ff2006f8689583377704a Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" <vermarohanfinal@gmail.com> Date: Thu, 28 May 2026 21:44:22 -0700 Subject: [PATCH 158/198] refactor(automations): enhance mention handling in task parameters - Updated the `mentionParams` function to separate document and connector mentions, improving clarity and organization of the output. - Modified the `mentionsFromParams` function to correctly handle and categorize mentions from parameters, ensuring connectors are processed separately. - Adjusted documentation comments for better understanding of the changes in mention handling. --- .../lib/automations/builder-schema.ts | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/surfsense_web/lib/automations/builder-schema.ts b/surfsense_web/lib/automations/builder-schema.ts index a6ed08c09..a9349c56f 100644 --- a/surfsense_web/lib/automations/builder-schema.ts +++ b/surfsense_web/lib/automations/builder-schema.ts @@ -148,26 +148,33 @@ export function scheduleToCron(schedule: BuilderSchedule): string { * Project a task's @-mentions into the ``agent_task`` param fields the backend * understands (the same names the chat ``new_chat`` request uses, minus * SurfSense docs). Returns an empty object when there are no mentions so the - * params stay clean. ``mentioned_documents`` carries full chip metadata so the - * run can resolve titles/paths and the form can round-trip the chips back. + * params stay clean. + * + * ``mentioned_documents`` carries doc/folder chip metadata (so the run can + * resolve titles to paths); connectors live only in ``mentioned_connectors`` / + * ``mentioned_connector_ids`` to avoid duplicating them across buckets. */ function mentionParams(mentions: MentionedDocumentInfo[]): Record<string, unknown> { if (mentions.length === 0) return {}; const documentIds: number[] = []; const folderIds: number[] = []; const connectorIds: number[] = []; + const documents: MentionedDocumentInfo[] = []; const connectors: MentionedDocumentInfo[] = []; for (const mention of mentions) { if (mention.kind === "folder") { folderIds.push(mention.id); + documents.push(mention); } else if (mention.kind === "connector") { connectorIds.push(mention.id); connectors.push(mention); } else { documentIds.push(mention.id); + documents.push(mention); } } - const out: Record<string, unknown> = { mentioned_documents: mentions }; + const out: Record<string, unknown> = {}; + if (documents.length > 0) out.mentioned_documents = documents; if (documentIds.length > 0) out.mentioned_document_ids = documentIds; if (folderIds.length > 0) out.mentioned_folder_ids = folderIds; if (connectorIds.length > 0) { @@ -294,17 +301,26 @@ function coerceMention(raw: unknown): MentionedDocumentInfo | null { } /** - * Rebuild a task's mention chips from step params. Returns ``null`` when the - * step carries mention IDs that aren't backed by usable ``mentioned_documents`` - * metadata (e.g. hand-edited JSON), so the caller can fall back to JSON mode - * rather than silently dropping those IDs on the next save. + * Rebuild a task's mention chips from step params. Doc/folder chips come from + * ``mentioned_documents``; connector chips from ``mentioned_connectors`` (kept + * in their own bucket). Returns ``null`` when the step carries mention IDs that + * aren't backed by usable chip metadata (e.g. hand-edited JSON), so the caller + * can fall back to JSON mode rather than silently dropping those IDs on save. */ function mentionsFromParams(params: Record<string, unknown>): MentionedDocumentInfo[] | null { - const rawList = Array.isArray(params.mentioned_documents) ? params.mentioned_documents : []; const mentions: MentionedDocumentInfo[] = []; - for (const raw of rawList) { + const docList = Array.isArray(params.mentioned_documents) ? params.mentioned_documents : []; + for (const raw of docList) { const mention = coerceMention(raw); - if (mention) mentions.push(mention); + // Connectors belong in their own bucket; ignore any that leak in here. + if (mention && mention.kind !== "connector") mentions.push(mention); + } + const connectorList = Array.isArray(params.mentioned_connectors) + ? params.mentioned_connectors + : []; + for (const raw of connectorList) { + const mention = coerceMention(raw); + if (mention && mention.kind === "connector") mentions.push(mention); } const haveByKind = { From 40ca9e6ed2d17b7a112fb9424c31fbedc9ae1e28 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" <vermarohanfinal@gmail.com> Date: Thu, 28 May 2026 22:35:14 -0700 Subject: [PATCH 159/198] refactor: remove `search_surfsense_docs` tool and related references - Deleted the `search_surfsense_docs` tool and its associated files, streamlining the agent's toolset. - Updated various components and prompts to remove references to the now-removed tool, ensuring consistency across the codebase. - Adjusted documentation to direct users to the SurfSense documentation link for product-related queries instead. --- .../146_drop_surfsense_docs_tables.py | 129 +++++++++ .../system_prompt/prompts/citations/on.md | 4 +- .../prompts/dynamic_context/private.md | 4 +- .../prompts/dynamic_context/team.md | 4 +- .../system_prompt/prompts/kb_first.md | 8 +- .../prompts/providers/anthropic.md | 2 +- .../prompts/providers/deepseek.md | 2 +- .../system_prompt/prompts/providers/google.md | 2 +- .../prompts/providers/openai_classic.md | 5 +- .../system_prompt/prompts/routing.md | 9 +- .../tools/search_surfsense_docs/__init__.py | 1 - .../search_surfsense_docs/description.md | 10 - .../tools/search_surfsense_docs/example.md | 15 -- .../main_agent/tools/index.py | 1 - .../builtins/research/system_prompt.md | 1 - .../builtins/research/tools/__init__.py | 4 +- .../builtins/research/tools/index.py | 2 - .../research/tools/search_surfsense_docs.py | 145 ---------- .../app/agents/new_chat/feature_flags.py | 2 +- .../app/agents/new_chat/mention_resolver.py | 10 +- .../new_chat/prompts/base/citations_on.md | 3 +- .../prompts/base/kb_only_policy_private.md | 2 +- .../prompts/base/kb_only_policy_team.md | 2 +- .../prompts/base/tool_routing_private.md | 1 + .../prompts/base/tool_routing_team.md | 1 + .../app/agents/new_chat/prompts/composer.py | 1 - .../prompts/examples/search_surfsense_docs.md | 9 - .../prompts/tools/search_surfsense_docs.md | 7 - .../skills/builtin/email-drafting/SKILL.md | 1 - .../skills/builtin/kb-research/SKILL.md | 2 +- .../skills/builtin/meeting-prep/SKILL.md | 2 +- .../skills/builtin/report-writing/SKILL.md | 2 +- .../skills/builtin/slack-summary/SKILL.md | 1 - .../app/agents/new_chat/subagents/config.py | 5 +- .../app/agents/new_chat/tools/__init__.py | 3 - .../app/agents/new_chat/tools/registry.py | 10 - .../new_chat/tools/search_surfsense_docs.py | 174 ------------ surfsense_backend/app/app.py | 8 - surfsense_backend/app/db.py | 45 ---- surfsense_backend/app/routes/__init__.py | 2 - .../app/routes/new_chat_routes.py | 2 - .../app/routes/surfsense_docs_routes.py | 172 ------------ surfsense_backend/app/schemas/new_chat.py | 4 - .../app/schemas/surfsense_docs.py | 43 --- .../app/tasks/chat/stream_new_chat.py | 112 +------- .../tasks/chat/streaming/context/__init__.py | 6 +- .../chat/streaming/context/mentioned_docs.py | 58 ---- .../flows/new_chat/initial_thinking_step.py | 26 +- .../streaming/flows/new_chat/input_state.py | 51 +--- .../streaming/flows/new_chat/orchestrator.py | 6 +- .../app/tasks/surfsense_docs_indexer.py | 249 ------------------ surfsense_backend/app/utils/surfsense_docs.py | 13 - .../scripts/seed_surfsense_docs.py | 40 --- .../test_default_permissions_layering.py | 1 - .../new_chat/test_specialized_subagents.py | 14 +- .../test_parallel_refactor_parity.py | 35 +-- .../components/builder/mention-task-input.tsx | 9 +- .../new-chat/[[...chat_id]]/page.tsx | 36 +-- .../atoms/chat/mentioned-documents.atom.ts | 5 +- .../assistant-ui/inline-citation.tsx | 159 +---------- .../components/assistant-ui/thread.tsx | 2 +- .../layout/ui/sidebar/DocumentsSidebar.tsx | 1 - .../new-chat/document-mention-picker.tsx | 113 ++------ .../contracts/enums/connectorIcons.tsx | 3 - surfsense_web/contracts/enums/toolIcons.tsx | 3 - .../contracts/types/document.types.ts | 55 ---- .../lib/apis/documents-api.service.ts | 46 ---- surfsense_web/lib/chat/thread-persistence.ts | 1 - .../lib/documents/document-type-labels.ts | 1 - surfsense_web/lib/query-client/cache-keys.ts | 1 - surfsense_web/tsc_out.txt | Bin 0 -> 32582 bytes 71 files changed, 232 insertions(+), 1676 deletions(-) create mode 100644 surfsense_backend/alembic/versions/146_drop_surfsense_docs_tables.py delete mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/__init__.py delete mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/description.md delete mode 100644 surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/example.md delete mode 100644 surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/search_surfsense_docs.py delete mode 100644 surfsense_backend/app/agents/new_chat/prompts/examples/search_surfsense_docs.md delete mode 100644 surfsense_backend/app/agents/new_chat/prompts/tools/search_surfsense_docs.md delete mode 100644 surfsense_backend/app/agents/new_chat/tools/search_surfsense_docs.py delete mode 100644 surfsense_backend/app/routes/surfsense_docs_routes.py delete mode 100644 surfsense_backend/app/schemas/surfsense_docs.py delete mode 100644 surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py delete mode 100644 surfsense_backend/app/tasks/surfsense_docs_indexer.py delete mode 100644 surfsense_backend/app/utils/surfsense_docs.py delete mode 100644 surfsense_backend/scripts/seed_surfsense_docs.py create mode 100644 surfsense_web/tsc_out.txt diff --git a/surfsense_backend/alembic/versions/146_drop_surfsense_docs_tables.py b/surfsense_backend/alembic/versions/146_drop_surfsense_docs_tables.py new file mode 100644 index 000000000..725405834 --- /dev/null +++ b/surfsense_backend/alembic/versions/146_drop_surfsense_docs_tables.py @@ -0,0 +1,129 @@ +"""Drop Surfsense docs tables (feature removed end to end) + +Revision ID: 146 +Revises: 145 +Create Date: 2026-05-28 + +Removes the SurfSense product-documentation feature: the +``surfsense_docs_documents`` and ``surfsense_docs_chunks`` tables (created +in revision 60) and the GIN trigram index on the title column (added in +revision 67). The docs were seeded at startup from local MDX files, so no +user data is lost. Downgrade recreates the tables and indexes. +""" + +from collections.abc import Sequence + +from alembic import op +from app.config import config + +# revision identifiers, used by Alembic. +revision: str = "146" +down_revision: str | None = "145" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + +# Embedding dimension is required to recreate the vector columns on downgrade. +EMBEDDING_DIM = config.embedding_model_instance.dimension + + +def upgrade() -> None: + """Drop surfsense docs tables and all their indexes.""" + # Trigram index from revision 67 + op.execute("DROP INDEX IF EXISTS idx_surfsense_docs_title_trgm") + + # Full-text search indexes + op.execute("DROP INDEX IF EXISTS surfsense_docs_chunks_search_index") + op.execute("DROP INDEX IF EXISTS surfsense_docs_documents_search_index") + + # Vector indexes + op.execute("DROP INDEX IF EXISTS surfsense_docs_chunks_vector_index") + op.execute("DROP INDEX IF EXISTS surfsense_docs_documents_vector_index") + + # B-tree indexes + op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_chunks_document_id") + op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_documents_updated_at") + op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_documents_content_hash") + op.execute("DROP INDEX IF EXISTS ix_surfsense_docs_documents_source") + + # Tables (chunks first due to FK) + op.execute("DROP TABLE IF EXISTS surfsense_docs_chunks") + op.execute("DROP TABLE IF EXISTS surfsense_docs_documents") + + +def downgrade() -> None: + """Recreate surfsense docs tables and indexes (reverses revisions 60 + 67).""" + op.execute( + f""" + CREATE TABLE IF NOT EXISTS surfsense_docs_documents ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + source VARCHAR NOT NULL UNIQUE, + title VARCHAR NOT NULL, + content TEXT NOT NULL, + content_hash VARCHAR NOT NULL, + embedding vector({EMBEDDING_DIM}), + updated_at TIMESTAMP WITH TIME ZONE + ); + """ + ) + op.execute( + f""" + CREATE TABLE IF NOT EXISTS surfsense_docs_chunks ( + id SERIAL PRIMARY KEY, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + content TEXT NOT NULL, + embedding vector({EMBEDDING_DIM}), + document_id INTEGER NOT NULL REFERENCES surfsense_docs_documents(id) ON DELETE CASCADE + ); + """ + ) + + # B-tree indexes + op.execute( + "CREATE INDEX IF NOT EXISTS ix_surfsense_docs_documents_source ON surfsense_docs_documents(source)" + ) + op.execute( + "CREATE INDEX IF NOT EXISTS ix_surfsense_docs_documents_content_hash ON surfsense_docs_documents(content_hash)" + ) + op.execute( + "CREATE INDEX IF NOT EXISTS ix_surfsense_docs_documents_updated_at ON surfsense_docs_documents(updated_at)" + ) + op.execute( + "CREATE INDEX IF NOT EXISTS ix_surfsense_docs_chunks_document_id ON surfsense_docs_chunks(document_id)" + ) + + # Vector indexes + op.execute( + """ + CREATE INDEX IF NOT EXISTS surfsense_docs_documents_vector_index + ON surfsense_docs_documents USING hnsw (embedding public.vector_cosine_ops); + """ + ) + op.execute( + """ + CREATE INDEX IF NOT EXISTS surfsense_docs_chunks_vector_index + ON surfsense_docs_chunks USING hnsw (embedding public.vector_cosine_ops); + """ + ) + + # Full-text search indexes + op.execute( + """ + CREATE INDEX IF NOT EXISTS surfsense_docs_documents_search_index + ON surfsense_docs_documents USING gin (to_tsvector('english', content)); + """ + ) + op.execute( + """ + CREATE INDEX IF NOT EXISTS surfsense_docs_chunks_search_index + ON surfsense_docs_chunks USING gin (to_tsvector('english', content)); + """ + ) + + # Trigram index from revision 67 + op.execute( + """ + CREATE INDEX IF NOT EXISTS idx_surfsense_docs_title_trgm + ON surfsense_docs_documents USING gin (title gin_trgm_ops); + """ + ) diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/citations/on.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/citations/on.md index e61a0bffb..2abd95d5a 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/citations/on.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/citations/on.md @@ -4,8 +4,8 @@ never invent ids you didn't see. Citation ids are resolved by exact-match lookup; a wrong id silently breaks the link, so when in doubt, omit. ### Channel A — chunk blocks injected this turn -When `search_surfsense_docs` or `web_search` returns `<document>` / -`<chunk id='…'>` blocks in this turn: +When `web_search` returns `<document>` / `<chunk id='…'>` blocks in this +turn: 1. For each factual statement taken from those chunks, add `[citation:chunk_id]` using the **exact** id from a visible diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/private.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/private.md index 71c86be40..8f2bfca4e 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/private.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/private.md @@ -20,8 +20,8 @@ it to resolve paths the user describes in natural language ("my Q2 roadmap", delegating to a specialist. `<document>` and `<chunk id='…'>` blocks are chunked indexed content returned -by KB search (from `search_surfsense_docs`, or backing `<priority_documents>`). -Each chunk carries a stable `id` attribute. +by KB search (backing `<priority_documents>`). Each chunk carries a stable +`id` attribute. If a block doesn't appear this turn, work from the conversation alone. </dynamic_context> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/team.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/team.md index 592c2ed9c..a5892c23a 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/team.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/dynamic_context/team.md @@ -20,8 +20,8 @@ week's planning notes") into concrete document references before delegating to a specialist. `<document>` and `<chunk id='…'>` blocks are chunked indexed content returned -by KB search (from `search_surfsense_docs`, or backing `<priority_documents>`). -Each chunk carries a stable `id` attribute. +by KB search (backing `<priority_documents>`). Each chunk carries a stable +`id` attribute. If a block doesn't appear this turn, work from the conversation alone. </dynamic_context> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/kb_first.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/kb_first.md index f06a52c1d..80fa4bf8f 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/kb_first.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/kb_first.md @@ -1,19 +1,21 @@ <knowledge_base_first> CRITICAL — ground factual answers in what you actually receive this turn: - injected workspace context (see `<dynamic_context>`), -- results from your own tool calls (`search_surfsense_docs`, `web_search`, - `scrape_webpage`), +- results from your own tool calls (`web_search`, `scrape_webpage`), - or substantive summaries returned by a `task` specialist you invoked. Do **not** answer factual or informational questions from general knowledge unless the user explicitly authorises it after you say you couldn't find enough in those sources. The flow when nothing is found: -1. Say you couldn't find enough in their workspace, docs, or tool output. +1. Say you couldn't find enough in their workspace or tool output. 2. Ask: *"Would you like me to answer from my general knowledge instead?"* 3. Only answer from general knowledge after a clear yes. This rule does NOT apply to: casual conversation · meta-questions about SurfSense ("what can you do?") · formatting or analysis of content already in chat · clear rewrite/edit instructions · lightweight web research. + +For "how do I use SurfSense" / product-documentation questions, point the +user to https://www.surfsense.com/docs. </knowledge_base_first> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/anthropic.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/anthropic.md index 89154c443..d852f5955 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/anthropic.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/anthropic.md @@ -5,7 +5,7 @@ Structured reasoning: - For non-trivial work, `<thinking>` / short `<plan>` before tool calls is fine. Professional objectivity: -- Accuracy over flattery; verify with **search_surfsense_docs**, **web_search**, **scrape_webpage**, or **task** when unsure — don’t invent connector access. +- Accuracy over flattery; verify with **web_search**, **scrape_webpage**, or **task** when unsure — don’t invent connector access. Task management: - For 3+ steps, use todo tooling; update statuses promptly. diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/deepseek.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/deepseek.md index 4254e9ed5..01d56999f 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/deepseek.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/deepseek.md @@ -13,6 +13,6 @@ Attribution: Tool calls: - Parallelise independent calls. -- Prefer **search_surfsense_docs** for SurfSense docs/product questions before **web_search** when that fits the ask. +- For SurfSense docs/product questions, point the user to https://www.surfsense.com/docs. - Don’t invent paths, chunk ids, or URLs — only values from tools or the user. </provider_hints> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/google.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/google.md index dc5073538..32ed959c1 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/google.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/google.md @@ -7,7 +7,7 @@ Output style: - GitHub-flavoured Markdown; monospace-friendly. Workflow (Understand → Plan → Act → Verify): -1. **Understand:** parse the ask; use **search_surfsense_docs** / injected workspace context before guessing. +1. **Understand:** parse the ask; use injected workspace context before guessing. 2. **Plan:** for multi-step work, a short plan first. 3. **Act:** only with tools you actually have on this agent (see `<tools>` and `<tool_routing>`). Connector work → **task**. 4. **Verify:** re-read or re-search only when it materially reduces risk. diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/openai_classic.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/openai_classic.md index 7ff3ec912..8596c42cd 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/openai_classic.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/providers/openai_classic.md @@ -15,6 +15,7 @@ Output style: Tool calls: - Parallelise independent calls in one turn. -- Prefer **search_surfsense_docs** for SurfSense-product questions, **web_search** / **scrape_webpage** - for fresh public facts; integrations and heavy workflows → **task**. +- For SurfSense-product questions, point the user to https://www.surfsense.com/docs; + use **web_search** / **scrape_webpage** for fresh public facts; integrations and + heavy workflows → **task**. </provider_hints> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md index 1038dde3d..28cf0ac63 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/routing.md @@ -3,10 +3,7 @@ You have two execution channels. Pick the one that owns the work — never simulate one with the other. ### 1. Direct tools (you call them yourself) -- `search_surfsense_docs` — SurfSense product docs (setup, configuration, - connector docs, feature behavior). -- `web_search` — search the public web (anything outside SurfSense docs and - the workspace KB). +- `web_search` — search the public web (anything outside the workspace KB). - `scrape_webpage` — fetch the body of a specific public URL. - `update_memory` — curate persistent memory (see `<memory_protocol>`). - `write_todos` — maintain a structured plan when the turn series spans @@ -14,6 +11,10 @@ simulate one with the other. `in_progress` **before** the `task` call that handles it, `completed` once the call returns. Skip for single-step requests. +**Questions about how to use SurfSense itself** (setup, configuration, +connectors, feature behavior) — point the user to the documentation: +https://www.surfsense.com/docs. There is no docs-search tool; give the link. + **You have NO filesystem tools.** Any read, write, edit, move, rename, or search inside the user's workspace goes through `task(knowledge_base, …)` — never via `write_file`, `ls`, or any direct file operation. diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/__init__.py deleted file mode 100644 index c2cda318e..000000000 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""``search_surfsense_docs`` — description + few-shot examples.""" diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/description.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/description.md deleted file mode 100644 index 256d3f3a4..000000000 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/description.md +++ /dev/null @@ -1,10 +0,0 @@ -- `search_surfsense_docs` — Search official SurfSense documentation (product - help). - - Use when the user asks how SurfSense itself works — setup, configuration, - connector documentation, feature behavior, anything covered in the - product docs. - - Not a substitute for `task` when the user wants actions inside a - connected service (Gmail, Slack, Jira, Notion, etc.). - - Args: `query`, `top_k` (default 10). - - Returns doc excerpts; chunk ids may appear for attribution — see - `<citations>` for the contract. diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/example.md b/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/example.md deleted file mode 100644 index d53ad8c91..000000000 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/system_prompt/prompts/tools/search_surfsense_docs/example.md +++ /dev/null @@ -1,15 +0,0 @@ -<example> -user: "How do I install SurfSense?" -→ search_surfsense_docs(query="installation setup") -</example> - -<example> -user: "What connectors does SurfSense support?" -→ search_surfsense_docs(query="available connectors integrations") -</example> - -<example> -user: "How do I set up the Notion connector?" -→ search_surfsense_docs(query="Notion connector setup configuration") -(Changing data inside Notion itself → `task(notion, …)`, not this tool.) -</example> diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py index 88509eda7..70fb42c0d 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/index.py @@ -6,7 +6,6 @@ Connector integrations, MCP, deliverables, etc. are delegated via ``task`` subag from __future__ import annotations MAIN_AGENT_SURFSENSE_TOOL_NAMES_ORDERED: tuple[str, ...] = ( - "search_surfsense_docs", "web_search", "scrape_webpage", "update_memory", diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md index 3eabd8ee0..1b9ccaefa 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/system_prompt.md @@ -8,7 +8,6 @@ Gather and synthesize evidence using SurfSense research tools with clear citatio <available_tools> - `web_search` - `scrape_webpage` -- `search_surfsense_docs` </available_tools> <tool_policy> diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/__init__.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/__init__.py index 414cc96f4..7234942b6 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/__init__.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/__init__.py @@ -1,11 +1,9 @@ -"""Research-stage tools: web search, scrape, and in-product doc search.""" +"""Research-stage tools: web search and scrape.""" from .scrape_webpage import create_scrape_webpage_tool -from .search_surfsense_docs import create_search_surfsense_docs_tool from .web_search import create_web_search_tool __all__ = [ "create_scrape_webpage_tool", - "create_search_surfsense_docs_tool", "create_web_search_tool", ] diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/index.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/index.py index ea544a8da..d8abce46c 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/index.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/index.py @@ -9,7 +9,6 @@ from langchain_core.tools import BaseTool from app.agents.new_chat.permissions import Ruleset from .scrape_webpage import create_scrape_webpage_tool -from .search_surfsense_docs import create_search_surfsense_docs_tool from .web_search import create_web_search_tool NAME = "research" @@ -27,5 +26,4 @@ def load_tools( available_connectors=d.get("available_connectors"), ), create_scrape_webpage_tool(firecrawl_api_key=d.get("firecrawl_api_key")), - create_search_surfsense_docs_tool(db_session=d["db_session"]), ] diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/search_surfsense_docs.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/search_surfsense_docs.py deleted file mode 100644 index ccc5c49e2..000000000 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/research/tools/search_surfsense_docs.py +++ /dev/null @@ -1,145 +0,0 @@ -"""Semantic search over pre-indexed in-app documentation chunks for user how-to questions.""" - -import asyncio -import json - -from langchain_core.tools import tool -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession - -from app.db import SurfsenseDocsChunk, SurfsenseDocsDocument -from app.utils.document_converters import embed_text -from app.utils.surfsense_docs import surfsense_docs_public_url - - -def format_surfsense_docs_results(results: list[tuple]) -> str: - """Format (chunk, document) rows as XML with ``doc-`` chunk IDs for citations and UI routing.""" - if not results: - return "No relevant Surfsense documentation found for your query." - - # Group chunks by document - grouped: dict[int, dict] = {} - for chunk, doc in results: - public_url = surfsense_docs_public_url(doc.source) - if doc.id not in grouped: - grouped[doc.id] = { - "document_id": f"doc-{doc.id}", - "document_type": "SURFSENSE_DOCS", - "title": doc.title, - "url": public_url, - "metadata": {"source": doc.source, "public_url": public_url}, - "chunks": [], - } - grouped[doc.id]["chunks"].append( - { - "chunk_id": f"doc-{chunk.id}", - "content": chunk.content, - } - ) - - # Render XML matching format_documents_for_context structure - parts: list[str] = [] - for g in grouped.values(): - metadata_json = json.dumps(g["metadata"], ensure_ascii=False) - - parts.append("<document>") - parts.append("<document_metadata>") - parts.append(f" <document_id>{g['document_id']}</document_id>") - parts.append(f" <document_type>{g['document_type']}</document_type>") - parts.append(f" <title><![CDATA[{g['title']}]]>") - parts.append(f" ") - parts.append(f" ") - parts.append("") - parts.append("") - parts.append("") - - for ch in g["chunks"]: - parts.append( - f" " - ) - - parts.append("") - parts.append("") - parts.append("") - - return "\n".join(parts).strip() - - -async def search_surfsense_docs_async( - query: str, - db_session: AsyncSession, - top_k: int = 10, -) -> str: - """ - Search Surfsense documentation using vector similarity. - - Args: - query: The search query about Surfsense usage - db_session: Database session for executing queries - top_k: Number of results to return - - Returns: - Formatted string with relevant documentation content - """ - # Get embedding for the query - query_embedding = await asyncio.to_thread(embed_text, query) - - # Vector similarity search on chunks, joining with documents - stmt = ( - select(SurfsenseDocsChunk, SurfsenseDocsDocument) - .join( - SurfsenseDocsDocument, - SurfsenseDocsChunk.document_id == SurfsenseDocsDocument.id, - ) - .order_by(SurfsenseDocsChunk.embedding.op("<=>")(query_embedding)) - .limit(top_k) - ) - - result = await db_session.execute(stmt) - rows = result.all() - - return format_surfsense_docs_results(rows) - - -def create_search_surfsense_docs_tool(db_session: AsyncSession): - """ - Factory function to create the search_surfsense_docs tool. - - Args: - db_session: Database session for executing queries - - Returns: - A configured tool function for searching Surfsense documentation - """ - - @tool - async def search_surfsense_docs(query: str, top_k: int = 10) -> str: - """ - Search Surfsense documentation for help with using the application. - - Use this tool when the user asks questions about: - - How to use Surfsense features - - Installation and setup instructions - - Configuration options and settings - - Troubleshooting common issues - - Available connectors and integrations - - Browser extension usage - - API documentation - - This searches the official Surfsense documentation that was indexed - at deployment time. It does NOT search the user's personal knowledge base. - - Args: - query: The search query about Surfsense usage or features - top_k: Number of documentation chunks to retrieve (default: 10) - - Returns: - Relevant documentation content formatted with chunk IDs for citations - """ - return await search_surfsense_docs_async( - query=query, - db_session=db_session, - top_k=top_k, - ) - - return search_surfsense_docs diff --git a/surfsense_backend/app/agents/new_chat/feature_flags.py b/surfsense_backend/app/agents/new_chat/feature_flags.py index 3cea051ef..27188fac3 100644 --- a/surfsense_backend/app/agents/new_chat/feature_flags.py +++ b/surfsense_backend/app/agents/new_chat/feature_flags.py @@ -104,7 +104,7 @@ class AgentFeatureFlags: # ``tools/google_drive``, ``tools/dropbox``, ``tools/onedrive``, # ``tools/google_calendar``, ``tools/confluence``, ``tools/discord``, # ``tools/teams``, ``tools/luma``, ``connected_accounts``, - # ``update_memory``, ``search_surfsense_docs``) now acquire fresh + # ``update_memory``) now acquire fresh # short-lived ``AsyncSession`` instances per call via # :data:`async_session_maker`. The factory still accepts ``db_session`` # for registry compatibility but ``del``'s it immediately — see any diff --git a/surfsense_backend/app/agents/new_chat/mention_resolver.py b/surfsense_backend/app/agents/new_chat/mention_resolver.py index 6a025b947..f13dbc6ae 100644 --- a/surfsense_backend/app/agents/new_chat/mention_resolver.py +++ b/surfsense_backend/app/agents/new_chat/mention_resolver.py @@ -73,9 +73,8 @@ class ResolvedMentionSet: ``@Project Roadmap`` is never shadowed by a shorter prefix ``@Project``). - ``mentioned_document_ids`` collapses doc + surfsense_doc chips into - a single ordered, deduped list because the priority middleware - treats them uniformly downstream — see + ``mentioned_document_ids`` is an ordered, deduped list consumed by + the priority middleware downstream — see ``KnowledgePriorityMiddleware._compute_priority_paths``. """ @@ -103,7 +102,6 @@ async def resolve_mentions( search_space_id: int, mentioned_documents: list[MentionedDocumentInfo] | None, mentioned_document_ids: list[int] | None = None, - mentioned_surfsense_doc_ids: list[int] | None = None, mentioned_folder_ids: list[int] | None = None, ) -> ResolvedMentionSet: """Resolve every @-mention chip on a turn into virtual paths. @@ -111,8 +109,7 @@ async def resolve_mentions( The function takes both the ``mentioned_documents`` discriminated list (chip metadata used for substitution + persistence) and the parallel id arrays (``mentioned_document_ids``, - ``mentioned_surfsense_doc_ids``, ``mentioned_folder_ids``) for two - reasons: + ``mentioned_folder_ids``) for two reasons: * Legacy clients that haven't migrated to the unified chip list still send the id arrays — we treat the union as authoritative. @@ -142,7 +139,6 @@ async def resolve_mentions( dict.fromkeys( [ *(mentioned_document_ids or []), - *(mentioned_surfsense_doc_ids or []), *chip_doc_ids, ] ) diff --git a/surfsense_backend/app/agents/new_chat/prompts/base/citations_on.md b/surfsense_backend/app/agents/new_chat/prompts/base/citations_on.md index 56291bf3e..3562ce66e 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/base/citations_on.md +++ b/surfsense_backend/app/agents/new_chat/prompts/base/citations_on.md @@ -59,14 +59,13 @@ Do NOT cite document_id. Always use the chunk id. - NEVER create your own citation format - use the exact chunk_id values from the documents in the [citation:chunk_id] format - NEVER format citations as clickable links or as markdown links like "([citation:5](https://example.com))". Always use plain square brackets only - NEVER make up chunk IDs if you are unsure about the chunk_id. It is better to omit the citation than to guess -- Copy the EXACT chunk id from the XML - if it says ``, use [citation:doc-123] +- Copy the EXACT chunk id from the XML - if it says ``, use [citation:5] - If the chunk id is a URL like ``, use [citation:https://example.com/page] CORRECT citation formats: - [citation:5] (numeric chunk ID from knowledge base) -- [citation:doc-123] (for Surfsense documentation chunks) - [citation:https://example.com/article] (URL chunk ID from web search results) - [citation:chunk_id1], [citation:chunk_id2], [citation:chunk_id3] (multiple citations) diff --git a/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_private.md b/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_private.md index 9cc767e7e..073b75fa5 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_private.md +++ b/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_private.md @@ -7,7 +7,7 @@ CRITICAL RULE — KNOWLEDGE BASE FIRST, NEVER DEFAULT TO GENERAL KNOWLEDGE: 2. Ask the user: "Would you like me to answer from my general knowledge instead?" 3. ONLY provide a general-knowledge answer AFTER the user explicitly says yes. - This policy does NOT apply to: - * Casual conversation, greetings, or meta-questions about SurfSense itself (e.g., "what can you do?") + * Casual conversation, greetings, or meta-questions about SurfSense itself (e.g., "what can you do?"). For "how do I use SurfSense" / product-documentation questions, point the user to https://www.surfsense.com/docs. * Formatting, summarization, or analysis of content already present in the conversation * Following user instructions that are clearly task-oriented (e.g., "rewrite this in bullet points") * Tool-usage actions like generating reports, podcasts, images, or scraping webpages diff --git a/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_team.md b/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_team.md index 1d806dbae..1a43ed490 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_team.md +++ b/surfsense_backend/app/agents/new_chat/prompts/base/kb_only_policy_team.md @@ -7,7 +7,7 @@ CRITICAL RULE — KNOWLEDGE BASE FIRST, NEVER DEFAULT TO GENERAL KNOWLEDGE: 2. Ask: "Would you like me to answer from my general knowledge instead?" 3. ONLY provide a general-knowledge answer AFTER a team member explicitly says yes. - This policy does NOT apply to: - * Casual conversation, greetings, or meta-questions about SurfSense itself (e.g., "what can you do?") + * Casual conversation, greetings, or meta-questions about SurfSense itself (e.g., "what can you do?"). For "how do I use SurfSense" / product-documentation questions, point the user to https://www.surfsense.com/docs. * Formatting, summarization, or analysis of content already present in the conversation * Following user instructions that are clearly task-oriented (e.g., "rewrite this in bullet points") * Tool-usage actions like generating reports, podcasts, images, or scraping webpages diff --git a/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_private.md b/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_private.md index b8bb069e2..9121de879 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_private.md +++ b/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_private.md @@ -13,6 +13,7 @@ When to use which tool: - Knowledge base content (Notion, GitHub, files, notes) → automatically searched - Real-time public web data → call web_search - Reading a specific webpage → call scrape_webpage +- SurfSense product / how-to questions (setup, configuration, connectors, feature behavior) → point the user to the documentation: https://www.surfsense.com/docs **`task` subagents (when to delegate):** - **`linear_specialist`** — Linear-only investigations and tool use. diff --git a/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_team.md b/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_team.md index b081a2123..c5383be77 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_team.md +++ b/surfsense_backend/app/agents/new_chat/prompts/base/tool_routing_team.md @@ -13,6 +13,7 @@ When to use which tool: - Knowledge base content (Notion, GitHub, files, notes) → automatically searched - Real-time public web data → call web_search - Reading a specific webpage → call scrape_webpage +- SurfSense product / how-to questions (setup, configuration, connectors, feature behavior) → point the user to the documentation: https://www.surfsense.com/docs **`task` subagents (when to delegate):** - **`linear_specialist`** — Linear-only investigations and tool use. diff --git a/surfsense_backend/app/agents/new_chat/prompts/composer.py b/surfsense_backend/app/agents/new_chat/prompts/composer.py index 42f8303e6..412665813 100644 --- a/surfsense_backend/app/agents/new_chat/prompts/composer.py +++ b/surfsense_backend/app/agents/new_chat/prompts/composer.py @@ -151,7 +151,6 @@ def _read_fragment(subpath: str) -> str: # Ordered for reading flow: fundamentals first, then artifact generators, # then memory at the end (mirrors the legacy ``_ALL_TOOL_NAMES_ORDERED``). ALL_TOOL_NAMES_ORDERED: tuple[str, ...] = ( - "search_surfsense_docs", "web_search", "generate_podcast", "generate_video_presentation", diff --git a/surfsense_backend/app/agents/new_chat/prompts/examples/search_surfsense_docs.md b/surfsense_backend/app/agents/new_chat/prompts/examples/search_surfsense_docs.md deleted file mode 100644 index b90f2b7a7..000000000 --- a/surfsense_backend/app/agents/new_chat/prompts/examples/search_surfsense_docs.md +++ /dev/null @@ -1,9 +0,0 @@ - -- User: "How do I install SurfSense?" - - Call: `search_surfsense_docs(query="installation setup")` -- User: "What connectors does SurfSense support?" - - Call: `search_surfsense_docs(query="available connectors integrations")` -- User: "How do I set up the Notion connector?" - - Call: `search_surfsense_docs(query="Notion connector setup configuration")` -- User: "How do I use Docker to run SurfSense?" - - Call: `search_surfsense_docs(query="Docker installation setup")` diff --git a/surfsense_backend/app/agents/new_chat/prompts/tools/search_surfsense_docs.md b/surfsense_backend/app/agents/new_chat/prompts/tools/search_surfsense_docs.md deleted file mode 100644 index 133717fec..000000000 --- a/surfsense_backend/app/agents/new_chat/prompts/tools/search_surfsense_docs.md +++ /dev/null @@ -1,7 +0,0 @@ - -- search_surfsense_docs: Search the official SurfSense documentation. - - Use this tool when the user asks anything about SurfSense itself (the application they are using). - - Args: - - query: The search query about SurfSense - - top_k: Number of documentation chunks to retrieve (default: 10) - - Returns: Documentation content with chunk IDs for citations (prefixed with 'doc-', e.g., [citation:doc-123]) diff --git a/surfsense_backend/app/agents/new_chat/skills/builtin/email-drafting/SKILL.md b/surfsense_backend/app/agents/new_chat/skills/builtin/email-drafting/SKILL.md index 32e599e98..2dbc8ec43 100644 --- a/surfsense_backend/app/agents/new_chat/skills/builtin/email-drafting/SKILL.md +++ b/surfsense_backend/app/agents/new_chat/skills/builtin/email-drafting/SKILL.md @@ -1,7 +1,6 @@ --- name: email-drafting description: Draft an email matching the user's voice, with structured intent and CTA -allowed-tools: search_surfsense_docs --- # Email drafting diff --git a/surfsense_backend/app/agents/new_chat/skills/builtin/kb-research/SKILL.md b/surfsense_backend/app/agents/new_chat/skills/builtin/kb-research/SKILL.md index c268278ab..0f0b5ffbb 100644 --- a/surfsense_backend/app/agents/new_chat/skills/builtin/kb-research/SKILL.md +++ b/surfsense_backend/app/agents/new_chat/skills/builtin/kb-research/SKILL.md @@ -1,7 +1,7 @@ --- name: kb-research description: Structured approach to finding and synthesizing information from the user's knowledge base -allowed-tools: search_surfsense_docs, scrape_webpage, read_file, ls_tree, grep, web_search +allowed-tools: scrape_webpage, read_file, ls_tree, grep, web_search --- # Knowledge-base research diff --git a/surfsense_backend/app/agents/new_chat/skills/builtin/meeting-prep/SKILL.md b/surfsense_backend/app/agents/new_chat/skills/builtin/meeting-prep/SKILL.md index 9657eb078..5a375fbde 100644 --- a/surfsense_backend/app/agents/new_chat/skills/builtin/meeting-prep/SKILL.md +++ b/surfsense_backend/app/agents/new_chat/skills/builtin/meeting-prep/SKILL.md @@ -1,7 +1,7 @@ --- name: meeting-prep description: Pull together briefing materials before a scheduled meeting -allowed-tools: search_surfsense_docs, web_search, scrape_webpage, read_file +allowed-tools: web_search, scrape_webpage, read_file --- # Meeting preparation diff --git a/surfsense_backend/app/agents/new_chat/skills/builtin/report-writing/SKILL.md b/surfsense_backend/app/agents/new_chat/skills/builtin/report-writing/SKILL.md index 17ac2f391..cfea9593f 100644 --- a/surfsense_backend/app/agents/new_chat/skills/builtin/report-writing/SKILL.md +++ b/surfsense_backend/app/agents/new_chat/skills/builtin/report-writing/SKILL.md @@ -1,7 +1,7 @@ --- name: report-writing description: How to scope, draft, and revise a Markdown report artifact via generate_report -allowed-tools: generate_report, search_surfsense_docs, read_file +allowed-tools: generate_report, read_file --- # Report writing diff --git a/surfsense_backend/app/agents/new_chat/skills/builtin/slack-summary/SKILL.md b/surfsense_backend/app/agents/new_chat/skills/builtin/slack-summary/SKILL.md index 33b9e72a2..1a4c3da9f 100644 --- a/surfsense_backend/app/agents/new_chat/skills/builtin/slack-summary/SKILL.md +++ b/surfsense_backend/app/agents/new_chat/skills/builtin/slack-summary/SKILL.md @@ -1,7 +1,6 @@ --- name: slack-summary description: Distill a Slack channel or thread into actionable summary -allowed-tools: search_surfsense_docs --- # Slack summarization diff --git a/surfsense_backend/app/agents/new_chat/subagents/config.py b/surfsense_backend/app/agents/new_chat/subagents/config.py index b993d2b06..2cfd47441 100644 --- a/surfsense_backend/app/agents/new_chat/subagents/config.py +++ b/surfsense_backend/app/agents/new_chat/subagents/config.py @@ -46,7 +46,6 @@ logger = logging.getLogger(__name__) # ``glob``, ``grep``) plus the SurfSense-side read tools. EXPLORE_READ_TOOLS: frozenset[str] = frozenset( { - "search_surfsense_docs", "web_search", "scrape_webpage", "read_file", @@ -61,7 +60,6 @@ EXPLORE_READ_TOOLS: frozenset[str] = frozenset( # is needed, the parent should hand off to ``explore`` first. REPORT_WRITER_TOOLS: frozenset[str] = frozenset( { - "search_surfsense_docs", "read_file", "generate_report", } @@ -222,7 +220,6 @@ EXPLORE_SYSTEM_PROMPT = """You are the **explore** subagent for SurfSense. Conduct read-only research across the user's knowledge base, the web, and any documents the parent agent has surfaced. Return a synthesized answer with explicit citations — never speculate beyond the sources you have actually inspected. ## Tools available -- `search_surfsense_docs` — fast hybrid search over the user's knowledge base. - `web_search` — only when the user's KB clearly does not contain the answer. - `scrape_webpage` — to read a URL the user or the search results provided. - `read_file`, `ls`, `glob`, `grep` — to inspect specific documents or trees the parent has flagged. @@ -242,7 +239,7 @@ Produce a single high-quality report deliverable using `generate_report`. The pa ## Workflow 1. **Outline first.** Before calling `generate_report`, write a one-paragraph outline of the sections you plan to produce. Confirm the outline reflects the parent's instructions. -2. **Source resolution.** Decide whether to call `search_surfsense_docs` and `read_file` for any final-checks, or whether the parent's earlier tool calls already cover the source set. +2. **Source resolution.** Decide whether to call `read_file` for any final-checks, or whether the parent's earlier tool calls already cover the source set. 3. **One report.** Call `generate_report` exactly once with `source_strategy` chosen per the topic and chat history (see the `report-writing` skill). 4. **Confirm.** End with a one-sentence summary in your final message — never paste the report back into chat; the artifact card renders itself. """ diff --git a/surfsense_backend/app/agents/new_chat/tools/__init__.py b/surfsense_backend/app/agents/new_chat/tools/__init__.py index bc444b0c0..4b5ae3706 100644 --- a/surfsense_backend/app/agents/new_chat/tools/__init__.py +++ b/surfsense_backend/app/agents/new_chat/tools/__init__.py @@ -5,7 +5,6 @@ This module contains all the tools available to the SurfSense agent. To add a new tool, see the documentation in registry.py. Available tools: -- search_surfsense_docs: Search Surfsense documentation for usage help - generate_podcast: Generate audio podcasts from content - generate_video_presentation: Generate video presentations with slides and narration - generate_image: Generate images from text descriptions using AI models @@ -31,7 +30,6 @@ from .registry import ( get_tool_by_name, ) from .scrape_webpage import create_scrape_webpage_tool -from .search_surfsense_docs import create_search_surfsense_docs_tool from .update_memory import create_update_memory_tool, create_update_team_memory_tool from .video_presentation import create_generate_video_presentation_tool @@ -47,7 +45,6 @@ __all__ = [ "create_generate_podcast_tool", "create_generate_video_presentation_tool", "create_scrape_webpage_tool", - "create_search_surfsense_docs_tool", "create_update_memory_tool", "create_update_team_memory_tool", "format_documents_for_context", diff --git a/surfsense_backend/app/agents/new_chat/tools/registry.py b/surfsense_backend/app/agents/new_chat/tools/registry.py index 8c263ca20..6f011e372 100644 --- a/surfsense_backend/app/agents/new_chat/tools/registry.py +++ b/surfsense_backend/app/agents/new_chat/tools/registry.py @@ -101,7 +101,6 @@ from .podcast import create_generate_podcast_tool from .report import create_generate_report_tool from .resume import create_generate_resume_tool from .scrape_webpage import create_scrape_webpage_tool -from .search_surfsense_docs import create_search_surfsense_docs_tool from .teams import ( create_list_teams_channels_tool, create_read_teams_messages_tool, @@ -258,15 +257,6 @@ BUILTIN_TOOLS: list[ToolDefinition] = [ ), requires=[], ), - # Surfsense documentation search tool - ToolDefinition( - name="search_surfsense_docs", - description="Search Surfsense documentation for help with using the application", - factory=lambda deps: create_search_surfsense_docs_tool( - db_session=deps["db_session"], - ), - requires=["db_session"], - ), # ========================================================================= # SERVICE ACCOUNT DISCOVERY # Generic tool for the LLM to discover connected accounts and resolve diff --git a/surfsense_backend/app/agents/new_chat/tools/search_surfsense_docs.py b/surfsense_backend/app/agents/new_chat/tools/search_surfsense_docs.py deleted file mode 100644 index d8a0efac7..000000000 --- a/surfsense_backend/app/agents/new_chat/tools/search_surfsense_docs.py +++ /dev/null @@ -1,174 +0,0 @@ -""" -Surfsense documentation search tool. - -This tool allows the agent to search the pre-indexed Surfsense documentation -to help users with questions about how to use the application. - -The documentation is indexed at deployment time from MDX files and stored -in dedicated tables (surfsense_docs_documents, surfsense_docs_chunks). -""" - -import asyncio -import json - -from langchain_core.tools import tool -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession - -from app.db import SurfsenseDocsChunk, SurfsenseDocsDocument, async_session_maker -from app.utils.document_converters import embed_text -from app.utils.surfsense_docs import surfsense_docs_public_url - - -def format_surfsense_docs_results(results: list[tuple]) -> str: - """ - Format search results into XML structure for the LLM context. - - Uses the same XML structure as format_documents_for_context from knowledge_base.py - but with 'doc-' prefix on chunk IDs. This allows: - - LLM to use consistent [citation:doc-XXX] format - - Frontend to detect 'doc-' prefix and route to surfsense docs endpoint - - Args: - results: List of (chunk, document) tuples from the database query - - Returns: - Formatted XML string with documentation content and citation-ready chunks - """ - if not results: - return "No relevant Surfsense documentation found for your query." - - # Group chunks by document - grouped: dict[int, dict] = {} - for chunk, doc in results: - public_url = surfsense_docs_public_url(doc.source) - if doc.id not in grouped: - grouped[doc.id] = { - "document_id": f"doc-{doc.id}", - "document_type": "SURFSENSE_DOCS", - "title": doc.title, - "url": public_url, - "metadata": {"source": doc.source, "public_url": public_url}, - "chunks": [], - } - grouped[doc.id]["chunks"].append( - { - "chunk_id": f"doc-{chunk.id}", - "content": chunk.content, - } - ) - - # Render XML matching format_documents_for_context structure - parts: list[str] = [] - for g in grouped.values(): - metadata_json = json.dumps(g["metadata"], ensure_ascii=False) - - parts.append("") - parts.append("") - parts.append(f" {g['document_id']}") - parts.append(f" {g['document_type']}") - parts.append(f" <![CDATA[{g['title']}]]>") - parts.append(f" ") - parts.append(f" ") - parts.append("") - parts.append("") - parts.append("") - - for ch in g["chunks"]: - parts.append( - f" " - ) - - parts.append("") - parts.append("") - parts.append("") - - return "\n".join(parts).strip() - - -async def search_surfsense_docs_async( - query: str, - db_session: AsyncSession, - top_k: int = 10, -) -> str: - """ - Search Surfsense documentation using vector similarity. - - Args: - query: The search query about Surfsense usage - db_session: Database session for executing queries - top_k: Number of results to return - - Returns: - Formatted string with relevant documentation content - """ - # Get embedding for the query - query_embedding = await asyncio.to_thread(embed_text, query) - - # Vector similarity search on chunks, joining with documents - stmt = ( - select(SurfsenseDocsChunk, SurfsenseDocsDocument) - .join( - SurfsenseDocsDocument, - SurfsenseDocsChunk.document_id == SurfsenseDocsDocument.id, - ) - .order_by(SurfsenseDocsChunk.embedding.op("<=>")(query_embedding)) - .limit(top_k) - ) - - result = await db_session.execute(stmt) - rows = result.all() - - return format_surfsense_docs_results(rows) - - -def create_search_surfsense_docs_tool(db_session: AsyncSession): - """ - Factory function to create the search_surfsense_docs tool. - - The tool acquires its own short-lived ``AsyncSession`` per call via - :data:`async_session_maker` so the closure is safe to share across - HTTP requests by the compiled-agent cache. Capturing a per-request - session here would surface stale/closed sessions on cache hits. - - Args: - db_session: Reserved for registry compatibility. Per-call sessions - are opened via :data:`async_session_maker` inside the tool body. - - Returns: - A configured tool function for searching Surfsense documentation - """ - del db_session # per-call session — see docstring - - @tool - async def search_surfsense_docs(query: str, top_k: int = 10) -> str: - """ - Search Surfsense documentation for help with using the application. - - Use this tool when the user asks questions about: - - How to use Surfsense features - - Installation and setup instructions - - Configuration options and settings - - Troubleshooting common issues - - Available connectors and integrations - - Browser extension usage - - API documentation - - This searches the official Surfsense documentation that was indexed - at deployment time. It does NOT search the user's personal knowledge base. - - Args: - query: The search query about Surfsense usage or features - top_k: Number of documentation chunks to retrieve (default: 10) - - Returns: - Relevant documentation content formatted with chunk IDs for citations - """ - async with async_session_maker() as db_session: - return await search_surfsense_docs_async( - query=query, - db_session=db_session, - top_k=top_k, - ) - - return search_surfsense_docs diff --git a/surfsense_backend/app/app.py b/surfsense_backend/app/app.py index 43b0af7d2..223eb5a1b 100644 --- a/surfsense_backend/app/app.py +++ b/surfsense_backend/app/app.py @@ -43,7 +43,6 @@ from app.rate_limiter import get_real_client_ip, limiter from app.routes import router as crud_router from app.routes.auth_routes import router as auth_router from app.schemas import UserCreate, UserRead, UserUpdate -from app.tasks.surfsense_docs_indexer import seed_surfsense_docs from app.users import SECRET, auth_backend, current_active_user, fastapi_users from app.utils.perf import log_system_snapshot @@ -576,13 +575,6 @@ async def lifespan(app: FastAPI): initialize_llm_router() initialize_image_gen_router() initialize_vision_llm_router() - try: - await asyncio.wait_for(seed_surfsense_docs(), timeout=120) - except TimeoutError: - logging.getLogger(__name__).warning( - "Surfsense docs seeding timed out after 120s — skipping. " - "Docs will be indexed on the next restart." - ) # Phase 1.7 — JIT warmup. Bounded so a stuck warmup never delays # worker readiness. ``shield`` so Uvicorn cancelling startup diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py index fe2e53268..d6ee9ff88 100644 --- a/surfsense_backend/app/db.py +++ b/surfsense_backend/app/db.py @@ -1150,46 +1150,6 @@ class Chunk(BaseModel, TimestampMixin): document = relationship("Document", back_populates="chunks") -class SurfsenseDocsDocument(BaseModel, TimestampMixin): - """ - Surfsense documentation storage. - Indexed at migration time from MDX files. - """ - - __tablename__ = "surfsense_docs_documents" - - source = Column( - String, nullable=False, unique=True, index=True - ) # File path: "connectors/slack.mdx" - title = Column(String, nullable=False) - content = Column(Text, nullable=False) - content_hash = Column(String, nullable=False, index=True) # For detecting changes - embedding = Column(Vector(config.embedding_model_instance.dimension)) - updated_at = Column(TIMESTAMP(timezone=True), nullable=True, index=True) - - chunks = relationship( - "SurfsenseDocsChunk", - back_populates="document", - cascade="all, delete-orphan", - ) - - -class SurfsenseDocsChunk(BaseModel, TimestampMixin): - """Chunk storage for Surfsense documentation.""" - - __tablename__ = "surfsense_docs_chunks" - - content = Column(Text, nullable=False) - embedding = Column(Vector(config.embedding_model_instance.dimension)) - - document_id = Column( - Integer, - ForeignKey("surfsense_docs_documents.id", ondelete="CASCADE"), - nullable=False, - ) - document = relationship("SurfsenseDocsDocument", back_populates="chunks") - - class Podcast(BaseModel, TimestampMixin): """Podcast model for storing generated podcasts.""" @@ -2680,11 +2640,6 @@ async def setup_indexes(): "CREATE INDEX IF NOT EXISTS idx_documents_search_space_updated ON documents (search_space_id, updated_at DESC NULLS LAST) INCLUDE (id, title, document_type)" ) ) - await conn.execute( - text( - "CREATE INDEX IF NOT EXISTS idx_surfsense_docs_title_trgm ON surfsense_docs_documents USING gin (title gin_trgm_ops)" - ) - ) async def create_db_and_tables(): diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index 48a095456..8373f13c3 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -55,7 +55,6 @@ from .search_source_connectors_routes import router as search_source_connectors_ from .search_spaces_routes import router as search_spaces_router from .slack_add_connector_route import router as slack_add_connector_router from .stripe_routes import router as stripe_router -from .surfsense_docs_routes import router as surfsense_docs_router from .team_memory_routes import router as team_memory_router from .teams_add_connector_route import router as teams_add_connector_router from .video_presentations_routes import router as video_presentations_router @@ -108,7 +107,6 @@ router.include_router(new_llm_config_router) # LLM configs with prompt configur router.include_router(model_list_router) # Dynamic model catalogue from OpenRouter router.include_router(logs_router) router.include_router(circleback_webhook_router) # Circleback meeting webhooks -router.include_router(surfsense_docs_router) # Surfsense documentation for citations router.include_router(notifications_router) # Notifications with Zero sync router.include_router( mcp_oauth_router diff --git a/surfsense_backend/app/routes/new_chat_routes.py b/surfsense_backend/app/routes/new_chat_routes.py index fb4d5a049..63b7732a9 100644 --- a/surfsense_backend/app/routes/new_chat_routes.py +++ b/surfsense_backend/app/routes/new_chat_routes.py @@ -1785,7 +1785,6 @@ async def handle_new_chat( user_id=str(user.id), llm_config_id=llm_config_id, mentioned_document_ids=request.mentioned_document_ids, - mentioned_surfsense_doc_ids=request.mentioned_surfsense_doc_ids, mentioned_folder_ids=request.mentioned_folder_ids, mentioned_connector_ids=request.mentioned_connector_ids, mentioned_connectors=mentioned_connectors_payload, @@ -2278,7 +2277,6 @@ async def regenerate_response( user_id=str(user.id), llm_config_id=llm_config_id, mentioned_document_ids=request.mentioned_document_ids, - mentioned_surfsense_doc_ids=request.mentioned_surfsense_doc_ids, mentioned_folder_ids=request.mentioned_folder_ids, mentioned_connector_ids=request.mentioned_connector_ids, mentioned_connectors=mentioned_connectors_payload, diff --git a/surfsense_backend/app/routes/surfsense_docs_routes.py b/surfsense_backend/app/routes/surfsense_docs_routes.py deleted file mode 100644 index 0d5428dec..000000000 --- a/surfsense_backend/app/routes/surfsense_docs_routes.py +++ /dev/null @@ -1,172 +0,0 @@ -""" -Routes for Surfsense documentation. - -These endpoints support the citation system for Surfsense docs, -allowing the frontend to fetch document details when a user clicks -on a [citation:doc-XXX] link. -""" - -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy import func, select -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import selectinload - -from app.db import ( - SurfsenseDocsChunk, - SurfsenseDocsDocument, - User, - get_async_session, -) -from app.schemas import PaginatedResponse -from app.schemas.surfsense_docs import ( - SurfsenseDocsChunkRead, - SurfsenseDocsDocumentRead, - SurfsenseDocsDocumentWithChunksRead, -) -from app.users import current_active_user -from app.utils.surfsense_docs import surfsense_docs_public_url - -router = APIRouter() - - -@router.get( - "/surfsense-docs/by-chunk/{chunk_id}", - response_model=SurfsenseDocsDocumentWithChunksRead, -) -async def get_surfsense_doc_by_chunk_id( - chunk_id: int, - session: AsyncSession = Depends(get_async_session), - user: User = Depends(current_active_user), -): - """ - Retrieves a Surfsense documentation document based on a chunk ID. - - This endpoint is used by the frontend to resolve [citation:doc-XXX] links. - """ - try: - # Get the chunk - chunk_result = await session.execute( - select(SurfsenseDocsChunk).filter(SurfsenseDocsChunk.id == chunk_id) - ) - chunk = chunk_result.scalars().first() - - if not chunk: - raise HTTPException( - status_code=404, - detail=f"Surfsense docs chunk with id {chunk_id} not found", - ) - - # Get the associated document with all its chunks - document_result = await session.execute( - select(SurfsenseDocsDocument) - .options(selectinload(SurfsenseDocsDocument.chunks)) - .filter(SurfsenseDocsDocument.id == chunk.document_id) - ) - document = document_result.scalars().first() - - if not document: - raise HTTPException( - status_code=404, - detail="Surfsense docs document not found", - ) - - # Sort chunks by ID - sorted_chunks = sorted(document.chunks, key=lambda x: x.id) - - return SurfsenseDocsDocumentWithChunksRead( - id=document.id, - title=document.title, - source=document.source, - public_url=surfsense_docs_public_url(document.source), - content=document.content, - chunks=[ - SurfsenseDocsChunkRead(id=c.id, content=c.content) - for c in sorted_chunks - ], - ) - except HTTPException: - raise - except Exception as e: - raise HTTPException( - status_code=500, - detail=f"Failed to retrieve Surfsense documentation: {e!s}", - ) from e - - -@router.get( - "/surfsense-docs", - response_model=PaginatedResponse[SurfsenseDocsDocumentRead], -) -async def list_surfsense_docs( - page: int = 0, - page_size: int = 50, - title: str | None = None, - session: AsyncSession = Depends(get_async_session), - user: User = Depends(current_active_user), -): - """ - List all Surfsense documentation documents. - - Args: - page: Zero-based page index. - page_size: Number of items per page (default: 50). - title: Optional title filter (case-insensitive substring match). - session: Database session (injected). - user: Current authenticated user (injected). - - Returns: - PaginatedResponse[SurfsenseDocsDocumentRead]: Paginated list of Surfsense docs. - """ - try: - # Base query - query = select(SurfsenseDocsDocument) - count_query = select(func.count()).select_from(SurfsenseDocsDocument) - - # Filter by title if provided - if title and title.strip(): - query = query.filter(SurfsenseDocsDocument.title.ilike(f"%{title}%")) - count_query = count_query.filter( - SurfsenseDocsDocument.title.ilike(f"%{title}%") - ) - - # Get total count - total_result = await session.execute(count_query) - total = total_result.scalar() or 0 - - # Calculate offset - offset = page * page_size - - # Get paginated results - result = await session.execute( - query.order_by(SurfsenseDocsDocument.title).offset(offset).limit(page_size) - ) - docs = result.scalars().all() - - # Convert to response format - items = [ - SurfsenseDocsDocumentRead( - id=doc.id, - title=doc.title, - source=doc.source, - public_url=surfsense_docs_public_url(doc.source), - content=doc.content, - created_at=doc.created_at, - updated_at=doc.updated_at, - ) - for doc in docs - ] - - has_more = (offset + len(items)) < total - - return PaginatedResponse( - items=items, - total=total, - page=page, - page_size=page_size, - has_more=has_more, - ) - except Exception as e: - raise HTTPException( - status_code=500, - detail=f"Failed to list Surfsense documentation: {e!s}", - ) from e diff --git a/surfsense_backend/app/schemas/new_chat.py b/surfsense_backend/app/schemas/new_chat.py index 8b49413c6..ab95f9b6b 100644 --- a/surfsense_backend/app/schemas/new_chat.py +++ b/surfsense_backend/app/schemas/new_chat.py @@ -239,9 +239,6 @@ class NewChatRequest(BaseModel): mentioned_document_ids: list[int] | None = ( None # Optional document IDs mentioned with @ in the chat ) - mentioned_surfsense_doc_ids: list[int] | None = ( - None # Optional SurfSense documentation IDs mentioned with @ in the chat - ) mentioned_folder_ids: list[int] | None = Field( default=None, description=( @@ -326,7 +323,6 @@ class RegenerateRequest(BaseModel): None # New user query (for edit). None = reload with same query ) mentioned_document_ids: list[int] | None = None - mentioned_surfsense_doc_ids: list[int] | None = None mentioned_folder_ids: list[int] | None = Field( default=None, description=( diff --git a/surfsense_backend/app/schemas/surfsense_docs.py b/surfsense_backend/app/schemas/surfsense_docs.py deleted file mode 100644 index 3adf25032..000000000 --- a/surfsense_backend/app/schemas/surfsense_docs.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Schemas for Surfsense documentation. -""" - -from datetime import datetime - -from pydantic import BaseModel, ConfigDict - - -class SurfsenseDocsChunkRead(BaseModel): - """Schema for a Surfsense docs chunk.""" - - id: int - content: str - - model_config = ConfigDict(from_attributes=True) - - -class SurfsenseDocsDocumentRead(BaseModel): - """Schema for a Surfsense docs document (without chunks).""" - - id: int - title: str - source: str - public_url: str - content: str - created_at: datetime | None = None - updated_at: datetime | None = None - - model_config = ConfigDict(from_attributes=True) - - -class SurfsenseDocsDocumentWithChunksRead(BaseModel): - """Schema for a Surfsense docs document with its chunks.""" - - id: int - title: str - source: str - public_url: str - content: str - chunks: list[SurfsenseDocsChunkRead] - - model_config = ConfigDict(from_attributes=True) diff --git a/surfsense_backend/app/tasks/chat/stream_new_chat.py b/surfsense_backend/app/tasks/chat/stream_new_chat.py index 78f80c955..e150cf494 100644 --- a/surfsense_backend/app/tasks/chat/stream_new_chat.py +++ b/surfsense_backend/app/tasks/chat/stream_new_chat.py @@ -25,7 +25,6 @@ from uuid import UUID import anyio from langchain_core.messages import HumanMessage from sqlalchemy.future import select -from sqlalchemy.orm import selectinload from app.agents.multi_agent_chat import create_multi_agent_chat_deep_agent from app.agents.new_chat.chat_deepagent import create_surfsense_deep_agent @@ -55,7 +54,6 @@ from app.db import ( NewChatThread, Report, SearchSourceConnectorType, - SurfsenseDocsDocument, async_session_maker, shielded_async_session, ) @@ -77,7 +75,6 @@ from app.tasks.chat.streaming.helpers.interrupt_inspector import ( ) from app.utils.content_utils import bootstrap_history_from_db from app.utils.perf import get_perf_logger, log_system_snapshot, trim_native_heap -from app.utils.surfsense_docs import surfsense_docs_public_url from app.utils.user_message_multimodal import build_human_message_content _background_tasks: set[asyncio.Task] = set() @@ -198,58 +195,6 @@ def _extract_chunk_parts(chunk: Any) -> dict[str, Any]: return out -def format_mentioned_surfsense_docs_as_context( - documents: list[SurfsenseDocsDocument], -) -> str: - """Format mentioned SurfSense documentation as context for the agent.""" - if not documents: - return "" - - context_parts = [""] - context_parts.append( - "The user has explicitly mentioned the following SurfSense documentation pages. " - "These are official documentation about how to use SurfSense and should be used to answer questions about the application. " - "Use [citation:CHUNK_ID] format for citations (e.g., [citation:doc-123])." - ) - - for doc in documents: - public_url = surfsense_docs_public_url(doc.source) - metadata_json = json.dumps( - {"source": doc.source, "public_url": public_url}, ensure_ascii=False - ) - - context_parts.append("") - context_parts.append("") - context_parts.append(f" doc-{doc.id}") - context_parts.append(" SURFSENSE_DOCS") - context_parts.append(f" <![CDATA[{doc.title}]]>") - context_parts.append(f" ") - context_parts.append( - f" " - ) - context_parts.append("") - context_parts.append("") - context_parts.append("") - - if hasattr(doc, "chunks") and doc.chunks: - for chunk in doc.chunks: - context_parts.append( - f" " - ) - else: - context_parts.append( - f" " - ) - - context_parts.append("") - context_parts.append("") - context_parts.append("") - - context_parts.append("") - - return "\n".join(context_parts) - - def extract_todos_from_deepagents(command_output) -> dict: """ Extract todos from deepagents' TodoListMiddleware Command output. @@ -837,7 +782,6 @@ async def stream_new_chat( user_id: str | None = None, llm_config_id: int = -1, mentioned_document_ids: list[int] | None = None, - mentioned_surfsense_doc_ids: list[int] | None = None, mentioned_folder_ids: list[int] | None = None, mentioned_connector_ids: list[int] | None = None, mentioned_connectors: list[dict[str, Any]] | None = None, @@ -869,7 +813,6 @@ async def stream_new_chat( llm_config_id: The LLM configuration ID (default: -1 for first global config) needs_history_bootstrap: If True, load message history from DB (for cloned chats) mentioned_document_ids: Optional list of document IDs mentioned with @ in the chat - mentioned_surfsense_doc_ids: Optional list of SurfSense doc IDs mentioned with @ in the chat mentioned_folder_ids: Optional list of knowledge-base folder IDs mentioned with @ (cloud mode) checkpoint_id: Optional checkpoint ID to rewind/fork from (for edit/reload operations) @@ -1295,19 +1238,7 @@ async def stream_new_chat( # Mentioned KB documents are now handled by KnowledgeBaseSearchMiddleware # which merges them into the scoped filesystem with full document - # structure. Only SurfSense docs and report context are inlined here. - - # Fetch mentioned SurfSense docs if any - mentioned_surfsense_docs: list[SurfsenseDocsDocument] = [] - if mentioned_surfsense_doc_ids: - result = await session.execute( - select(SurfsenseDocsDocument) - .options(selectinload(SurfsenseDocsDocument.chunks)) - .filter( - SurfsenseDocsDocument.id.in_(mentioned_surfsense_doc_ids), - ) - ) - mentioned_surfsense_docs = list(result.scalars().all()) + # structure. Only report context is inlined here. # Fetch the most recent report(s) in this thread so the LLM can # easily find report_id for versioning decisions, instead of @@ -1341,10 +1272,7 @@ async def stream_new_chat( agent_user_query = user_query accepted_folder_ids: list[int] = [] if fs_mode == FilesystemMode.CLOUD.value and ( - mentioned_document_ids - or mentioned_surfsense_doc_ids - or mentioned_folder_ids - or mentioned_documents + mentioned_document_ids or mentioned_folder_ids or mentioned_documents ): from app.schemas.new_chat import ( MentionedDocumentInfo as _MentionedDocumentInfo, @@ -1370,23 +1298,17 @@ async def stream_new_chat( search_space_id=search_space_id, mentioned_documents=chip_objs, mentioned_document_ids=mentioned_document_ids, - mentioned_surfsense_doc_ids=mentioned_surfsense_doc_ids, mentioned_folder_ids=mentioned_folder_ids, ) agent_user_query = substitute_in_text(user_query, resolved.token_to_path) accepted_folder_ids = resolved.mentioned_folder_ids - # Format the user query with context (SurfSense docs + reports only). + # Format the user query with context (reports only). # Uses ``agent_user_query`` so the LLM sees backtick-wrapped paths # instead of bare ``@title`` tokens. final_query = agent_user_query context_parts = [] - if mentioned_surfsense_docs: - context_parts.append( - format_mentioned_surfsense_docs_as_context(mentioned_surfsense_docs) - ) - if mentioned_connectors: connector_lines = [] for connector in mentioned_connectors: @@ -1617,12 +1539,8 @@ async def stream_new_chat( stream_result.content_builder = AssistantContentBuilder() # Initial thinking step - analyzing the request - if mentioned_surfsense_docs: - initial_title = "Analyzing referenced content" - action_verb = "Analyzing" - else: - initial_title = "Understanding your request" - action_verb = "Processing" + initial_title = "Understanding your request" + action_verb = "Processing" processing_parts = [] if user_query.strip(): @@ -1633,18 +1551,6 @@ async def stream_new_chat( else: processing_parts.append("(message)") - if mentioned_surfsense_docs: - doc_names = [] - for doc in mentioned_surfsense_docs: - title = doc.title - if len(title) > 30: - title = title[:27] + "..." - doc_names.append(title) - if len(doc_names) == 1: - processing_parts.append(f"[{doc_names[0]}]") - else: - processing_parts.append(f"[{len(doc_names)} docs]") - initial_items = [f"{action_verb}: {' '.join(processing_parts)}"] initial_step_id = "thinking-1" @@ -1664,10 +1570,10 @@ async def stream_new_chat( items=initial_items, ) - # These ORM objects (with eagerly-loaded chunks) can be very large. - # They're only needed to build context strings already copied into - # final_query / langchain_messages — release them before streaming. - del mentioned_surfsense_docs, recent_reports + # These ORM objects can be large. They're only needed to build context + # strings already copied into final_query / langchain_messages — + # release them before streaming. + del recent_reports del langchain_messages, final_query # Check if this is the first assistant response so we can generate diff --git a/surfsense_backend/app/tasks/chat/streaming/context/__init__.py b/surfsense_backend/app/tasks/chat/streaming/context/__init__.py index f858a6c06..4cf58d76f 100644 --- a/surfsense_backend/app/tasks/chat/streaming/context/__init__.py +++ b/surfsense_backend/app/tasks/chat/streaming/context/__init__.py @@ -1,15 +1,11 @@ -"""Pre-agent context shaping: mentioned-doc rendering and todos extraction.""" +"""Pre-agent context shaping: todos extraction.""" from __future__ import annotations from app.tasks.chat.streaming.context.deepagents_todos import ( extract_todos_from_deepagents, ) -from app.tasks.chat.streaming.context.mentioned_docs import ( - format_mentioned_surfsense_docs_as_context, -) __all__ = [ "extract_todos_from_deepagents", - "format_mentioned_surfsense_docs_as_context", ] diff --git a/surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py b/surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py deleted file mode 100644 index e02e98d34..000000000 --- a/surfsense_backend/app/tasks/chat/streaming/context/mentioned_docs.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Render user-mentioned SurfSense docs as XML context for the agent.""" - -from __future__ import annotations - -import json - -from app.db import SurfsenseDocsDocument -from app.utils.surfsense_docs import surfsense_docs_public_url - - -def format_mentioned_surfsense_docs_as_context( - documents: list[SurfsenseDocsDocument], -) -> str: - if not documents: - return "" - - context_parts = [""] - context_parts.append( - "The user has explicitly mentioned the following SurfSense documentation pages. " - "These are official documentation about how to use SurfSense and should be used to answer questions about the application. " - "Use [citation:CHUNK_ID] format for citations (e.g., [citation:doc-123])." - ) - - for doc in documents: - public_url = surfsense_docs_public_url(doc.source) - metadata_json = json.dumps( - {"source": doc.source, "public_url": public_url}, ensure_ascii=False - ) - - context_parts.append("") - context_parts.append("") - context_parts.append(f" doc-{doc.id}") - context_parts.append(" SURFSENSE_DOCS") - context_parts.append(f" <![CDATA[{doc.title}]]>") - context_parts.append(f" ") - context_parts.append( - f" " - ) - context_parts.append("") - context_parts.append("") - context_parts.append("") - - if hasattr(doc, "chunks") and doc.chunks: - for chunk in doc.chunks: - context_parts.append( - f" " - ) - else: - context_parts.append( - f" " - ) - - context_parts.append("") - context_parts.append("") - context_parts.append("") - - context_parts.append("") - return "\n".join(context_parts) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py index c860e517e..e727200eb 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/initial_thinking_step.py @@ -1,8 +1,8 @@ """Build and emit the first ``thinking-1`` step for a new-chat turn. The step title and "Processing X" items are derived from what the user sent -(text snippet, image count, mentioned doc titles) so the FE can render a -meaningful placeholder while the agent stream warms up. +(text snippet, image count) so the FE can render a meaningful placeholder +while the agent stream warms up. ``thinking-1`` is the canonical id for this step — every subsequent ``thinking-N`` produced by ``stream_agent_events`` folds into the same @@ -15,7 +15,6 @@ from collections.abc import Iterator from dataclasses import dataclass from typing import Any -from app.db import SurfsenseDocsDocument from app.services.new_streaming_service import VercelStreamingService @@ -37,14 +36,9 @@ def build_initial_thinking_step( *, user_query: str, user_image_data_urls: list[str] | None, - mentioned_surfsense_docs: list[SurfsenseDocsDocument], ) -> InitialThinkingStep: - if mentioned_surfsense_docs: - title = "Analyzing referenced content" - action_verb = "Analyzing" - else: - title = "Understanding your request" - action_verb = "Processing" + title = "Understanding your request" + action_verb = "Processing" processing_parts: list[str] = [] if user_query.strip(): @@ -55,18 +49,6 @@ def build_initial_thinking_step( else: processing_parts.append("(message)") - if mentioned_surfsense_docs: - doc_names: list[str] = [] - for doc in mentioned_surfsense_docs: - t = doc.title - if len(t) > 30: - t = t[:27] + "..." - doc_names.append(t) - if len(doc_names) == 1: - processing_parts.append(f"[{doc_names[0]}]") - else: - processing_parts.append(f"[{len(doc_names)} docs]") - items = [f"{action_verb}: {' '.join(processing_parts)}"] return InitialThinkingStep(step_id="thinking-1", title=title, items=items) diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py index f508571b0..0c6704bd1 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/input_state.py @@ -5,20 +5,17 @@ Pipeline: 1. **History bootstrap** — only for cloned chats with no LangGraph checkpoint yet; flips the per-thread ``needs_history_bootstrap`` flag back to False once the rows are loaded. - 2. **Mentioned SurfSense docs** — eager-load chunks so the formatter has the - full content without a second roundtrip. - 3. **Recent reports** — top 3 by id desc with non-null content, so the LLM + 2. **Recent reports** — top 3 by id desc with non-null content, so the LLM can resolve ``report_id`` for versioning without spelunking history. - 4. **@-mention resolve** (cloud mode) — substitute ``@title`` tokens in the + 3. **@-mention resolve** (cloud mode) — substitute ``@title`` tokens in the query with canonical ``\`/documents/...\``` paths the LLM expects. - 5. **Context block render** — XML-wrap surfsense docs + reports, prepend to - the rewritten query, optionally prefix with display name for SEARCH_SPACE + 4. **Context block render** — XML-wrap recent reports, prepend to the + rewritten query, optionally prefix with display name for SEARCH_SPACE visibility. - 6. **HumanMessage** — multimodal content if images are attached. + 5. **HumanMessage** — multimodal content if images are attached. Returns the assembled ``input_state`` dict plus side-channel data the -orchestrator needs downstream (``accepted_folder_ids`` for runtime context; -``mentioned_surfsense_docs`` for the initial thinking step). +orchestrator needs downstream (``accepted_folder_ids`` for runtime context). """ from __future__ import annotations @@ -30,7 +27,6 @@ from typing import Any from langchain_core.messages import HumanMessage from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select -from sqlalchemy.orm import selectinload from app.agents.new_chat.filesystem_selection import FilesystemMode from app.agents.new_chat.mention_resolver import resolve_mentions, substitute_in_text @@ -38,10 +34,6 @@ from app.db import ( ChatVisibility, NewChatThread, Report, - SurfsenseDocsDocument, -) -from app.tasks.chat.streaming.context.mentioned_docs import ( - format_mentioned_surfsense_docs_as_context, ) from app.utils.content_utils import bootstrap_history_from_db from app.utils.user_message_multimodal import build_human_message_content @@ -55,13 +47,10 @@ class NewChatInputState: ``input_state`` is fed straight to the agent. ``accepted_folder_ids`` feeds the runtime context (the resolver may have dropped some chips). - ``mentioned_surfsense_docs`` is consumed by the initial thinking-step - builder for the FE placeholder before the agent stream starts. """ input_state: dict[str, Any] accepted_folder_ids: list[int] - mentioned_surfsense_docs: list[SurfsenseDocsDocument] async def build_new_chat_input_state( @@ -72,7 +61,6 @@ async def build_new_chat_input_state( user_query: str, user_image_data_urls: list[str] | None, mentioned_document_ids: list[int] | None, - mentioned_surfsense_doc_ids: list[int] | None, mentioned_folder_ids: list[int] | None, mentioned_documents: list[dict[str, Any]] | None, needs_history_bootstrap: bool, @@ -96,15 +84,6 @@ async def build_new_chat_input_state( thread.needs_history_bootstrap = False await session.commit() - mentioned_surfsense_docs: list[SurfsenseDocsDocument] = [] - if mentioned_surfsense_doc_ids: - result = await session.execute( - select(SurfsenseDocsDocument) - .options(selectinload(SurfsenseDocsDocument.chunks)) - .filter(SurfsenseDocsDocument.id.in_(mentioned_surfsense_doc_ids)) - ) - mentioned_surfsense_docs = list(result.scalars().all()) - # Top 3 reports keyed by id desc (newest first) with content present, # surfaced inline so the LLM resolves ``report_id`` for versioning without # digging through conversation history. @@ -125,14 +104,12 @@ async def build_new_chat_input_state( user_query=user_query, filesystem_mode=filesystem_mode, mentioned_document_ids=mentioned_document_ids, - mentioned_surfsense_doc_ids=mentioned_surfsense_doc_ids, mentioned_folder_ids=mentioned_folder_ids, mentioned_documents=mentioned_documents, ) final_query = _render_query_with_context( agent_user_query=agent_user_query, - mentioned_surfsense_docs=mentioned_surfsense_docs, recent_reports=recent_reports, ) @@ -154,7 +131,6 @@ async def build_new_chat_input_state( return NewChatInputState( input_state=input_state, accepted_folder_ids=accepted_folder_ids, - mentioned_surfsense_docs=mentioned_surfsense_docs, ) @@ -165,7 +141,6 @@ async def _resolve_mentions_for_query( user_query: str, filesystem_mode: str, mentioned_document_ids: list[int] | None, - mentioned_surfsense_doc_ids: list[int] | None, mentioned_folder_ids: list[int] | None, mentioned_documents: list[dict[str, Any]] | None, ) -> tuple[str, list[int]]: @@ -187,10 +162,7 @@ async def _resolve_mentions_for_query( accepted_folder_ids: list[int] = [] has_any_mention = bool( - mentioned_document_ids - or mentioned_surfsense_doc_ids - or mentioned_folder_ids - or mentioned_documents + mentioned_document_ids or mentioned_folder_ids or mentioned_documents ) if filesystem_mode != FilesystemMode.CLOUD.value or not has_any_mention: return agent_user_query, accepted_folder_ids @@ -214,7 +186,6 @@ async def _resolve_mentions_for_query( search_space_id=search_space_id, mentioned_documents=chip_objs, mentioned_document_ids=mentioned_document_ids, - mentioned_surfsense_doc_ids=mentioned_surfsense_doc_ids, mentioned_folder_ids=mentioned_folder_ids, ) agent_user_query = substitute_in_text(user_query, resolved.token_to_path) @@ -225,17 +196,11 @@ async def _resolve_mentions_for_query( def _render_query_with_context( *, agent_user_query: str, - mentioned_surfsense_docs: list[SurfsenseDocsDocument], recent_reports: list[Report], ) -> str: - """Prepend surfsense-docs + recent-reports XML blocks to the user query.""" + """Prepend recent-reports XML block to the user query.""" context_parts: list[str] = [] - if mentioned_surfsense_docs: - context_parts.append( - format_mentioned_surfsense_docs_as_context(mentioned_surfsense_docs) - ) - if recent_reports: report_lines: list[str] = [] for r in recent_reports: diff --git a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py index 6d0853502..1892320d3 100644 --- a/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py +++ b/surfsense_backend/app/tasks/chat/streaming/flows/new_chat/orchestrator.py @@ -123,7 +123,6 @@ async def stream_new_chat( user_id: str | None = None, llm_config_id: int = -1, mentioned_document_ids: list[int] | None = None, - mentioned_surfsense_doc_ids: list[int] | None = None, mentioned_folder_ids: list[int] | None = None, mentioned_documents: list[dict[str, Any]] | None = None, checkpoint_id: str | None = None, @@ -435,7 +434,6 @@ async def stream_new_chat( user_query=user_query, user_image_data_urls=user_image_data_urls, mentioned_document_ids=mentioned_document_ids, - mentioned_surfsense_doc_ids=mentioned_surfsense_doc_ids, mentioned_folder_ids=mentioned_folder_ids, mentioned_documents=mentioned_documents, needs_history_bootstrap=needs_history_bootstrap, @@ -447,7 +445,6 @@ async def stream_new_chat( ) input_state = assembled.input_state accepted_folder_ids = assembled.accepted_folder_ids - mentioned_surfsense_docs = assembled.mentioned_surfsense_docs _perf_log.info( "[stream_new_chat] History bootstrap + doc/report queries in %.3fs", time.perf_counter() - _t0, @@ -560,7 +557,6 @@ async def stream_new_chat( initial_step = build_initial_thinking_step( user_query=user_query, user_image_data_urls=user_image_data_urls, - mentioned_surfsense_docs=mentioned_surfsense_docs, ) for sse in iter_initial_thinking_step_frame( initial_step, @@ -575,7 +571,7 @@ async def stream_new_chat( # Drop the heavy ORM objects + the container that holds them so they # aren't retained for the entire streaming duration. ``input_state`` # already carries the langchain_messages list independently. - del assembled, mentioned_surfsense_docs + del assembled title_task = spawn_title_task( chat_id=chat_id, diff --git a/surfsense_backend/app/tasks/surfsense_docs_indexer.py b/surfsense_backend/app/tasks/surfsense_docs_indexer.py deleted file mode 100644 index db88c8700..000000000 --- a/surfsense_backend/app/tasks/surfsense_docs_indexer.py +++ /dev/null @@ -1,249 +0,0 @@ -""" -Surfsense documentation indexer. -Indexes MDX documentation files at startup. -""" - -import hashlib -import logging -import re -from datetime import UTC, datetime -from pathlib import Path - -from sqlalchemy import delete as sa_delete, select -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import selectinload -from sqlalchemy.orm.attributes import set_committed_value - -from app.config import config -from app.db import SurfsenseDocsChunk, SurfsenseDocsDocument, async_session_maker -from app.utils.document_converters import embed_text - -logger = logging.getLogger(__name__) - - -async def _safe_set_docs_chunks( - session: AsyncSession, document: SurfsenseDocsDocument, chunks: list -) -> None: - """safe_set_chunks variant for the SurfsenseDocsDocument/Chunk models.""" - if document.id is not None: - await session.execute( - sa_delete(SurfsenseDocsChunk).where( - SurfsenseDocsChunk.document_id == document.id - ) - ) - for chunk in chunks: - chunk.document_id = document.id - - set_committed_value(document, "chunks", chunks) - session.add_all(chunks) - - -# Path to docs relative to project root -DOCS_DIR = ( - Path(__file__).resolve().parent.parent.parent.parent - / "surfsense_web" - / "content" - / "docs" -) - - -def parse_mdx_frontmatter(content: str) -> tuple[str, str]: - """ - Parse MDX file to extract frontmatter title and content. - - Args: - content: Raw MDX file content - - Returns: - Tuple of (title, content_without_frontmatter) - """ - # Match frontmatter between --- markers - frontmatter_pattern = r"^---\s*\n(.*?)\n---\s*\n" - match = re.match(frontmatter_pattern, content, re.DOTALL) - - if match: - frontmatter = match.group(1) - content_without_frontmatter = content[match.end() :] - - # Extract title from frontmatter - title_match = re.search(r"^title:\s*(.+)$", frontmatter, re.MULTILINE) - title = title_match.group(1).strip() if title_match else "Untitled" - - # Remove quotes if present - title = title.strip("\"'") - - return title, content_without_frontmatter.strip() - - return "Untitled", content.strip() - - -def get_all_mdx_files() -> list[Path]: - """ - Get all MDX files from the docs directory. - - Returns: - List of Path objects for each MDX file - """ - if not DOCS_DIR.exists(): - logger.warning(f"Docs directory not found: {DOCS_DIR}") - return [] - - return list(DOCS_DIR.rglob("*.mdx")) - - -def generate_surfsense_docs_content_hash(content: str) -> str: - """Generate SHA-256 hash for Surfsense docs content.""" - return hashlib.sha256(content.encode("utf-8")).hexdigest() - - -def create_surfsense_docs_chunks(content: str) -> list[SurfsenseDocsChunk]: - """ - Create chunks from Surfsense documentation content. - - Args: - content: Document content to chunk - - Returns: - List of SurfsenseDocsChunk objects with embeddings - """ - return [ - SurfsenseDocsChunk( - content=chunk.text, - embedding=embed_text(chunk.text), - ) - for chunk in config.chunker_instance.chunk(content) - ] - - -async def index_surfsense_docs(session: AsyncSession) -> tuple[int, int, int, int]: - """ - Index all Surfsense documentation files. - - Args: - session: SQLAlchemy async session - - Returns: - Tuple of (created, updated, skipped, deleted) counts - """ - created = 0 - updated = 0 - skipped = 0 - deleted = 0 - - # Get all existing docs from database - existing_docs_result = await session.execute( - select(SurfsenseDocsDocument).options( - selectinload(SurfsenseDocsDocument.chunks) - ) - ) - existing_docs = {doc.source: doc for doc in existing_docs_result.scalars().all()} - - # Track which sources we've processed - processed_sources = set() - - # Get all MDX files - mdx_files = get_all_mdx_files() - logger.info(f"Found {len(mdx_files)} MDX files to index") - - for mdx_file in mdx_files: - try: - source = str(mdx_file.relative_to(DOCS_DIR)) - processed_sources.add(source) - - # Read file content - raw_content = mdx_file.read_text(encoding="utf-8") - title, content = parse_mdx_frontmatter(raw_content) - content_hash = generate_surfsense_docs_content_hash(raw_content) - - if source in existing_docs: - existing_doc = existing_docs[source] - - # Check if content changed - if existing_doc.content_hash == content_hash: - logger.debug(f"Skipping unchanged: {source}") - skipped += 1 - continue - - # Content changed - update document - logger.info(f"Updating changed document: {source}") - - # Create new chunks - chunks = create_surfsense_docs_chunks(content) - - # Update document fields - existing_doc.title = title - existing_doc.content = content - existing_doc.content_hash = content_hash - existing_doc.embedding = embed_text(content) - await _safe_set_docs_chunks(session, existing_doc, chunks) - existing_doc.updated_at = datetime.now(UTC) - - updated += 1 - else: - # New document - create it - logger.info(f"Creating new document: {source}") - - chunks = create_surfsense_docs_chunks(content) - - document = SurfsenseDocsDocument( - source=source, - title=title, - content=content, - content_hash=content_hash, - embedding=embed_text(content), - chunks=chunks, - updated_at=datetime.now(UTC), - ) - - session.add(document) - created += 1 - - except Exception as e: - logger.error(f"Error processing {mdx_file}: {e}", exc_info=True) - continue - - # Delete documents for removed files - for source, doc in existing_docs.items(): - if source not in processed_sources: - logger.info(f"Deleting removed document: {source}") - await session.delete(doc) - deleted += 1 - - # Commit all changes - await session.commit() - - logger.info( - f"Indexing complete: {created} created, {updated} updated, " - f"{skipped} skipped, {deleted} deleted" - ) - - return created, updated, skipped, deleted - - -async def seed_surfsense_docs() -> tuple[int, int, int, int]: - """ - Seed Surfsense documentation into the database. - - This function indexes all MDX files from the docs directory. - It handles creating, updating, and deleting docs based on content changes. - - Returns: - Tuple of (created, updated, skipped, deleted) counts - Returns (0, 0, 0, 0) if an error occurs - """ - logger.info("Starting Surfsense docs indexing...") - - try: - async with async_session_maker() as session: - created, updated, skipped, deleted = await index_surfsense_docs(session) - - logger.info( - f"Surfsense docs indexing complete: " - f"created={created}, updated={updated}, skipped={skipped}, deleted={deleted}" - ) - - return created, updated, skipped, deleted - - except Exception as e: - logger.error(f"Failed to seed Surfsense docs: {e}", exc_info=True) - return 0, 0, 0, 0 diff --git a/surfsense_backend/app/utils/surfsense_docs.py b/surfsense_backend/app/utils/surfsense_docs.py deleted file mode 100644 index 9a6ab11a9..000000000 --- a/surfsense_backend/app/utils/surfsense_docs.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Utilities for SurfSense's built-in documentation index.""" - -from pathlib import PurePosixPath - -DOCS_PUBLIC_ROOT = PurePosixPath("/docs") - - -def surfsense_docs_public_url(source: str) -> str: - """Return the public docs route for an indexed documentation source path.""" - docs_path = PurePosixPath(source).with_suffix("") - if docs_path.name == "index": - docs_path = docs_path.parent - return (DOCS_PUBLIC_ROOT / docs_path).as_posix() diff --git a/surfsense_backend/scripts/seed_surfsense_docs.py b/surfsense_backend/scripts/seed_surfsense_docs.py deleted file mode 100644 index 68899c2aa..000000000 --- a/surfsense_backend/scripts/seed_surfsense_docs.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -""" -Seed Surfsense documentation into the database. - -CLI wrapper for the seed_surfsense_docs function. -Can be run manually for debugging or re-indexing. - -Usage: - python scripts/seed_surfsense_docs.py -""" - -import asyncio -import sys -from pathlib import Path - -# Add the parent directory to the path so we can import app modules -sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -from app.tasks.surfsense_docs_indexer import seed_surfsense_docs - - -def main(): - """CLI entry point for seeding Surfsense docs.""" - print("=" * 50) - print(" Surfsense Documentation Seeding") - print("=" * 50) - - created, updated, skipped, deleted = asyncio.run(seed_surfsense_docs()) - - print() - print("Results:") - print(f" Created: {created}") - print(f" Updated: {updated}") - print(f" Skipped: {skipped}") - print(f" Deleted: {deleted}") - print("=" * 50) - - -if __name__ == "__main__": - main() diff --git a/surfsense_backend/tests/unit/agents/new_chat/test_default_permissions_layering.py b/surfsense_backend/tests/unit/agents/new_chat/test_default_permissions_layering.py index ac6b5d95c..2f222e148 100644 --- a/surfsense_backend/tests/unit/agents/new_chat/test_default_permissions_layering.py +++ b/surfsense_backend/tests/unit/agents/new_chat/test_default_permissions_layering.py @@ -60,7 +60,6 @@ class TestReadOnlyToolsAllowed: "glob", "web_search", "scrape_webpage", - "search_surfsense_docs", "get_connected_accounts", "write_todos", "task", diff --git a/surfsense_backend/tests/unit/agents/new_chat/test_specialized_subagents.py b/surfsense_backend/tests/unit/agents/new_chat/test_specialized_subagents.py index 3035cc8e0..3c7fe5336 100644 --- a/surfsense_backend/tests/unit/agents/new_chat/test_specialized_subagents.py +++ b/surfsense_backend/tests/unit/agents/new_chat/test_specialized_subagents.py @@ -22,12 +22,6 @@ from app.agents.new_chat.subagents.config import ( # --------------------------------------------------------------------------- -@tool -def search_surfsense_docs(query: str) -> str: - """Search the user's KB.""" - return "" - - @tool def web_search(query: str) -> str: """Search the public web.""" @@ -95,7 +89,6 @@ def generate_report(topic: str) -> str: ALL_TOOLS = [ - search_surfsense_docs, web_search, scrape_webpage, read_file, @@ -161,7 +154,7 @@ class TestReportWriterSubagent: names = {t.name for t in spec["tools"]} # type: ignore[index] assert names == REPORT_WRITER_TOOLS & {t.name for t in ALL_TOOLS} assert "generate_report" in names - assert "search_surfsense_docs" in names + assert "read_file" in names def test_deny_rules_block_writes_but_allow_generate_report(self) -> None: spec = build_report_writer_subagent(tools=ALL_TOOLS) @@ -272,9 +265,9 @@ class TestFilterToolsWarningSuppression: # Allowed set asks for two registry tools (one present, one # not) plus a bunch of middleware-provided names. _filter_tools( - [search_surfsense_docs], + [web_search], allowed_names={ - "search_surfsense_docs", + "web_search", "scrape_webpage", # legitimately missing → should warn "read_file", # mw-provided → suppressed "ls", @@ -322,7 +315,6 @@ class TestDenyPatternsCoverage: def test_deny_patterns_do_not_match_safe_read_tools(self) -> None: canonical_reads = [ - "search_surfsense_docs", "read_file", "ls_tree", "grep", diff --git a/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py b/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py index ff4ca30df..e014bb911 100644 --- a/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py +++ b/surfsense_backend/tests/unit/tasks/chat/streaming/test_parallel_refactor_parity.py @@ -25,7 +25,6 @@ from __future__ import annotations import asyncio import inspect -from dataclasses import dataclass from typing import Any from unittest.mock import AsyncMock, patch @@ -140,45 +139,28 @@ def test_orchestrators_are_async_generator_functions() -> None: # ------------------------------------------------------------ initial thinking -@dataclass -class _FakeSurfsenseDoc: - """Stand-in for ``SurfsenseDocsDocument`` with just the field we read.""" - - title: str - - @pytest.mark.parametrize( - "user_query, image_urls, docs, expected_title, expected_action", + "user_query, image_urls, expected_title, expected_action", [ - ("hello world", None, [], "Understanding your request", "Processing"), + ("hello world", None, "Understanding your request", "Processing"), ( "", ["data:image/png;base64,AAA"], - [], "Understanding your request", "Processing", ), - ("", None, [], "Understanding your request", "Processing"), - ( - "doc question", - None, - [_FakeSurfsenseDoc(title="My Doc")], - "Analyzing referenced content", - "Analyzing", - ), + ("", None, "Understanding your request", "Processing"), ], ) def test_initial_thinking_step_branches( user_query: str, image_urls: list[str] | None, - docs: list[Any], expected_title: str, expected_action: str, ) -> None: step = build_initial_thinking_step( user_query=user_query, user_image_data_urls=image_urls, - mentioned_surfsense_docs=docs, # type: ignore[arg-type] ) assert step.step_id == "thinking-1" assert step.title == expected_title @@ -191,7 +173,6 @@ def test_initial_thinking_step_truncates_long_query() -> None: step = build_initial_thinking_step( user_query=long_query, user_image_data_urls=None, - mentioned_surfsense_docs=[], ) # 80-char truncation + ellipsis, sandwiched after "Processing: ". assert "..." in step.items[0] @@ -200,16 +181,6 @@ def test_initial_thinking_step_truncates_long_query() -> None: assert payload.startswith("x" * 80) and payload.endswith("...") -def test_initial_thinking_step_collapses_many_doc_names() -> None: - docs = [_FakeSurfsenseDoc(title=f"Doc {i}") for i in range(5)] - step = build_initial_thinking_step( - user_query="q", - user_image_data_urls=None, - mentioned_surfsense_docs=docs, # type: ignore[arg-type] - ) - assert "[5 docs]" in step.items[0] - - # ------------------------------------------------------------ capability gate diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx index 312454056..c0651a90b 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/mention-task-input.tsx @@ -89,10 +89,10 @@ function removeFirstToken(text: string, token: string): string { /** * Task input that reuses the chat ``@`` mention experience -- the same - * ``InlineMentionEditor`` + ``DocumentMentionPicker`` as the composer, minus - * SurfSense product docs. The editor is the source of truth while mounted; - * ``onChange`` reports both the plain text (chips rendered as ``@Title``) and - * the structured mention list so the builder can persist IDs for the run. + * ``InlineMentionEditor`` + ``DocumentMentionPicker`` as the composer. The + * editor is the source of truth while mounted; ``onChange`` reports both the + * plain text (chips rendered as ``@Title``) and the structured mention list + * so the builder can persist IDs for the run. */ export function MentionTaskInput({ searchSpaceId, @@ -233,7 +233,6 @@ export function MentionTaskInput({ documentsApiService.searchDocumentTitles({ queryParams: prefetchParams }), staleTime: 60 * 1000, }); - - queryClient.prefetchQuery({ - queryKey: ["surfsense-docs-mention", "", false], - queryFn: () => - documentsApiService.getSurfsenseDocs({ - queryParams: { page: 0, page_size: 20 }, - }), - staleTime: 3 * 60 * 1000, - }); }, [searchSpaceId, queryClient]); // Handle scroll to comment from URL query params (e.g., from inbox item click) @@ -949,7 +940,6 @@ export default function NewChatPage() { trackChatMessageSent(searchSpaceId, currentThreadId, { hasAttachments: userImages.length > 0, hasMentionedDocuments: - mentionedDocumentIds.surfsense_doc_ids.length > 0 || mentionedDocumentIds.document_ids.length > 0 || mentionedDocumentIds.folder_ids.length > 0 || mentionedDocumentIds.connector_ids.length > 0, @@ -1027,12 +1017,11 @@ export default function NewChatPage() { // Get mentioned document IDs for context (separate fields for backend) const hasDocumentIds = mentionedDocumentIds.document_ids.length > 0; - const hasSurfsenseDocIds = mentionedDocumentIds.surfsense_doc_ids.length > 0; const hasFolderIds = mentionedDocumentIds.folder_ids.length > 0; const hasConnectorIds = mentionedDocumentIds.connector_ids.length > 0; // Clear mentioned documents after capturing them - if (hasDocumentIds || hasSurfsenseDocIds || hasFolderIds || hasConnectorIds) { + if (hasDocumentIds || hasFolderIds || hasConnectorIds) { setMentionedDocuments([]); } @@ -1054,9 +1043,6 @@ export default function NewChatPage() { mentioned_document_ids: hasDocumentIds ? mentionedDocumentIds.document_ids : undefined, - mentioned_surfsense_doc_ids: hasSurfsenseDocIds - ? mentionedDocumentIds.surfsense_doc_ids - : undefined, mentioned_folder_ids: hasFolderIds ? mentionedDocumentIds.folder_ids : undefined, mentioned_connector_ids: hasConnectorIds ? mentionedDocumentIds.connector_ids @@ -1947,18 +1933,14 @@ export default function NewChatPage() { const selection = await getAgentFilesystemSelection(searchSpaceId, { localFilesystemEnabled, }); - // Partition the source mentions back into doc/surfsense_doc/folder - // id buckets so the regenerate route can pass them to - // ``stream_new_chat`` and the priority middleware sees the - // same ``[USER-MENTIONED]`` priority entries the original - // turn did. Without this partition the regenerate flow - // silently dropped the agent's mention awareness — same - // architectural bug we fixed on the new-chat path. - const regenerateSurfsenseDocIds = sourceMentionedDocs - .filter((d) => d.kind === "doc" && d.document_type === "SURFSENSE_DOCS") - .map((d) => d.id); + // Partition the source mentions back into doc/folder id buckets + // so the regenerate route can pass them to ``stream_new_chat`` + // and the priority middleware sees the same ``[USER-MENTIONED]`` + // priority entries the original turn did. Without this partition + // the regenerate flow silently dropped the agent's mention + // awareness — same architectural bug we fixed on the new-chat path. const regenerateDocIds = sourceMentionedDocs - .filter((d) => d.kind === "doc" && d.document_type !== "SURFSENSE_DOCS") + .filter((d) => d.kind === "doc") .map((d) => d.id); const regenerateFolderIds = sourceMentionedDocs .filter((d) => d.kind === "folder") @@ -1973,8 +1955,6 @@ export default function NewChatPage() { client_platform: selection.client_platform, local_filesystem_mounts: selection.local_filesystem_mounts, mentioned_document_ids: regenerateDocIds.length > 0 ? regenerateDocIds : undefined, - mentioned_surfsense_doc_ids: - regenerateSurfsenseDocIds.length > 0 ? regenerateSurfsenseDocIds : undefined, mentioned_folder_ids: regenerateFolderIds.length > 0 ? regenerateFolderIds : undefined, mentioned_connector_ids: regenerateConnectors.length > 0 ? regenerateConnectors.map((d) => d.id) : undefined, diff --git a/surfsense_web/atoms/chat/mentioned-documents.atom.ts b/surfsense_web/atoms/chat/mentioned-documents.atom.ts index 25d1e397a..cf1bd8bcf 100644 --- a/surfsense_web/atoms/chat/mentioned-documents.atom.ts +++ b/surfsense_web/atoms/chat/mentioned-documents.atom.ts @@ -102,10 +102,7 @@ export const mentionedDocumentIdsAtom = atom((get) => { const folders = deduped.filter((m) => m.kind === "folder"); const connectors = deduped.filter((m) => m.kind === "connector"); return { - surfsense_doc_ids: docs - .filter((doc) => doc.document_type === "SURFSENSE_DOCS") - .map((doc) => doc.id), - document_ids: docs.filter((doc) => doc.document_type !== "SURFSENSE_DOCS").map((doc) => doc.id), + document_ids: docs.map((doc) => doc.id), folder_ids: folders.map((f) => f.id), connector_ids: connectors.map((c) => c.id), connectors: connectors.map((c) => ({ diff --git a/surfsense_web/components/assistant-ui/inline-citation.tsx b/surfsense_web/components/assistant-ui/inline-citation.tsx index a788c0ce6..cbf3c82d6 100644 --- a/surfsense_web/components/assistant-ui/inline-citation.tsx +++ b/surfsense_web/components/assistant-ui/inline-citation.tsx @@ -1,16 +1,13 @@ "use client"; -import { useQuery } from "@tanstack/react-query"; import { useSetAtom } from "jotai"; -import { ExternalLink, FileText } from "lucide-react"; -import dynamic from "next/dynamic"; +import { FileText } from "lucide-react"; import type { FC } from "react"; import { useState } from "react"; import { openCitationPanelAtom } from "@/atoms/citation/citation-panel.atom"; import { useCitationMetadata } from "@/components/assistant-ui/citation-metadata-context"; import { CitationPanelContent } from "@/components/citation-panel/citation-panel"; import { Citation } from "@/components/tool-ui/citation"; -import { CitationHoverPopover } from "@/components/tool-ui/citation/citation-hover-popover"; import { Button } from "@/components/ui/button"; import { Drawer, @@ -19,21 +16,8 @@ import { DrawerHeader, DrawerTitle, } from "@/components/ui/drawer"; -import { Spinner } from "@/components/ui/spinner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useMediaQuery } from "@/hooks/use-media-query"; -import { documentsApiService } from "@/lib/apis/documents-api.service"; -import { cacheKeys } from "@/lib/query-client/cache-keys"; - -// Lazily load MarkdownViewer here to break the static import cycle: -// `markdown-viewer.tsx` → `citation-renderer.tsx` → `inline-citation.tsx` -// would otherwise pull `markdown-viewer.tsx` back in at module-init time. -// Only `SurfsenseDocCitation` (popover body) ever renders this viewer, so -// the lazy boundary is invisible to most call paths. -const MarkdownViewer = dynamic( - () => import("@/components/markdown-viewer").then((m) => m.MarkdownViewer), - { ssr: false, loading: () => } -); interface InlineCitationProps { chunkId: number; @@ -41,9 +25,7 @@ interface InlineCitationProps { } /** - * Inline citation badge for knowledge-base chunks (numeric chunk IDs) and - * Surfsense documentation chunks (`isDocsChunk`). Negative chunk IDs render as - * a static "doc" pill (anonymous/synthetic uploads). + * Inline citation badge for knowledge-base chunks (numeric chunk IDs). * * Numeric KB chunks: clicking opens the citation panel in the right * sidebar (alongside the chat — does not replace it). The panel shows @@ -51,12 +33,13 @@ interface InlineCitationProps { * `chunk_window`), with the cited one highlighted and an option to * expand the window or jump into the full document via the editor panel. * - * Surfsense docs chunks: rendered as a hover-controlled shadcn Popover that - * lazily fetches and previews the cited chunk inline, since those docs aren't - * indexed into the user's search space and have no tab to open. + * Negative chunk IDs and legacy SurfSense-docs chunks (`isDocsChunk`) render + * as a static, non-interactive "doc" pill. The SurfSense product-docs feature + * was removed, so those markers are inert (no fetch, no preview) — they only + * survive in old persisted messages. */ export const InlineCitation: FC = ({ chunkId, isDocsChunk = false }) => { - if (chunkId < 0) { + if (chunkId < 0 || isDocsChunk) { return ( @@ -68,15 +51,11 @@ export const InlineCitation: FC = ({ chunkId, isDocsChunk = doc - Uploaded document + {isDocsChunk ? "Documentation reference" : "Uploaded document"} ); } - if (isDocsChunk) { - return ; - } - return ; }; @@ -127,128 +106,6 @@ const NumericChunkCitation: FC<{ chunkId: number }> = ({ chunkId }) => { ); }; -const SurfsenseDocCitation: FC<{ chunkId: number }> = ({ chunkId }) => { - const isTouchLike = useMediaQuery("(hover: none), (pointer: coarse)"); - const [mobilePreviewOpen, setMobilePreviewOpen] = useState(false); - const docQuery = useSurfsenseDocPreviewQuery(chunkId, mobilePreviewOpen); - - const handleMobileClick = () => { - setMobilePreviewOpen(true); - }; - - return ( - <> - ( - - )} - > - - - - - - - Surfsense documentation - - - - - - ); -}; - -function useSurfsenseDocPreviewQuery(chunkId: number, enabled = true) { - return useQuery({ - queryKey: cacheKeys.documents.byChunk(`doc-${chunkId}`), - queryFn: () => documentsApiService.getSurfsenseDocByChunk(chunkId), - staleTime: 5 * 60 * 1000, - enabled, - }); -} - -type SurfsenseDocPreviewQuery = ReturnType; - -const SurfsenseDocPreview: FC<{ chunkId: number }> = ({ chunkId }) => { - const query = useSurfsenseDocPreviewQuery(chunkId); - - return ; -}; - -const SurfsenseDocPreviewContent: FC<{ - chunkId: number; - query: SurfsenseDocPreviewQuery; - contentClassName?: string; -}> = ({ chunkId, query, contentClassName = "max-h-72" }) => { - const { data, isLoading, error } = query; - - const citedChunk = data?.chunks.find((c) => c.id === chunkId) ?? data?.chunks[0]; - - return ( - <> -
-
-

{data?.title ?? "Surfsense documentation"}

-

Chunk #{chunkId}

-
- {data?.public_url && ( - - - Open - - )} -
-
- {isLoading && ( -
- - Loading… -
- )} - {error && ( -

- {error instanceof Error ? error.message : "Failed to load chunk"} -

- )} - {!isLoading && !error && citedChunk?.content && ( - - )} - {!isLoading && !error && !citedChunk?.content && ( -

No content available.

- )} -
- - ); -}; - import { tryGetHostname } from "@/lib/url"; interface UrlCitationProps { diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 9abcfbb49..0336ffd35 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -1593,7 +1593,7 @@ interface ToolGroup { const TOOL_GROUPS: ToolGroup[] = [ { label: "Research", - tools: ["search_surfsense_docs", "scrape_webpage"], + tools: ["scrape_webpage"], }, { label: "Generate", diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx index 757ee2fc2..881fbe2b0 100644 --- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx @@ -90,7 +90,6 @@ const DesktopLocalTabContent = dynamic( ); const NON_DELETABLE_DOCUMENT_TYPES: readonly string[] = [ - "SURFSENSE_DOCS", "USER_MEMORY", "TEAM_MEMORY", ]; diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index 8e3fd4ca8..769327e1e 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -3,14 +3,7 @@ import { useQuery as useZeroQuery } from "@rocicorp/zero/react"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; import { useAtomValue } from "jotai"; -import { - BookOpen, - ChevronLeft, - ChevronRight, - Files, - Folder as FolderIcon, - Unplug, -} from "lucide-react"; +import { ChevronLeft, ChevronRight, Files, Folder as FolderIcon, Unplug } from "lucide-react"; import { Fragment, forwardRef, @@ -57,13 +50,6 @@ interface DocumentMentionPickerProps { onDone: () => void; initialSelectedDocuments?: MentionedDocumentInfo[]; externalSearch?: string; - /** - * Whether to surface the "SurfSense Docs" (product documentation) branch - * and include those docs in search results. Defaults to ``true`` so the - * chat composer is unchanged; callers like the automation task input pass - * ``false`` to reference only the user's own knowledge base + connectors. - */ - includeSurfsenseDocs?: boolean; } const PAGE_SIZE = 20; @@ -74,7 +60,6 @@ const RECENTS_STORAGE_PREFIX = "surfsense:composer-mention-recents:v1:"; type BrowseView = | { kind: "root" } - | { kind: "surfsense-docs" } | { kind: "files-folders" } | { kind: "connectors" } | { kind: "connector-type"; connectorType: string; title: string }; @@ -241,7 +226,6 @@ export const DocumentMentionPicker = forwardRef< onDone, initialSelectedDocuments = [], externalSearch = "", - includeSurfsenseDocs = true, }, ref ) { @@ -298,15 +282,6 @@ export const DocumentMentionPicker = forwardRef< [searchSpaceId, debouncedSearch, isSearchValid] ); - const surfsenseDocsQueryParams = useMemo(() => { - const params: { page: number; page_size: number; title?: string } = { - page: 0, - page_size: PAGE_SIZE, - }; - if (isSearchValid) params.title = debouncedSearch.trim(); - return params; - }, [debouncedSearch, isSearchValid]); - const { data: titleSearchResults, isLoading: isTitleSearchLoading } = useQuery({ queryKey: ["document-titles", titleSearchParams], queryFn: ({ signal }) => @@ -316,15 +291,6 @@ export const DocumentMentionPicker = forwardRef< placeholderData: keepPreviousData, }); - const { data: surfsenseDocs, isLoading: isSurfsenseDocsLoading } = useQuery({ - queryKey: ["surfsense-docs-mention", debouncedSearch, isSearchValid], - queryFn: ({ signal }) => - documentsApiService.getSurfsenseDocs({ queryParams: surfsenseDocsQueryParams }, signal), - staleTime: 3 * 60 * 1000, - enabled: includeSurfsenseDocs && (!hasSearch || isSearchValid), - placeholderData: keepPreviousData, - }); - const filterBySearchTerm = useCallback( (docs: Pick[]) => { if (!isSearchValid) return docs; @@ -338,23 +304,13 @@ export const DocumentMentionPicker = forwardRef< if (currentPage !== 0) return; const combinedDocs: Pick[] = []; - if (includeSurfsenseDocs && surfsenseDocs?.items) { - for (const doc of surfsenseDocs.items) { - combinedDocs.push({ - id: doc.id, - title: doc.title, - document_type: "SURFSENSE_DOCS", - }); - } - } - if (titleSearchResults?.items) { combinedDocs.push(...titleSearchResults.items); setHasMore(titleSearchResults.has_more); } setAccumulatedDocuments(filterBySearchTerm(combinedDocs)); - }, [titleSearchResults, surfsenseDocs, currentPage, filterBySearchTerm, includeSurfsenseDocs]); + }, [titleSearchResults, currentPage, filterBySearchTerm]); const loadNextPage = useCallback(async () => { if (isLoadingMore || !hasMore) return; @@ -391,14 +347,6 @@ export const DocumentMentionPicker = forwardRef< return accumulatedDocuments.filter((doc) => doc.title.toLowerCase().includes(searchLower)); }, [accumulatedDocuments, deferredSearch, isSingleCharSearch]); - const surfsenseDocsList = useMemo( - () => actualDocuments.filter((doc) => doc.document_type === "SURFSENSE_DOCS"), - [actualDocuments] - ); - const userDocsList = useMemo( - () => actualDocuments.filter((doc) => doc.document_type !== "SURFSENSE_DOCS"), - [actualDocuments] - ); const folderMentions = useMemo(() => { const all = (zeroFolders ?? []).map((f) => makeFolderMention({ id: f.id, title: f.name })); if (!hasSearch) return all; @@ -463,7 +411,6 @@ export const DocumentMentionPicker = forwardRef< () => new Set(initialSelectedDocuments.map((d) => getMentionDocKey(d))), [initialSelectedDocuments] ); - const showSurfsenseDocsRoot = includeSurfsenseDocs && surfsenseDocsList.length > 0; const selectMention = useCallback( (mention: MentionedDocumentInfo) => { @@ -487,16 +434,6 @@ export const DocumentMentionPicker = forwardRef< const rootNodes = useMemo[]>(() => { const nodes: ComposerSuggestionNode[] = [...recentRootNodes]; - if (showSurfsenseDocsRoot) { - nodes.push({ - id: "surfsense-docs", - label: "SurfSense Docs", - subtitle: "Browse product documentation", - icon: , - type: "branch", - value: { kind: "view", view: { kind: "surfsense-docs" } }, - }); - } nodes.push( { id: "files-folders", @@ -519,7 +456,7 @@ export const DocumentMentionPicker = forwardRef< } ); return nodes; - }, [activeConnectors.length, recentRootNodes, showSurfsenseDocsRoot]); + }, [activeConnectors.length, recentRootNodes]); const searchNodes = useMemo[]>(() => { const searchLower = (isSingleCharSearch ? deferredSearch : debouncedSearch) @@ -582,19 +519,6 @@ export const DocumentMentionPicker = forwardRef< const browseNodes = useMemo[]>(() => { if (view.kind === "root") return rootNodes; - if (view.kind === "surfsense-docs") { - return surfsenseDocsList.map((doc) => { - const mention = makeDocMention(doc); - return { - id: getMentionDocKey(mention), - label: doc.title, - icon: getConnectorIcon(doc.document_type, "size-4"), - type: "item" as const, - disabled: selectedKeys.has(getMentionDocKey(mention)), - value: { kind: "mention" as const, mention }, - }; - }); - } if (view.kind === "files-folders") { const folders = folderMentions.map((mention) => ({ id: getMentionDocKey(mention), @@ -605,7 +529,7 @@ export const DocumentMentionPicker = forwardRef< disabled: selectedKeys.has(getMentionDocKey(mention)), value: { kind: "mention" as const, mention }, })); - const docs = userDocsList.map((doc) => { + const docs = actualDocuments.map((doc) => { const mention = makeDocMention(doc); return { id: getMentionDocKey(mention), @@ -652,13 +576,12 @@ export const DocumentMentionPicker = forwardRef< }; }); }, [ + actualDocuments, activeConnectors, connectorTypeEntries, folderMentions, rootNodes, selectedKeys, - surfsenseDocsList, - userDocsList, view, ]); @@ -708,27 +631,23 @@ export const DocumentMentionPicker = forwardRef< const isRootBrowseView = !hasSearch && view.kind === "root"; const isVisibleViewLoading = hasSearch - ? isTitleSearchLoading || isSurfsenseDocsLoading || isConnectorsLoading - : view.kind === "surfsense-docs" - ? isSurfsenseDocsLoading - : view.kind === "files-folders" - ? isTitleSearchLoading - : view.kind === "connectors" || view.kind === "connector-type" - ? isConnectorsLoading - : false; + ? isTitleSearchLoading || isConnectorsLoading + : view.kind === "files-folders" + ? isTitleSearchLoading + : view.kind === "connectors" || view.kind === "connector-type" + ? isConnectorsLoading + : false; const actualLoading = isVisibleViewLoading && !isSingleCharSearch && visibleNodes.length === 0 && !isRootBrowseView; const title = hasSearch || view.kind === "root" ? null - : view.kind === "surfsense-docs" - ? "SurfSense Docs" - : view.kind === "files-folders" - ? "Files & Folders" - : view.kind === "connectors" - ? "Connectors" - : view.title; + : view.kind === "files-folders" + ? "Files & Folders" + : view.kind === "connectors" + ? "Connectors" + : view.title; return ( ; case "EXTENSION": return ; - case "SURFSENSE_DOCS": - return ; case "USER_MEMORY": case "TEAM_MEMORY": return ; diff --git a/surfsense_web/contracts/enums/toolIcons.tsx b/surfsense_web/contracts/enums/toolIcons.tsx index 668cb51cd..494c0eaee 100644 --- a/surfsense_web/contracts/enums/toolIcons.tsx +++ b/surfsense_web/contracts/enums/toolIcons.tsx @@ -1,5 +1,4 @@ import { - BookOpen, Brain, Calendar, FileEdit, @@ -47,7 +46,6 @@ const TOOL_ICONS: Record = { // Web / search scrape_webpage: ScanLine, web_search: Globe, - search_surfsense_docs: BookOpen, // Automations create_automation: Workflow, // Memory @@ -152,7 +150,6 @@ const TOOL_DISPLAY_NAMES: Record = { // Web / search scrape_webpage: "Read webpage", web_search: "Search the web", - search_surfsense_docs: "Search knowledge base", // Automations create_automation: "Create automation", // Memory diff --git a/surfsense_web/contracts/types/document.types.ts b/surfsense_web/contracts/types/document.types.ts index ccc15fa62..82c6cbdaf 100644 --- a/surfsense_web/contracts/types/document.types.ts +++ b/surfsense_web/contracts/types/document.types.ts @@ -27,7 +27,6 @@ export const documentTypeEnum = z.enum([ "CIRCLEBACK", "OBSIDIAN_CONNECTOR", "LOCAL_FOLDER_FILE", - "SURFSENSE_DOCS", "NOTE", "USER_MEMORY", "TEAM_MEMORY", @@ -77,27 +76,6 @@ export const documentWithChunks = document.extend({ chunk_start_index: z.number().optional().default(0), }); -/** - * Surfsense documentation schemas - * Follows the same pattern as document/documentWithChunks - */ -export const surfsenseDocsChunk = z.object({ - id: z.number(), - content: z.string(), -}); - -export const surfsenseDocsDocument = z.object({ - id: z.number(), - title: z.string(), - source: z.string(), - public_url: z.string(), - content: z.string(), -}); - -export const surfsenseDocsDocumentWithChunks = surfsenseDocsDocument.extend({ - chunks: z.array(surfsenseDocsChunk), -}); - /** * Get documents */ @@ -284,32 +262,6 @@ export const getDocumentChunksResponse = z.object({ has_more: z.boolean(), }); -/** - * Get Surfsense docs by chunk - */ -export const getSurfsenseDocsByChunkRequest = z.object({ - chunk_id: z.number(), -}); - -export const getSurfsenseDocsByChunkResponse = surfsenseDocsDocumentWithChunks; - -/** - * List Surfsense docs - */ -export const getSurfsenseDocsRequest = z.object({ - queryParams: paginationQueryParams.extend({ - title: z.string().optional(), - }), -}); - -export const getSurfsenseDocsResponse = z.object({ - items: z.array(surfsenseDocsDocument), - total: z.number(), - page: z.number(), - page_size: z.number(), - has_more: z.boolean(), -}); - /** * Update document */ @@ -358,13 +310,6 @@ export type DeleteDocumentResponse = z.infer; export type DocumentTypeEnum = z.infer; export type DocumentSortBy = z.infer; export type SortOrder = z.infer; -export type SurfsenseDocsChunk = z.infer; -export type SurfsenseDocsDocument = z.infer; -export type SurfsenseDocsDocumentWithChunks = z.infer; -export type GetSurfsenseDocsByChunkRequest = z.infer; -export type GetSurfsenseDocsByChunkResponse = z.infer; -export type GetSurfsenseDocsRequest = z.infer; -export type GetSurfsenseDocsResponse = z.infer; export type GetDocumentChunksRequest = z.infer; export type GetDocumentChunksResponse = z.infer; export type ChunkRead = z.infer; diff --git a/surfsense_web/lib/apis/documents-api.service.ts b/surfsense_web/lib/apis/documents-api.service.ts index 630c88d16..f9785c8a8 100644 --- a/surfsense_web/lib/apis/documents-api.service.ts +++ b/surfsense_web/lib/apis/documents-api.service.ts @@ -12,7 +12,6 @@ import { type GetDocumentsRequest, type GetDocumentsStatusRequest, type GetDocumentTypeCountsRequest, - type GetSurfsenseDocsRequest, getDocumentByChunkRequest, getDocumentByChunkResponse, getDocumentChunksRequest, @@ -25,9 +24,6 @@ import { getDocumentsStatusResponse, getDocumentTypeCountsRequest, getDocumentTypeCountsResponse, - getSurfsenseDocsByChunkResponse, - getSurfsenseDocsRequest, - getSurfsenseDocsResponse, type SearchDocumentsRequest, type SearchDocumentTitlesRequest, searchDocumentsRequest, @@ -363,48 +359,6 @@ class DocumentsApiService { ); }; - /** - * Get Surfsense documentation by chunk ID - * Used for resolving [citation:doc-XXX] citations - */ - getSurfsenseDocByChunk = async (chunkId: number) => { - return baseApiService.get( - `/api/v1/surfsense-docs/by-chunk/${chunkId}`, - getSurfsenseDocsByChunkResponse - ); - }; - - /** - * List all Surfsense documentation documents - * @param request - The request with query params - * @param signal - Optional AbortSignal for request cancellation - */ - getSurfsenseDocs = async (request: GetSurfsenseDocsRequest, signal?: AbortSignal) => { - const parsedRequest = getSurfsenseDocsRequest.safeParse(request); - - if (!parsedRequest.success) { - console.error("Invalid request:", parsedRequest.error); - - const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", "); - throw new ValidationError(`Invalid request: ${errorMessage}`); - } - - // Transform query params to be string values - const transformedQueryParams = parsedRequest.data.queryParams - ? Object.fromEntries( - Object.entries(parsedRequest.data.queryParams).map(([k, v]) => [k, String(v)]) - ) - : undefined; - - const queryParams = transformedQueryParams - ? new URLSearchParams(transformedQueryParams).toString() - : ""; - - const url = `/api/v1/surfsense-docs?${queryParams}`; - - return baseApiService.get(url, getSurfsenseDocsResponse, { signal }); - }; - /** * Update a document */ diff --git a/surfsense_web/lib/chat/thread-persistence.ts b/surfsense_web/lib/chat/thread-persistence.ts index abe6bc02c..d30b87665 100644 --- a/surfsense_web/lib/chat/thread-persistence.ts +++ b/surfsense_web/lib/chat/thread-persistence.ts @@ -221,7 +221,6 @@ export interface RegenerateParams { content: string; }>; mentionedDocumentIds?: number[]; - mentionedSurfsenseDocIds?: number[]; } /** diff --git a/surfsense_web/lib/documents/document-type-labels.ts b/surfsense_web/lib/documents/document-type-labels.ts index 844961886..9e187f940 100644 --- a/surfsense_web/lib/documents/document-type-labels.ts +++ b/surfsense_web/lib/documents/document-type-labels.ts @@ -25,7 +25,6 @@ export function getDocumentTypeLabel(type: string): string { CIRCLEBACK: "Circleback", OBSIDIAN_CONNECTOR: "Obsidian", LOCAL_FOLDER_FILE: "Local Folder", - SURFSENSE_DOCS: "SurfSense Docs", NOTE: "Note", COMPOSIO_GOOGLE_DRIVE_CONNECTOR: "Composio Google Drive", COMPOSIO_GMAIL_CONNECTOR: "Composio Gmail", diff --git a/surfsense_web/lib/query-client/cache-keys.ts b/surfsense_web/lib/query-client/cache-keys.ts index 8943d6842..35724cf94 100644 --- a/surfsense_web/lib/query-client/cache-keys.ts +++ b/surfsense_web/lib/query-client/cache-keys.ts @@ -30,7 +30,6 @@ export const cacheKeys = { withQueryParams: (queries: GetDocumentsRequest["queryParams"]) => ["documents-with-queries", ...stableEntries(queries)] as const, document: (documentId: string) => ["document", documentId] as const, - byChunk: (chunkId: string) => ["documents", "by-chunk", chunkId] as const, }, logs: { list: (searchSpaceId?: number | string) => ["logs", "list", searchSpaceId] as const, diff --git a/surfsense_web/tsc_out.txt b/surfsense_web/tsc_out.txt new file mode 100644 index 0000000000000000000000000000000000000000..c51e470851df2ea0ac136579d4d6112e183bad16 GIT binary patch literal 32582 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 LlrupwRW$TJ&LaAJ literal 0 HcmV?d00001 From 409fec94c329b15a7fbbab2ad55fce28e7293fc4 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Fri, 29 May 2026 03:13:46 -0700 Subject: [PATCH 160/198] feat(automations): implement model eligibility checks for automation creation - Added model eligibility checks to ensure automations can only use billable models (premium or BYOK). - Introduced new API endpoint to report model eligibility status for search spaces. - Updated frontend components to display eligibility alerts and disable creation options when models are not billable. - Enhanced automation creation forms to reflect model eligibility, preventing users from submitting invalid configurations. - Implemented server-side logic to capture and preserve model preferences across automation edits, ensuring consistent behavior during execution. --- .../main_agent/runtime/agent_cache.py | 7 +- .../main_agent/runtime/factory.py | 12 +- .../main_agent/tools/automation/create.py | 24 +- .../deliverables/tools/generate_image.py | 38 ++- .../builtins/deliverables/tools/index.py | 3 + .../actions/agent_task/dependencies.py | 45 +++- .../automations/actions/agent_task/invoke.py | 4 + .../app/automations/actions/types.py | 6 + .../app/automations/api/automation.py | 29 +++ .../app/automations/runtime/executor.py | 19 +- .../schemas/definition/__init__.py | 3 +- .../schemas/definition/envelope.py | 19 ++ .../app/automations/services/__init__.py | 12 + .../app/automations/services/automation.py | 64 ++++- .../app/automations/services/model_policy.py | 173 +++++++++++++ .../actions/agent_task/test_dependencies.py | 174 +++++++++++++ .../runtime/test_executor_action_ctx.py | 59 +++++ .../schemas/definition/test_envelope.py | 33 ++- .../unit/automations/services/__init__.py | 0 .../test_automation_service_policy.py | 236 ++++++++++++++++++ .../automations/services/test_model_policy.py | 196 +++++++++++++++ .../automations/automations-content.tsx | 25 +- .../automation-model-gate-alert.tsx | 61 +++++ .../components/automations-empty-state.tsx | 46 ++-- .../components/automations-header.tsx | 88 ++++++- .../builder/automation-builder-form.tsx | 52 +++- .../new/automation-new-content.tsx | 19 +- .../new-llm-config-mutation.atoms.ts | 6 + .../contracts/types/automation.types.ts | 30 +++ .../hooks/use-automation-model-eligibility.ts | 25 ++ .../lib/apis/automations-api.service.ts | 8 + surfsense_web/lib/query-client/cache-keys.ts | 2 + 32 files changed, 1451 insertions(+), 67 deletions(-) create mode 100644 surfsense_backend/app/automations/services/model_policy.py create mode 100644 surfsense_backend/tests/unit/automations/actions/agent_task/test_dependencies.py create mode 100644 surfsense_backend/tests/unit/automations/runtime/test_executor_action_ctx.py create mode 100644 surfsense_backend/tests/unit/automations/services/__init__.py create mode 100644 surfsense_backend/tests/unit/automations/services/test_automation_service_policy.py create mode 100644 surfsense_backend/tests/unit/automations/services/test_model_policy.py create mode 100644 surfsense_web/app/dashboard/[search_space_id]/automations/components/automation-model-gate-alert.tsx create mode 100644 surfsense_web/hooks/use-automation-model-eligibility.ts diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/agent_cache.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/agent_cache.py index 03cf7acb8..df1ee1b4c 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/agent_cache.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/agent_cache.py @@ -57,6 +57,7 @@ async def build_agent_with_cache( mcp_tools_by_agent: dict[str, list[BaseTool]], disabled_tools: list[str] | None, config_id: str | None, + image_generation_config_id_override: int | None = None, ) -> Any: """Compile the multi-agent graph, serving from cache when key components are stable.""" @@ -91,7 +92,7 @@ async def build_agent_with_cache( # the key, otherwise a hit will leak state across threads. Bump the schema # version when the component list changes shape. cache_key = stable_hash( - "multi-agent-v1", + "multi-agent-v2", config_id, thread_id, user_id, @@ -109,6 +110,10 @@ async def build_agent_with_cache( system_prompt_hash(final_system_prompt), max_input_tokens, sorted(disabled_tools) if disabled_tools else None, + # Bound into the generate_image subagent tool at construction time, so it + # must key the compiled-agent cache to avoid leaking one automation's + # image model into another with the same config_id/search_space. + image_generation_config_id_override, ) return await get_cache().get_or_build(cache_key, builder=_build) diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/factory.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/factory.py index 8451b3b7d..44529d243 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/factory.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/runtime/factory.py @@ -62,8 +62,14 @@ async def create_multi_agent_chat_deep_agent( mentioned_document_ids: list[int] | None = None, anon_session_id: str | None = None, filesystem_selection: FilesystemSelection | None = None, + image_generation_config_id: int | None = None, ): - """Deep agent with SurfSense tools/middleware; registry route subagents behind ``task`` when enabled.""" + """Deep agent with SurfSense tools/middleware; registry route subagents behind ``task`` when enabled. + + ``image_generation_config_id`` overrides the search space's image model for + this invocation (used by automations to run on their captured model). When + ``None``, the ``generate_image`` tool resolves the live search-space pref. + """ _t_agent_total = time.perf_counter() apply_litellm_prompt_caching(llm, agent_config=agent_config, thread_id=thread_id) @@ -129,6 +135,9 @@ async def create_multi_agent_chat_deep_agent( "available_document_types": available_document_types, "max_input_tokens": _max_input_tokens, "llm": llm, + # Per-invocation image model override (automations run on their captured + # model). Reaches the generate_image subagent tool via subagent_dependencies. + "image_generation_config_id_override": image_generation_config_id, } _t0 = time.perf_counter() @@ -285,6 +294,7 @@ async def create_multi_agent_chat_deep_agent( mcp_tools_by_agent=mcp_tools_by_agent, disabled_tools=disabled_tools, config_id=config_id, + image_generation_config_id_override=image_generation_config_id, ) _perf_log.info( "[create_agent] Middleware stack + graph compiled in %.3fs", diff --git a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py index 173d302e5..8e841c1e9 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py +++ b/surfsense_backend/app/agents/multi_agent_chat/main_agent/tools/automation/create.py @@ -32,7 +32,8 @@ from app.agents.multi_agent_chat.subagents.shared.hitl.approvals.self_gated impo ) from app.automations.schemas.api import AutomationCreate from app.automations.services.automation import AutomationService -from app.db import User, async_session_maker +from app.automations.services.model_policy import get_automation_model_eligibility +from app.db import SearchSpace, User, async_session_maker from app.utils.content_utils import extract_text_content from .prompt import build_draft_prompt @@ -98,6 +99,27 @@ def create_create_automation_tool( declined. Acknowledge once and stop — do NOT retry or pitch variants without a fresh user request. """ + # --- 0. Eligibility gate (fail fast, before drafting + HITL) --- + # Automations may only use premium or BYOK models. Check up front so we + # don't make the user draft + approve a card that can't be saved. + async with async_session_maker() as session: + search_space = await session.get(SearchSpace, search_space_id) + if search_space is None: + return { + "status": "error", + "message": "search space not found in this session", + } + eligibility = get_automation_model_eligibility(search_space) + if not eligibility["allowed"]: + reasons = " ".join(v["reason"] for v in eligibility["violations"]) + return { + "status": "error", + "message": ( + f"{reasons} Update the search space's model settings to a " + "premium or your own (BYOK) model, then try again." + ), + } + # --- 1. Draft via sub-LLM --- prompt = build_draft_prompt(search_space_id=search_space_id, intent=intent) try: diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py index f170a35db..094371760 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/generate_image.py @@ -63,8 +63,14 @@ def _get_global_image_gen_config(config_id: int) -> dict | None: def create_generate_image_tool( search_space_id: int, db_session: AsyncSession, + image_generation_config_id_override: int | None = None, ): - """Create ``generate_image`` with bound search space; DB work uses a per-call session.""" + """Create ``generate_image`` with bound search space; DB work uses a per-call session. + + ``image_generation_config_id_override``: when set (automations running on a + captured model), use this config id instead of reading the search space's + live ``image_generation_config_id``. + """ del db_session # use a fresh per-call session, see below @tool @@ -108,19 +114,27 @@ def create_generate_image_tool( # task's session is shared across every tool; without isolation, # autoflushes from a concurrent writer poison this tool too. async with shielded_async_session() as session: - result = await session.execute( - select(SearchSpace).filter(SearchSpace.id == search_space_id) - ) - search_space = result.scalars().first() - if not search_space: - return _failed( - {"error": "Search space not found"}, - error="Search space not found", + if image_generation_config_id_override is not None: + # Automation run: use the captured image model, insulated from + # later search-space changes. No search-space read needed. + config_id = ( + image_generation_config_id_override or IMAGE_GEN_AUTO_MODE_ID ) + else: + result = await session.execute( + select(SearchSpace).filter(SearchSpace.id == search_space_id) + ) + search_space = result.scalars().first() + if not search_space: + return _failed( + {"error": "Search space not found"}, + error="Search space not found", + ) - config_id = ( - search_space.image_generation_config_id or IMAGE_GEN_AUTO_MODE_ID - ) + config_id = ( + search_space.image_generation_config_id + or IMAGE_GEN_AUTO_MODE_ID + ) # Build generation kwargs # NOTE: size, quality, and style are intentionally NOT passed. diff --git a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/index.py b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/index.py index 5f76f1d52..ddfcbd7fb 100644 --- a/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/index.py +++ b/surfsense_backend/app/agents/multi_agent_chat/subagents/builtins/deliverables/tools/index.py @@ -51,5 +51,8 @@ def load_tools( create_generate_image_tool( search_space_id=d["search_space_id"], db_session=d["db_session"], + image_generation_config_id_override=d.get( + "image_generation_config_id_override" + ), ), ] diff --git a/surfsense_backend/app/automations/actions/agent_task/dependencies.py b/surfsense_backend/app/automations/actions/agent_task/dependencies.py index 79107cd65..e3736cc95 100644 --- a/surfsense_backend/app/automations/actions/agent_task/dependencies.py +++ b/surfsense_backend/app/automations/actions/agent_task/dependencies.py @@ -8,6 +8,12 @@ from typing import Any from langgraph.checkpoint.memory import InMemorySaver from sqlalchemy.ext.asyncio import AsyncSession +from app.automations.services.model_policy import ( + AutomationModelPolicyError, + assert_automation_models_billable, + assert_models_billable, +) +from app.db import SearchSpace from app.tasks.chat.streaming.flows.shared.llm_bundle import load_llm_bundle from app.tasks.chat.streaming.flows.shared.pre_stream_setup import ( setup_connector_and_firecrawl, @@ -33,17 +39,48 @@ async def build_dependencies( *, session: AsyncSession, search_space_id: int, + agent_llm_id: int | None = None, + image_generation_config_id: int | None = None, + vision_llm_config_id: int | None = None, ) -> AgentDependencies: """Load the LLM bundle, connector service, and a per-invoke in-memory checkpointer. - Uses the search space's default LLM config (``config_id=-1``). Per-step - model overrides land in a future iteration alongside the ``model`` param. + Resolves the agent LLM from the automation's *captured* model snapshot + (``agent_llm_id``) so runs are insulated from later chat/search-space model + changes. The model policy is enforced here as a runtime backstop: a captured + model that is no longer billable (e.g. a premium global config was removed) + fails the run clearly instead of silently consuming a free model. + + When ``agent_llm_id`` is ``None`` (no captured snapshot — defensive fallback), + fall back to the live search space's ``agent_llm_id`` and validate that. """ + if agent_llm_id is not None: + try: + assert_models_billable( + agent_llm_id=agent_llm_id, + image_generation_config_id=image_generation_config_id, + vision_llm_config_id=vision_llm_config_id, + ) + except AutomationModelPolicyError as exc: + raise DependencyError(str(exc)) from exc + resolved_agent_llm_id = agent_llm_id or 0 + else: + search_space = await session.get(SearchSpace, search_space_id) + if search_space is None: + raise DependencyError(f"search space {search_space_id} not found") + try: + assert_automation_models_billable(search_space) + except AutomationModelPolicyError as exc: + raise DependencyError(str(exc)) from exc + resolved_agent_llm_id = search_space.agent_llm_id or 0 + llm, agent_config, err = await load_llm_bundle( - session, config_id=-1, search_space_id=search_space_id + session, + config_id=resolved_agent_llm_id, + search_space_id=search_space_id, ) if err is not None or llm is None: - raise DependencyError(err or "failed to load default LLM config") + raise DependencyError(err or "failed to load agent LLM config") connector_service, firecrawl_api_key = await setup_connector_and_firecrawl( session, search_space_id=search_space_id diff --git a/surfsense_backend/app/automations/actions/agent_task/invoke.py b/surfsense_backend/app/automations/actions/agent_task/invoke.py index fa02d263f..b645b748d 100644 --- a/surfsense_backend/app/automations/actions/agent_task/invoke.py +++ b/surfsense_backend/app/automations/actions/agent_task/invoke.py @@ -147,6 +147,9 @@ async def run_agent_task( deps = await build_dependencies( session=agent_session, search_space_id=ctx.search_space_id, + agent_llm_id=ctx.agent_llm_id, + image_generation_config_id=ctx.image_generation_config_id, + vision_llm_config_id=ctx.vision_llm_config_id, ) agent = await create_multi_agent_chat_deep_agent( @@ -161,6 +164,7 @@ async def run_agent_task( firecrawl_api_key=deps.firecrawl_api_key, thread_visibility=ChatVisibility.PRIVATE, mentioned_document_ids=mentioned_document_ids, + image_generation_config_id=ctx.image_generation_config_id, ) agent_query, runtime_context = await _resolve_mention_context( diff --git a/surfsense_backend/app/automations/actions/types.py b/surfsense_backend/app/automations/actions/types.py index 2c4ffad8d..453721a43 100644 --- a/surfsense_backend/app/automations/actions/types.py +++ b/surfsense_backend/app/automations/actions/types.py @@ -20,6 +20,12 @@ class ActionContext: step_id: str search_space_id: int creator_user_id: UUID | None + # Captured model snapshot from the automation definition (``definition.models``), + # resolved per run instead of the live search space. ``None`` falls back to the + # search space's current prefs (defensive; should not happen post-capture). + agent_llm_id: int | None = None + image_generation_config_id: int | None = None + vision_llm_config_id: int | None = None ActionHandler = Callable[[dict[str, Any]], Awaitable[Any]] diff --git a/surfsense_backend/app/automations/api/automation.py b/surfsense_backend/app/automations/api/automation.py index b67f0af09..911ae57a6 100644 --- a/surfsense_backend/app/automations/api/automation.py +++ b/surfsense_backend/app/automations/api/automation.py @@ -3,6 +3,7 @@ from __future__ import annotations from fastapi import APIRouter, Depends, Query, status +from pydantic import BaseModel from app.automations.schemas.api import ( AutomationCreate, @@ -16,6 +17,17 @@ from app.automations.services import AutomationService, get_automation_service router = APIRouter() +class ModelEligibilityViolation(BaseModel): + kind: str + config_id: int | None + reason: str + + +class ModelEligibility(BaseModel): + allowed: bool + violations: list[ModelEligibilityViolation] + + @router.post( "/automations", response_model=AutomationDetail, @@ -47,6 +59,23 @@ async def list_automations( ) +@router.get("/automations/model-eligibility", response_model=ModelEligibility) +async def get_automation_model_eligibility( + search_space_id: int = Query(...), + service: AutomationService = Depends(get_automation_service), +) -> ModelEligibility: + """Report whether a search space's models are billable for automations. + + Used by the frontend to gate creation: automations may only use premium + global models or user BYOK models (free models and Auto mode are blocked). + + NOTE: declared before ``/automations/{automation_id}`` so the literal path + isn't captured by the int-typed ``{automation_id}`` route. + """ + result = await service.model_eligibility(search_space_id=search_space_id) + return ModelEligibility.model_validate(result) + + @router.get("/automations/{automation_id}", response_model=AutomationDetail) async def get_automation( automation_id: int, diff --git a/surfsense_backend/app/automations/runtime/executor.py b/surfsense_backend/app/automations/runtime/executor.py index 6a33ab314..da249d8e5 100644 --- a/surfsense_backend/app/automations/runtime/executor.py +++ b/surfsense_backend/app/automations/runtime/executor.py @@ -9,7 +9,10 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.automations.actions.types import ActionContext from app.automations.persistence.enums.run_status import RunStatus from app.automations.persistence.models.run import AutomationRun -from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.schemas.definition.envelope import ( + AutomationDefinition, + AutomationModels, +) from app.automations.schemas.definition.plan_step import PlanStep from app.automations.templating import build_run_context @@ -47,7 +50,7 @@ async def execute_run(session: AsyncSession, run_id: int) -> None: for step in definition.plan: template_ctx = _build_template_ctx(run, step_outputs) - action_ctx = _build_action_ctx(session, run, step) + action_ctx = _build_action_ctx(session, run, step, definition.models) result = await execute_step( step=step, template_context=template_ctx, @@ -82,7 +85,7 @@ async def _run_on_failure( return template_ctx = _build_template_ctx(run, step_outputs={}) for step in definition.execution.on_failure: - action_ctx = _build_action_ctx(session, run, step) + action_ctx = _build_action_ctx(session, run, step, definition.models) result = await execute_step( step=step, template_context=template_ctx, @@ -117,7 +120,10 @@ def _build_template_ctx( def _build_action_ctx( - session: AsyncSession, run: AutomationRun, step: PlanStep + session: AsyncSession, + run: AutomationRun, + step: PlanStep, + models: AutomationModels | None, ) -> ActionContext: automation = run.automation return ActionContext( @@ -126,4 +132,9 @@ def _build_action_ctx( step_id=step.step_id, search_space_id=automation.search_space_id, creator_user_id=automation.created_by_user_id, + agent_llm_id=models.agent_llm_id if models else None, + image_generation_config_id=( + models.image_generation_config_id if models else None + ), + vision_llm_config_id=models.vision_llm_config_id if models else None, ) diff --git a/surfsense_backend/app/automations/schemas/definition/__init__.py b/surfsense_backend/app/automations/schemas/definition/__init__.py index 3fb0a739b..72404264e 100644 --- a/surfsense_backend/app/automations/schemas/definition/__init__.py +++ b/surfsense_backend/app/automations/schemas/definition/__init__.py @@ -2,7 +2,7 @@ from __future__ import annotations -from .envelope import AutomationDefinition +from .envelope import AutomationDefinition, AutomationModels from .execution import Execution from .inputs import Inputs from .metadata import Metadata @@ -11,6 +11,7 @@ from .trigger_spec import TriggerSpec __all__ = [ "AutomationDefinition", + "AutomationModels", "Execution", "Inputs", "Metadata", diff --git a/surfsense_backend/app/automations/schemas/definition/envelope.py b/surfsense_backend/app/automations/schemas/definition/envelope.py index f919b2abb..7ca55b1ce 100644 --- a/surfsense_backend/app/automations/schemas/definition/envelope.py +++ b/surfsense_backend/app/automations/schemas/definition/envelope.py @@ -11,6 +11,21 @@ from .plan_step import PlanStep from .trigger_spec import TriggerSpec +class AutomationModels(BaseModel): + """Captured model profile for an automation. + + Snapshotted from the search space's preferences at create time so runs are + insulated from later chat/search-space model changes. Config-id conventions + match the shared scheme (``0`` Auto, ``< 0`` global, ``> 0`` BYOK). + """ + + model_config = ConfigDict(extra="forbid") + + agent_llm_id: int = 0 + image_generation_config_id: int = 0 + vision_llm_config_id: int = 0 + + class AutomationDefinition(BaseModel): """Top-level shape of an automation.""" @@ -24,3 +39,7 @@ class AutomationDefinition(BaseModel): plan: list[PlanStep] = Field(..., min_length=1) execution: Execution = Field(default_factory=Execution) metadata: Metadata = Field(default_factory=Metadata) + # Captured server-side at create() and preserved across update(); resolved + # at runtime instead of the live search space. Optional so drafts/builder + # payloads validate without it. + models: AutomationModels | None = None diff --git a/surfsense_backend/app/automations/services/__init__.py b/surfsense_backend/app/automations/services/__init__.py index 597aca98a..904a3413a 100644 --- a/surfsense_backend/app/automations/services/__init__.py +++ b/surfsense_backend/app/automations/services/__init__.py @@ -3,14 +3,26 @@ from __future__ import annotations from .automation import AutomationService, get_automation_service +from .model_policy import ( + AutomationModelPolicyError, + assert_automation_models_billable, + assert_models_billable, + get_automation_model_eligibility, + get_model_eligibility, +) from .run import RunService, get_run_service from .trigger import TriggerService, get_trigger_service __all__ = [ + "AutomationModelPolicyError", "AutomationService", "RunService", "TriggerService", + "assert_automation_models_billable", + "assert_models_billable", + "get_automation_model_eligibility", "get_automation_service", + "get_model_eligibility", "get_run_service", "get_trigger_service", ] diff --git a/surfsense_backend/app/automations/services/automation.py b/surfsense_backend/app/automations/services/automation.py index 0d2937e0e..6c602d886 100644 --- a/surfsense_backend/app/automations/services/automation.py +++ b/surfsense_backend/app/automations/services/automation.py @@ -18,9 +18,15 @@ from app.automations.schemas.api import ( AutomationUpdate, TriggerCreate, ) +from app.automations.schemas.definition.envelope import AutomationModels +from app.automations.services.model_policy import ( + AutomationModelPolicyError, + assert_automation_models_billable, + get_automation_model_eligibility, +) from app.automations.triggers import get_trigger from app.automations.triggers.schedule import compute_next_fire_at -from app.db import Permission, User, get_async_session +from app.db import Permission, SearchSpace, User, get_async_session from app.users import current_active_user from app.utils.rbac import check_permission @@ -37,6 +43,16 @@ class AutomationService: await self._authorize( payload.search_space_id, Permission.AUTOMATIONS_CREATE.value ) + search_space = await self._assert_models_billable(payload.search_space_id) + + # Snapshot the search space's current (already-validated) model prefs onto + # the definition so runs are insulated from later chat/search-space model + # changes. Captured ids are guaranteed billable by the check above. + payload.definition.models = AutomationModels( + agent_llm_id=search_space.agent_llm_id or 0, + image_generation_config_id=search_space.image_generation_config_id or 0, + vision_llm_config_id=search_space.vision_llm_config_id or 0, + ) automation = Automation( search_space_id=payload.search_space_id, @@ -105,9 +121,15 @@ class AutomationService: if "status" in data: automation.status = data["status"] if "definition" in data: - automation.definition = patch.definition.model_dump( - mode="json", by_alias=True - ) + new_def = patch.definition.model_dump(mode="json", by_alias=True) + # Preserve the captured model snapshot across edits so a definition + # change never silently re-binds the automation to the current chat + # model selection. Backend-managed; survives whether or not the + # client round-trips ``models``. + existing_models = (automation.definition or {}).get("models") + if existing_models is not None: + new_def["models"] = existing_models + automation.definition = new_def automation.version += 1 await self.session.commit() @@ -143,6 +165,40 @@ class AutomationService: ) return automation + async def model_eligibility(self, *, search_space_id: int) -> dict: + """Return whether a search space's models are billable for automations. + + ``{"allowed": bool, "violations": [{kind, config_id, reason}, ...]}``. + """ + await self._authorize(search_space_id, Permission.AUTOMATIONS_READ.value) + search_space = await self.session.get(SearchSpace, search_space_id) + if search_space is None: + raise HTTPException( + status_code=404, detail=f"search space {search_space_id} not found" + ) + return get_automation_model_eligibility(search_space) + + async def _assert_models_billable(self, search_space_id: int) -> SearchSpace: + """Reject creation when the search space's models aren't billable. + + Automations may only use premium global models or user BYOK models; free + global models and Auto mode are blocked. Mirrors the runtime backstop in + ``agent_task`` so users can't save an automation that would fail to run. + + Returns the loaded :class:`SearchSpace` so the caller can capture its + model prefs without a second DB read. + """ + search_space = await self.session.get(SearchSpace, search_space_id) + if search_space is None: + raise HTTPException( + status_code=404, detail=f"search space {search_space_id} not found" + ) + try: + assert_automation_models_billable(search_space) + except AutomationModelPolicyError as exc: + raise HTTPException(status_code=422, detail=str(exc)) from exc + return search_space + async def _authorize(self, search_space_id: int, permission: str) -> None: await check_permission( self.session, diff --git a/surfsense_backend/app/automations/services/model_policy.py b/surfsense_backend/app/automations/services/model_policy.py new file mode 100644 index 000000000..88e9d5f28 --- /dev/null +++ b/surfsense_backend/app/automations/services/model_policy.py @@ -0,0 +1,173 @@ +"""Model-billing policy for automations. + +Automations run unattended, so every run must be **billable**: it may only use +either a premium global model (``billing_tier == "premium"``) or a user-provided +BYOK model (a positive config id pointing at a per-user/per-space DB row). Free +global models and Auto mode are blocked, because Auto can dispatch to a free +deployment and free models aren't metered in premium credits. + +Config id conventions (shared across chat / image / vision): +- ``id == 0`` → Auto mode (``AUTO_MODE_ID`` / ``IMAGE_GEN_AUTO_MODE_ID`` / + ``VISION_AUTO_MODE_ID``). Blocked. +- ``id < 0`` → global YAML/OpenRouter config. Allowed only if premium. +- ``id > 0`` → user BYOK DB row. Always allowed. + +This module is the single source of truth used by both creation-time enforcement +(``AutomationService.create`` and the ``create_automation`` chat tool) and the +runtime backstop (``agent_task`` dependencies). +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Literal + +if TYPE_CHECKING: + from app.db import SearchSpace + +ModelKind = Literal["llm", "image", "vision"] + +_KIND_LABEL: dict[ModelKind, str] = { + "llm": "agent LLM", + "image": "image generation model", + "vision": "vision model", +} + + +def _is_premium_global(kind: ModelKind, config_id: int) -> bool: + """Return True if a negative (global) config id is a premium tier model.""" + from app.config import config as app_config + + cfg: dict | None = None + if kind == "llm": + from app.agents.new_chat.llm_config import load_global_llm_config_by_id + + cfg = load_global_llm_config_by_id(config_id) + elif kind == "image": + cfg = next( + ( + c + for c in app_config.GLOBAL_IMAGE_GEN_CONFIGS + if c.get("id") == config_id + ), + None, + ) + else: # vision + cfg = next( + ( + c + for c in app_config.GLOBAL_VISION_LLM_CONFIGS + if c.get("id") == config_id + ), + None, + ) + + if not cfg: + return False + return str(cfg.get("billing_tier", "free")).lower() == "premium" + + +def _classify(kind: ModelKind, config_id: int | None) -> tuple[bool, str]: + """Classify a resolved config id as allowed or blocked. + + Returns ``(allowed, reason)``; ``reason`` is empty when allowed. + """ + label = _KIND_LABEL[kind] + + if config_id is None or config_id == 0: + return ( + False, + f"The {label} is set to Auto mode. Automations require an explicit " + "premium model or your own (BYOK) model so every run is billable.", + ) + + if config_id > 0: + # Positive id → user-owned BYOK config. Always allowed. + return True, "" + + # Negative id → global config. Allowed only if premium. + if _is_premium_global(kind, config_id): + return True, "" + + return ( + False, + f"The {label} is a free model. Automations can only use premium models " + "or your own (BYOK) models so every run is billable.", + ) + + +def get_model_eligibility( + *, + agent_llm_id: int | None, + image_generation_config_id: int | None, + vision_llm_config_id: int | None, +) -> dict: + """Return ``{"allowed": bool, "violations": [...]}`` for explicit config ids. + + The ID-based core shared by both the search-space path (creation/eligibility) + and the captured-snapshot path (runtime backstop). Each violation is + ``{"kind", "config_id", "reason"}``. + """ + checks: list[tuple[ModelKind, int | None]] = [ + ("llm", agent_llm_id), + ("image", image_generation_config_id), + ("vision", vision_llm_config_id), + ] + + violations: list[dict] = [] + for kind, config_id in checks: + allowed, reason = _classify(kind, config_id) + if not allowed: + violations.append({"kind": kind, "config_id": config_id, "reason": reason}) + + return {"allowed": not violations, "violations": violations} + + +def get_automation_model_eligibility(search_space: SearchSpace) -> dict: + """Return ``{"allowed": bool, "violations": [...]}`` for a search space. + + Used by the eligibility endpoint and the chat tool's early check. Thin + wrapper over :func:`get_model_eligibility`. + """ + return get_model_eligibility( + agent_llm_id=search_space.agent_llm_id, + image_generation_config_id=search_space.image_generation_config_id, + vision_llm_config_id=search_space.vision_llm_config_id, + ) + + +class AutomationModelPolicyError(Exception): + """Raised when a search space's models are not billable for automations.""" + + def __init__(self, violations: list[dict]) -> None: + self.violations = violations + reasons = "; ".join(v["reason"] for v in violations) + super().__init__( + reasons or "Automations require premium or BYOK models for all model slots." + ) + + +def assert_models_billable( + *, + agent_llm_id: int | None, + image_generation_config_id: int | None, + vision_llm_config_id: int | None, +) -> None: + """Raise :class:`AutomationModelPolicyError` if any explicit id is not billable. + + The ID-based core used by the runtime backstop against an automation's + captured model snapshot. + """ + result = get_model_eligibility( + agent_llm_id=agent_llm_id, + image_generation_config_id=image_generation_config_id, + vision_llm_config_id=vision_llm_config_id, + ) + if not result["allowed"]: + raise AutomationModelPolicyError(result["violations"]) + + +def assert_automation_models_billable(search_space: SearchSpace) -> None: + """Raise :class:`AutomationModelPolicyError` if any model slot is not billable.""" + result = get_automation_model_eligibility(search_space) + if not result["allowed"]: + raise AutomationModelPolicyError(result["violations"]) diff --git a/surfsense_backend/tests/unit/automations/actions/agent_task/test_dependencies.py b/surfsense_backend/tests/unit/automations/actions/agent_task/test_dependencies.py new file mode 100644 index 000000000..ac20b2608 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/actions/agent_task/test_dependencies.py @@ -0,0 +1,174 @@ +"""Lock the runtime model-policy backstop in ``build_dependencies``. + +Automations resolve their LLM from the *captured* ``agent_llm_id`` snapshot (so +runs are insulated from later chat/search-space model changes), and the model +policy is re-checked at run time so a captured model that is no longer billable +fails the run clearly. When no snapshot is present, resolution falls back to the +live search space. +""" + +from __future__ import annotations + +from types import SimpleNamespace +from typing import Any + +import pytest + +import app.automations.actions.agent_task.dependencies as deps_mod +from app.automations.actions.agent_task.dependencies import ( + DependencyError, + build_dependencies, +) +from app.automations.services.model_policy import AutomationModelPolicyError + +pytestmark = pytest.mark.unit + + +class _FakeSession: + """Minimal async session whose ``get`` returns a preset search space.""" + + def __init__(self, search_space: Any) -> None: + self._search_space = search_space + + async def get(self, _model: Any, _pk: int) -> Any: + return self._search_space + + +@pytest.fixture +def patched_side_effects(monkeypatch: pytest.MonkeyPatch): + """Stub the connector setup + checkpointer so only policy/LLM logic runs.""" + + async def _fake_setup(_session, *, search_space_id): + return (SimpleNamespace(name="connector"), "fc-key") + + monkeypatch.setattr(deps_mod, "setup_connector_and_firecrawl", _fake_setup) + return None + + +async def test_build_dependencies_resolves_captured_agent_llm_id( + monkeypatch: pytest.MonkeyPatch, patched_side_effects +) -> None: + """The bundle loads with the *captured* ``agent_llm_id``, not the live search space.""" + captured: dict[str, Any] = {} + + async def _fake_load(_session, *, config_id, search_space_id): + captured["config_id"] = config_id + captured["search_space_id"] = search_space_id + return (SimpleNamespace(name="llm"), SimpleNamespace(name="agent_config"), None) + + monkeypatch.setattr(deps_mod, "load_llm_bundle", _fake_load) + # Captured path validates the explicit ids; passes for this test. + monkeypatch.setattr(deps_mod, "assert_models_billable", lambda **_kw: None) + # A different value on the live search space proves we ignore it when a + # snapshot is supplied. + monkeypatch.setattr( + deps_mod, + "assert_automation_models_billable", + lambda _ss: pytest.fail("search-space policy should not run on captured path"), + ) + + search_space = SimpleNamespace(agent_llm_id=-99) + result = await build_dependencies( + session=_FakeSession(search_space), + search_space_id=42, + agent_llm_id=-7, + image_generation_config_id=5, + vision_llm_config_id=-1, + ) + + assert captured == {"config_id": -7, "search_space_id": 42} + assert result.llm.name == "llm" + assert result.firecrawl_api_key == "fc-key" + + +async def test_build_dependencies_validates_captured_ids( + monkeypatch: pytest.MonkeyPatch, patched_side_effects +) -> None: + """The captured ids (not the search space) are what gets policy-checked.""" + seen: dict[str, Any] = {} + + def _capture(**kwargs): + seen.update(kwargs) + + monkeypatch.setattr(deps_mod, "assert_models_billable", _capture) + + async def _fake_load(_session, *, config_id, search_space_id): + return (SimpleNamespace(name="llm"), SimpleNamespace(name="agent_config"), None) + + monkeypatch.setattr(deps_mod, "load_llm_bundle", _fake_load) + + await build_dependencies( + session=_FakeSession(SimpleNamespace(agent_llm_id=0)), + search_space_id=42, + agent_llm_id=-7, + image_generation_config_id=5, + vision_llm_config_id=-1, + ) + + assert seen == { + "agent_llm_id": -7, + "image_generation_config_id": 5, + "vision_llm_config_id": -1, + } + + +async def test_build_dependencies_raises_on_captured_policy_violation( + monkeypatch: pytest.MonkeyPatch, patched_side_effects +) -> None: + """A blocked captured model raises ``DependencyError`` so the step fails clearly.""" + + def _raise(**_kw): + raise AutomationModelPolicyError( + [{"kind": "image", "config_id": -2, "reason": "free model"}] + ) + + monkeypatch.setattr(deps_mod, "assert_models_billable", _raise) + monkeypatch.setattr( + deps_mod, + "load_llm_bundle", + lambda *a, **k: pytest.fail("load_llm_bundle should not be called"), + ) + + with pytest.raises(DependencyError): + await build_dependencies( + session=_FakeSession(SimpleNamespace(agent_llm_id=-7)), + search_space_id=42, + agent_llm_id=-7, + image_generation_config_id=-2, + vision_llm_config_id=-1, + ) + + +async def test_build_dependencies_falls_back_to_search_space( + monkeypatch: pytest.MonkeyPatch, patched_side_effects +) -> None: + """With no captured snapshot, resolve + validate the live search space.""" + captured: dict[str, Any] = {} + + async def _fake_load(_session, *, config_id, search_space_id): + captured["config_id"] = config_id + return (SimpleNamespace(name="llm"), SimpleNamespace(name="agent_config"), None) + + monkeypatch.setattr(deps_mod, "load_llm_bundle", _fake_load) + monkeypatch.setattr(deps_mod, "assert_automation_models_billable", lambda _ss: None) + monkeypatch.setattr( + deps_mod, + "assert_models_billable", + lambda **_kw: pytest.fail("captured policy should not run on fallback path"), + ) + + search_space = SimpleNamespace(agent_llm_id=-7) + result = await build_dependencies( + session=_FakeSession(search_space), search_space_id=42 + ) + + assert captured == {"config_id": -7} + assert result.llm.name == "llm" + + +async def test_build_dependencies_raises_when_search_space_missing( + patched_side_effects, +) -> None: + """A missing search space (fallback path) surfaces as a ``DependencyError``.""" + with pytest.raises(DependencyError): + await build_dependencies(session=_FakeSession(None), search_space_id=999) diff --git a/surfsense_backend/tests/unit/automations/runtime/test_executor_action_ctx.py b/surfsense_backend/tests/unit/automations/runtime/test_executor_action_ctx.py new file mode 100644 index 000000000..d7e3c4a0c --- /dev/null +++ b/surfsense_backend/tests/unit/automations/runtime/test_executor_action_ctx.py @@ -0,0 +1,59 @@ +"""Lock that the executor propagates the captured model snapshot into the +``ActionContext``, so runs resolve their own model (insulated from chat / +search-space changes) and not the live search space. +""" + +from __future__ import annotations + +from types import SimpleNamespace +from typing import cast + +import pytest +from sqlalchemy.ext.asyncio import AsyncSession + +from app.automations.runtime.executor import _build_action_ctx +from app.automations.schemas.definition.envelope import AutomationModels +from app.automations.schemas.definition.plan_step import PlanStep + +pytestmark = pytest.mark.unit + + +def _run() -> SimpleNamespace: + return SimpleNamespace( + id=1, + automation=SimpleNamespace(search_space_id=42, created_by_user_id="u-1"), + ) + + +def test_build_action_ctx_propagates_captured_models() -> None: + """``definition.models`` flows onto the ActionContext model fields.""" + models = AutomationModels( + agent_llm_id=-1, + image_generation_config_id=5, + vision_llm_config_id=-1, + ) + ctx = _build_action_ctx( + cast(AsyncSession, None), + _run(), + PlanStep(step_id="s1", action="agent_task"), + models, + ) + + assert ctx.search_space_id == 42 + assert ctx.agent_llm_id == -1 + assert ctx.image_generation_config_id == 5 + assert ctx.vision_llm_config_id == -1 + + +def test_build_action_ctx_none_models_leaves_fields_none() -> None: + """No captured snapshot → model fields are None (defensive fallback path).""" + ctx = _build_action_ctx( + cast(AsyncSession, None), + _run(), + PlanStep(step_id="s1", action="agent_task"), + None, + ) + + assert ctx.agent_llm_id is None + assert ctx.image_generation_config_id is None + assert ctx.vision_llm_config_id is None diff --git a/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py b/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py index d7b392a1d..25e193ffa 100644 --- a/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py +++ b/surfsense_backend/tests/unit/automations/schemas/definition/test_envelope.py @@ -5,7 +5,10 @@ from __future__ import annotations import pytest from pydantic import ValidationError -from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.schemas.definition.envelope import ( + AutomationDefinition, + AutomationModels, +) from app.automations.schemas.definition.plan_step import PlanStep pytestmark = pytest.mark.unit @@ -27,6 +30,34 @@ def test_automation_definition_accepts_minimal_valid_input_with_sensible_default assert definition.goal is None assert definition.inputs is None assert definition.triggers == [] + # ``models`` is optional (populated server-side at create()). + assert definition.models is None + + +def test_automation_definition_models_round_trip() -> None: + """The captured ``models`` snapshot survives a model_dump/validate round-trip.""" + definition = AutomationDefinition( + name="Daily digest", + plan=[PlanStep(step_id="s1", action="agent_task")], + models=AutomationModels( + agent_llm_id=-1, + image_generation_config_id=5, + vision_llm_config_id=-1, + ), + ) + + dumped = definition.model_dump(mode="json", by_alias=True) + assert dumped["models"] == { + "agent_llm_id": -1, + "image_generation_config_id": 5, + "vision_llm_config_id": -1, + } + + restored = AutomationDefinition.model_validate(dumped) + assert restored.models is not None + assert restored.models.agent_llm_id == -1 + assert restored.models.image_generation_config_id == 5 + assert restored.models.vision_llm_config_id == -1 def test_automation_definition_rejects_unknown_top_level_field() -> None: diff --git a/surfsense_backend/tests/unit/automations/services/__init__.py b/surfsense_backend/tests/unit/automations/services/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/surfsense_backend/tests/unit/automations/services/test_automation_service_policy.py b/surfsense_backend/tests/unit/automations/services/test_automation_service_policy.py new file mode 100644 index 000000000..d81302380 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/services/test_automation_service_policy.py @@ -0,0 +1,236 @@ +"""Lock creation-time model-policy enforcement in ``AutomationService``. + +Creation (REST + manual builder) rejects search spaces whose models aren't +billable for automations with HTTP 422, mirroring the runtime backstop. These +tests isolate the new ``_assert_models_billable`` / ``model_eligibility`` paths +without touching the DB commit. +""" + +from __future__ import annotations + +from types import SimpleNamespace +from typing import Any + +import pytest +from fastapi import HTTPException + +import app.automations.services.automation as automation_mod +from app.automations.schemas.api import AutomationCreate, AutomationUpdate +from app.automations.schemas.definition.envelope import AutomationDefinition +from app.automations.schemas.definition.plan_step import PlanStep +from app.automations.services.automation import AutomationService +from app.automations.services.model_policy import AutomationModelPolicyError + +pytestmark = pytest.mark.unit + + +class _FakeSession: + def __init__(self, search_space: Any) -> None: + self._search_space = search_space + self.added: list[Any] = [] + self.commits = 0 + + async def get(self, _model: Any, _pk: int) -> Any: + return self._search_space + + def add(self, obj: Any) -> None: + self.added.append(obj) + + async def commit(self) -> None: + self.commits += 1 + + +def _service(search_space: Any) -> AutomationService: + return AutomationService( + session=_FakeSession(search_space), user=SimpleNamespace(id="u-1") + ) + + +def _definition(**kwargs: Any) -> AutomationDefinition: + return AutomationDefinition( + name="A", + plan=[PlanStep(step_id="s1", action="agent_task")], + **kwargs, + ) + + +async def test_assert_models_billable_raises_422_on_violation( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A blocked model maps the policy error to HTTP 422.""" + + def _raise(_ss): + raise AutomationModelPolicyError( + [{"kind": "llm", "config_id": 0, "reason": "Auto mode"}] + ) + + monkeypatch.setattr(automation_mod, "assert_automation_models_billable", _raise) + + service = _service(SimpleNamespace(agent_llm_id=0)) + with pytest.raises(HTTPException) as exc_info: + await service._assert_models_billable(1) + + assert exc_info.value.status_code == 422 + + +async def test_assert_models_billable_raises_404_when_missing( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A missing search space is a 404, not a policy error.""" + monkeypatch.setattr( + automation_mod, "assert_automation_models_billable", lambda _ss: None + ) + + service = _service(None) + with pytest.raises(HTTPException) as exc_info: + await service._assert_models_billable(999) + + assert exc_info.value.status_code == 404 + + +async def test_assert_models_billable_returns_search_space_when_ok( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """When the policy accepts, the loaded search space is returned for reuse.""" + monkeypatch.setattr( + automation_mod, "assert_automation_models_billable", lambda _ss: None + ) + + search_space = SimpleNamespace(agent_llm_id=-1) + service = _service(search_space) + assert await service._assert_models_billable(1) is search_space + + +async def test_create_injects_captured_models_from_search_space( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """create() snapshots the search space's model prefs onto the definition.""" + monkeypatch.setattr( + automation_mod, "assert_automation_models_billable", lambda _ss: None + ) + + async def _noop_authorize(self, *_a, **_k): + return None + + monkeypatch.setattr(AutomationService, "_authorize", _noop_authorize) + + async def _return_added(self, _aid): + return self.session.added[-1] + + monkeypatch.setattr(AutomationService, "_get_with_triggers_or_raise", _return_added) + + search_space = SimpleNamespace( + agent_llm_id=-1, + image_generation_config_id=5, + vision_llm_config_id=-1, + ) + service = _service(search_space) + payload = AutomationCreate( + search_space_id=1, + name="A", + definition=_definition(), + ) + + automation = await service.create(payload) + + assert automation.definition["models"] == { + "agent_llm_id": -1, + "image_generation_config_id": 5, + "vision_llm_config_id": -1, + } + + +async def test_create_treats_unset_prefs_as_auto_zero( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """``None`` search-space prefs are captured as ``0`` (Auto) ids.""" + monkeypatch.setattr( + automation_mod, "assert_automation_models_billable", lambda _ss: None + ) + + async def _noop_authorize(self, *_a, **_k): + return None + + monkeypatch.setattr(AutomationService, "_authorize", _noop_authorize) + + async def _return_added(self, _aid): + return self.session.added[-1] + + monkeypatch.setattr(AutomationService, "_get_with_triggers_or_raise", _return_added) + + search_space = SimpleNamespace( + agent_llm_id=None, + image_generation_config_id=None, + vision_llm_config_id=None, + ) + service = _service(search_space) + payload = AutomationCreate(search_space_id=1, name="A", definition=_definition()) + + automation = await service.create(payload) + + assert automation.definition["models"] == { + "agent_llm_id": 0, + "image_generation_config_id": 0, + "vision_llm_config_id": 0, + } + + +async def test_update_preserves_captured_models( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A definition edit carries over the previously captured ``models``.""" + captured = { + "agent_llm_id": -1, + "image_generation_config_id": 5, + "vision_llm_config_id": -1, + } + existing = SimpleNamespace( + search_space_id=1, + definition={"name": "A", "plan": [], "models": captured}, + version=3, + ) + + async def _noop_authorize(self, *_a, **_k): + return None + + async def _return_existing(self, _aid): + return existing + + monkeypatch.setattr(AutomationService, "_authorize", _noop_authorize) + monkeypatch.setattr( + AutomationService, "_get_with_triggers_or_raise", _return_existing + ) + + service = _service(SimpleNamespace()) + # The incoming patch definition has no ``models`` (frontend strips it). + patch = AutomationUpdate(definition=_definition()) + + result = await service.update(7, patch) + + assert result.definition["models"] == captured + assert result.version == 4 + + +async def test_model_eligibility_authorizes_and_returns_payload( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """``model_eligibility`` checks read access then returns the eligibility dict.""" + authorized: dict[str, Any] = {} + + async def _fake_check_permission(_session, _user, ss_id, permission, _msg): + authorized["search_space_id"] = ss_id + authorized["permission"] = permission + + monkeypatch.setattr(automation_mod, "check_permission", _fake_check_permission) + monkeypatch.setattr( + automation_mod, + "get_automation_model_eligibility", + lambda _ss: {"allowed": False, "violations": [{"kind": "image"}]}, + ) + + service = _service(SimpleNamespace(agent_llm_id=-2)) + result = await service.model_eligibility(search_space_id=5) + + assert result == {"allowed": False, "violations": [{"kind": "image"}]} + assert authorized["search_space_id"] == 5 + assert authorized["permission"] == "automations:read" diff --git a/surfsense_backend/tests/unit/automations/services/test_model_policy.py b/surfsense_backend/tests/unit/automations/services/test_model_policy.py new file mode 100644 index 000000000..2a471b4e9 --- /dev/null +++ b/surfsense_backend/tests/unit/automations/services/test_model_policy.py @@ -0,0 +1,196 @@ +"""Lock the automation model-billing policy. + +Automations may only run on billable models: premium global configs +(``billing_tier == "premium"``) or user BYOK configs (positive id). Free +globals and Auto mode (id == 0 / None) are blocked. These tests pin that rule +across all three model slots (chat LLM, image, vision). +""" + +from __future__ import annotations + +from types import SimpleNamespace + +import pytest + +import app.automations.services.model_policy as model_policy +from app.automations.services.model_policy import ( + AutomationModelPolicyError, + assert_automation_models_billable, + assert_models_billable, + get_automation_model_eligibility, + get_model_eligibility, +) + +pytestmark = pytest.mark.unit + + +def _search_space(*, llm: int | None, image: int | None, vision: int | None): + """Minimal stand-in for the ``SearchSpace`` ORM row the policy reads.""" + return SimpleNamespace( + agent_llm_id=llm, + image_generation_config_id=image, + vision_llm_config_id=vision, + ) + + +@pytest.fixture +def patched_globals(monkeypatch: pytest.MonkeyPatch): + """Stub the global config sources the policy consults for negative ids. + + Negative ids: -1 is premium, -2 is free, for each of llm/image/vision. + """ + llm_configs = { + -1: {"id": -1, "billing_tier": "premium"}, + -2: {"id": -2, "billing_tier": "free"}, + } + monkeypatch.setattr( + "app.agents.new_chat.llm_config.load_global_llm_config_by_id", + lambda cid: llm_configs.get(cid), + ) + + from app.config import config as app_config + + monkeypatch.setattr( + app_config, + "GLOBAL_IMAGE_GEN_CONFIGS", + [ + {"id": -1, "billing_tier": "premium"}, + {"id": -2, "billing_tier": "free"}, + ], + raising=False, + ) + monkeypatch.setattr( + app_config, + "GLOBAL_VISION_LLM_CONFIGS", + [ + {"id": -1, "billing_tier": "premium"}, + {"id": -2, "billing_tier": "free"}, + ], + raising=False, + ) + return None + + +@pytest.mark.parametrize("kind", ["llm", "image", "vision"]) +def test_byok_positive_id_is_allowed(kind: str, patched_globals) -> None: + """A positive config id is a user-owned BYOK model — always billable.""" + allowed, reason = model_policy._classify(kind, 7) + assert allowed is True + assert reason == "" + + +@pytest.mark.parametrize("kind", ["llm", "image", "vision"]) +@pytest.mark.parametrize("config_id", [0, None]) +def test_auto_mode_is_blocked(kind: str, config_id, patched_globals) -> None: + """Auto mode (id 0) and an unset slot (None) are blocked.""" + allowed, reason = model_policy._classify(kind, config_id) + assert allowed is False + assert "Auto mode" in reason + + +@pytest.mark.parametrize("kind", ["llm", "image", "vision"]) +def test_premium_global_is_allowed(kind: str, patched_globals) -> None: + """A negative (global) id with premium billing tier is allowed.""" + allowed, reason = model_policy._classify(kind, -1) + assert allowed is True + assert reason == "" + + +@pytest.mark.parametrize("kind", ["llm", "image", "vision"]) +def test_free_global_is_blocked(kind: str, patched_globals) -> None: + """A negative (global) id with a free billing tier is blocked.""" + allowed, reason = model_policy._classify(kind, -2) + assert allowed is False + assert "free model" in reason + + +@pytest.mark.parametrize("kind", ["llm", "image", "vision"]) +def test_unknown_global_id_is_blocked(kind: str, patched_globals) -> None: + """A negative id that resolves to no config is treated as not premium.""" + allowed, _ = model_policy._classify(kind, -999) + assert allowed is False + + +def test_eligibility_all_billable(patched_globals) -> None: + """Premium LLM + BYOK image + premium vision → allowed, no violations.""" + search_space = _search_space(llm=-1, image=5, vision=-1) + result = get_automation_model_eligibility(search_space) + assert result == {"allowed": True, "violations": []} + + +def test_eligibility_reports_each_violation(patched_globals) -> None: + """A free LLM, Auto image, and free vision each produce a violation.""" + search_space = _search_space(llm=-2, image=0, vision=-2) + result = get_automation_model_eligibility(search_space) + + assert result["allowed"] is False + kinds = {v["kind"] for v in result["violations"]} + assert kinds == {"llm", "image", "vision"} + # config_id is echoed back for the UI / settings deep-link. + by_kind = {v["kind"]: v["config_id"] for v in result["violations"]} + assert by_kind == {"llm": -2, "image": 0, "vision": -2} + + +def test_assert_raises_with_violations(patched_globals) -> None: + """``assert_automation_models_billable`` raises when any slot is blocked.""" + search_space = _search_space(llm=0, image=5, vision=-1) + with pytest.raises(AutomationModelPolicyError) as exc_info: + assert_automation_models_billable(search_space) + + assert len(exc_info.value.violations) == 1 + assert exc_info.value.violations[0]["kind"] == "llm" + + +def test_assert_passes_when_all_billable(patched_globals) -> None: + """No exception when every slot is premium or BYOK.""" + search_space = _search_space(llm=3, image=-1, vision=4) + assert assert_automation_models_billable(search_space) is None + + +# --- ID-based core (used by the runtime backstop against captured snapshots) --- + + +def test_get_model_eligibility_all_billable(patched_globals) -> None: + """Premium LLM + BYOK image + premium vision (explicit ids) → allowed.""" + result = get_model_eligibility( + agent_llm_id=-1, image_generation_config_id=5, vision_llm_config_id=-1 + ) + assert result == {"allowed": True, "violations": []} + + +def test_get_model_eligibility_reports_each_violation(patched_globals) -> None: + """Free LLM, Auto image, free vision (explicit ids) each produce a violation.""" + result = get_model_eligibility( + agent_llm_id=-2, image_generation_config_id=0, vision_llm_config_id=-2 + ) + assert result["allowed"] is False + by_kind = {v["kind"]: v["config_id"] for v in result["violations"]} + assert by_kind == {"llm": -2, "image": 0, "vision": -2} + + +def test_assert_models_billable_raises(patched_globals) -> None: + """``assert_models_billable`` raises when any explicit id is blocked.""" + with pytest.raises(AutomationModelPolicyError) as exc_info: + assert_models_billable( + agent_llm_id=0, image_generation_config_id=5, vision_llm_config_id=-1 + ) + assert len(exc_info.value.violations) == 1 + assert exc_info.value.violations[0]["kind"] == "llm" + + +def test_assert_models_billable_passes(patched_globals) -> None: + """No exception when every explicit id is premium or BYOK.""" + assert ( + assert_models_billable( + agent_llm_id=3, image_generation_config_id=-1, vision_llm_config_id=4 + ) + is None + ) + + +def test_search_space_wrapper_delegates_to_core(patched_globals) -> None: + """The search-space wrapper produces the same result as the ID core.""" + search_space = _search_space(llm=-2, image=0, vision=-2) + assert get_automation_model_eligibility(search_space) == get_model_eligibility( + agent_llm_id=-2, image_generation_config_id=0, vision_llm_config_id=-2 + ) diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx index 756221d38..6bbe55ec9 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/automations-content.tsx @@ -1,6 +1,8 @@ "use client"; import { ShieldAlert } from "lucide-react"; +import { useAutomationModelEligibility } from "@/hooks/use-automation-model-eligibility"; import { useAutomations } from "@/hooks/use-automations"; +import { AutomationModelGateAlert } from "./components/automation-model-gate-alert"; import { AutomationsEmptyState } from "./components/automations-empty-state"; import { AutomationsHeader } from "./components/automations-header"; import { AutomationsTable } from "./components/automations-table"; @@ -22,6 +24,18 @@ interface AutomationsContentProps { export function AutomationsContent({ searchSpaceId }: AutomationsContentProps) { const { automations, total, loading, error } = useAutomations(); const perms = useAutomationPermissions(); + // Gate creation on billable models (premium/BYOK). Only meaningful for + // users who can create; the eligibility query loads in parallel. + const { data: eligibility, isLoading: eligibilityLoading } = useAutomationModelEligibility( + perms.canCreate ? searchSpaceId : undefined + ); + const modelViolations = eligibility?.violations ?? []; + // Disable create CTAs while loading (avoid a flash of enabled buttons) and + // when the resolved models aren't billable. + const createDisabled = perms.canCreate && (eligibilityLoading || modelViolations.length > 0); + const disabledReason = eligibilityLoading + ? "Checking model eligibility…" + : modelViolations[0]?.reason; if (perms.loading) { // Permissions gate the entire page; defer everything until we know. @@ -77,7 +91,11 @@ export function AutomationsContent({ searchSpaceId }: AutomationsContentProps) { canCreate={perms.canCreate} showCreateCta={false} /> - + ); } @@ -89,7 +107,12 @@ export function AutomationsContent({ searchSpaceId }: AutomationsContentProps) { total={total} loading={loading} canCreate={perms.canCreate} + createDisabled={createDisabled} + disabledReason={disabledReason} /> + {modelViolations.length > 0 && ( + + )} = { + llm: "Agent model", + image: "Image model", + vision: "Vision model", +}; + +/** + * Warns that the search space's models aren't billable for automations. + * + * Automations may only use premium global models or your own (BYOK) models — + * free models and Auto mode are blocked so every run is metered in premium + * credits. Surfaced wherever a user can start creating an automation. + */ +export function AutomationModelGateAlert({ + searchSpaceId, + violations, + className, +}: AutomationModelGateAlertProps) { + if (violations.length === 0) return null; + + return ( + + + Automations need a premium or your own model + +

+ Automations run unattended, so every run must use a premium model or your own (BYOK) + model. Update these in your model settings, then create your automation. +

+
    + {violations.map((violation) => ( +
  • + + {KIND_LABEL[violation.kind]} + + — {violation.reason} +
  • + ))} +
+
+
+ ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx index cc54c5e94..ee7dadce6 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-empty-state.tsx @@ -2,10 +2,14 @@ import { MessageSquarePlus, SquarePen, Workflow } from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; +import type { ModelEligibilityViolation } from "@/contracts/types/automation.types"; +import { AutomationModelGateAlert } from "./automation-model-gate-alert"; interface AutomationsEmptyStateProps { searchSpaceId: number; canCreate: boolean; + /** Model slots that block creation (free/Auto). Empty when eligible. */ + modelViolations?: ModelEligibilityViolation[]; } /** @@ -14,7 +18,13 @@ interface AutomationsEmptyStateProps { * "new automation" form. We surface the chat path explicitly so users * don't go hunting for an "add" button that doesn't exist. */ -export function AutomationsEmptyState({ searchSpaceId, canCreate }: AutomationsEmptyStateProps) { +export function AutomationsEmptyState({ + searchSpaceId, + canCreate, + modelViolations = [], +}: AutomationsEmptyStateProps) { + const modelsBlocked = modelViolations.length > 0; + return (
@@ -26,20 +36,26 @@ export function AutomationsEmptyState({ searchSpaceId, canCreate }: AutomationsE SurfSense drafts the automation for your approval.

{canCreate ? ( -
- - -
+ modelsBlocked ? ( +
+ +
+ ) : ( +
+ + +
+ ) ) : (

You don't have permission to create automations in this search space. diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx index 8d5fab033..308eaccfb 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/automations-header.tsx @@ -1,7 +1,9 @@ "use client"; import { MessageSquarePlus, SquarePen } from "lucide-react"; import Link from "next/link"; +import type { ReactNode } from "react"; import { Button } from "@/components/ui/button"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; interface AutomationsHeaderProps { searchSpaceId: number; @@ -14,8 +16,18 @@ interface AutomationsHeaderProps { * there to avoid a duplicate button. */ showCreateCta?: boolean; + /** + * Disable the create CTAs when the search space's models aren't billable + * for automations (free/Auto). When set, a tooltip explains why and the + * buttons render disabled rather than as links. + */ + createDisabled?: boolean; + disabledReason?: string; } +const DEFAULT_DISABLED_REASON = + "Automations need a premium or your own (BYOK) model. Update your model settings to enable."; + /** * Page header: title + count + "Create via chat" CTA. Creation is intent-driven * (the create_automation tool runs inside chat with a HITL approval card), so @@ -27,6 +39,8 @@ export function AutomationsHeader({ loading, canCreate, showCreateCta = true, + createDisabled = false, + disabledReason, }: AutomationsHeaderProps) { return (

@@ -40,20 +54,70 @@ export function AutomationsHeader({
{canCreate && showCreateCta && (
- - + {createDisabled ? ( + <> + } + label="Create manually" + reason={disabledReason ?? DEFAULT_DISABLED_REASON} + /> + } + label="Create via chat" + reason={disabledReason ?? DEFAULT_DISABLED_REASON} + /> + + ) : ( + <> + + + + )}
)}
); } + +function DisabledCta({ + icon, + label, + reason, + variant, +}: { + icon: ReactNode; + label: string; + reason: string; + variant?: "outline"; +}) { + return ( + + + {/* aria-disabled (not `disabled`) keeps the button focusable so the + tooltip is reachable by hover and keyboard; onClick is a no-op. */} + + + {reason} + + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx index 1fd37cd3d..f3323da61 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/components/builder/automation-builder-form.tsx @@ -15,6 +15,7 @@ import { import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Spinner } from "@/components/ui/spinner"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { type Automation, automationCreateRequest, @@ -45,6 +46,12 @@ interface AutomationBuilderFormProps { searchSpaceId: number; /** Required in edit mode; seeds the form and trigger reconciliation. */ automation?: Automation; + /** + * When set (create mode only), the search space's models aren't billable + * for automations. Submit is disabled with this reason as the tooltip; the + * orchestrator also renders the full gate alert above the form. + */ + submitDisabledReason?: string; } type Mode = "form" | "json"; @@ -66,6 +73,7 @@ export function AutomationBuilderForm({ mode, searchSpaceId, automation, + submitDisabledReason, }: AutomationBuilderFormProps) { const router = useRouter(); const { mutateAsync: createAutomation } = useAtomValue(createAutomationMutationAtom); @@ -273,6 +281,8 @@ export function AutomationBuilderForm({ } const submitLabel = mode === "edit" ? "Save changes" : "Create automation"; + // Only gate creation; editing an existing automation isn't blocked here. + const submitBlocked = mode === "create" && !!submitDisabledReason; return (
@@ -390,15 +400,39 @@ export function AutomationBuilderForm({ - + {submitBlocked ? ( + + + {/* aria-disabled keeps the button focusable so the tooltip is + reachable by hover and keyboard; onClick is a no-op. */} + + + {submitDisabledReason} + + ) : ( + + )}
); diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx index 0c983aaf8..a40b0a31b 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx @@ -1,5 +1,7 @@ "use client"; import { ShieldAlert } from "lucide-react"; +import { useAutomationModelEligibility } from "@/hooks/use-automation-model-eligibility"; +import { AutomationModelGateAlert } from "../components/automation-model-gate-alert"; import { AutomationBuilderForm } from "../components/builder/automation-builder-form"; import { useAutomationPermissions } from "../hooks/use-automation-permissions"; import { AutomationNewHeader } from "./components/automation-new-header"; @@ -16,6 +18,9 @@ interface AutomationNewContentProps { */ export function AutomationNewContent({ searchSpaceId }: AutomationNewContentProps) { const perms = useAutomationPermissions(); + const { data: eligibility, isLoading: eligibilityLoading } = useAutomationModelEligibility( + perms.canCreate ? searchSpaceId : undefined + ); if (perms.loading) { return
; @@ -33,10 +38,22 @@ export function AutomationNewContent({ searchSpaceId }: AutomationNewContentProp ); } + const modelViolations = eligibility?.violations ?? []; + const submitDisabledReason = eligibilityLoading + ? "Checking model eligibility…" + : modelViolations[0]?.reason; + return ( <> - + {modelViolations.length > 0 && ( + + )} + ); } diff --git a/surfsense_web/atoms/new-llm-config/new-llm-config-mutation.atoms.ts b/surfsense_web/atoms/new-llm-config/new-llm-config-mutation.atoms.ts index f4577a7a9..476d89d4c 100644 --- a/surfsense_web/atoms/new-llm-config/new-llm-config-mutation.atoms.ts +++ b/surfsense_web/atoms/new-llm-config/new-llm-config-mutation.atoms.ts @@ -118,6 +118,12 @@ export const updateLLMPreferencesMutationAtom = atomWithMutation((get) => { cacheKeys.newLLMConfigs.preferences(Number(searchSpaceId)), (old: Record | undefined) => ({ ...old, ...request.data }) ); + // Automation eligibility is derived from these model preferences + // (agent/image/vision). Invalidate it so the automations gate alert + // reflects the new selection without a manual refresh. + queryClient.invalidateQueries({ + queryKey: cacheKeys.automations.modelEligibility(Number(searchSpaceId)), + }); }, onError: (error: Error) => { toast.error(error.message || "Failed to update LLM preferences"); diff --git a/surfsense_web/contracts/types/automation.types.ts b/surfsense_web/contracts/types/automation.types.ts index a93249735..a1f2bd382 100644 --- a/surfsense_web/contracts/types/automation.types.ts +++ b/surfsense_web/contracts/types/automation.types.ts @@ -60,6 +60,15 @@ export const inputs = z.object({ }); export type Inputs = z.infer; +// Captured model snapshot (server-managed). Set at create time and preserved +// across edits so runs are insulated from later chat/search-space model changes. +export const automationModels = z.object({ + agent_llm_id: z.number().int().default(0), + image_generation_config_id: z.number().int().default(0), + vision_llm_config_id: z.number().int().default(0), +}); +export type AutomationModels = z.infer; + export const automationDefinition = z.object({ schema_version: z.string().default("1.0"), name: z.string().min(1).max(200), @@ -69,6 +78,7 @@ export const automationDefinition = z.object({ plan: z.array(planStep).min(1), execution: execution.default(execution.parse({})), metadata: metadata.default(metadata.parse({})), + models: automationModels.nullable().optional(), }); export type AutomationDefinition = z.infer; @@ -191,3 +201,23 @@ export const runListParams = z.object({ offset: z.number().int().min(0).default(0), }); export type RunListParams = z.infer; + +// ============================================================================= +// Model eligibility — mirror app/automations/api/automation.py (ModelEligibility) +// ============================================================================= + +export const modelEligibilityKind = z.enum(["llm", "image", "vision"]); +export type ModelEligibilityKind = z.infer; + +export const modelEligibilityViolation = z.object({ + kind: modelEligibilityKind, + config_id: z.number().nullable(), + reason: z.string(), +}); +export type ModelEligibilityViolation = z.infer; + +export const modelEligibility = z.object({ + allowed: z.boolean(), + violations: z.array(modelEligibilityViolation), +}); +export type ModelEligibility = z.infer; diff --git a/surfsense_web/hooks/use-automation-model-eligibility.ts b/surfsense_web/hooks/use-automation-model-eligibility.ts new file mode 100644 index 000000000..c9bb8b1ea --- /dev/null +++ b/surfsense_web/hooks/use-automation-model-eligibility.ts @@ -0,0 +1,25 @@ +"use client"; +import { useQuery } from "@tanstack/react-query"; +import type { ModelEligibility } from "@/contracts/types/automation.types"; +import { automationsApiService } from "@/lib/apis/automations-api.service"; +import { cacheKeys } from "@/lib/query-client/cache-keys"; + +/** + * Whether the search space's configured models are billable for automations. + * + * Automations may only run on premium global models or user-provided (BYOK) + * models; free global models and Auto mode are blocked so every run is metered + * in premium credits. Creation surfaces use this to gate their CTAs before the + * user invests effort drafting an automation that can't be saved. + * + * Keyed by search space id (not the jotai "current scope" atom) so it can be + * used on the create route as well as the list page. + */ +export function useAutomationModelEligibility(searchSpaceId: number | undefined) { + return useQuery({ + queryKey: cacheKeys.automations.modelEligibility(searchSpaceId ?? 0), + queryFn: () => automationsApiService.getModelEligibility(searchSpaceId as number), + enabled: !!searchSpaceId, + staleTime: 60_000, + }); +} diff --git a/surfsense_web/lib/apis/automations-api.service.ts b/surfsense_web/lib/apis/automations-api.service.ts index ebe72bea5..baaf08799 100644 --- a/surfsense_web/lib/apis/automations-api.service.ts +++ b/surfsense_web/lib/apis/automations-api.service.ts @@ -6,6 +6,7 @@ import { automationCreateRequest, automationListResponse, automationUpdateRequest, + modelEligibility, type RunListParams, run, runListResponse, @@ -62,6 +63,13 @@ class AutomationsApiService { return baseApiService.delete(`${BASE}/${automationId}`); }; + // Whether the search space's models are billable for automations (premium + // global or BYOK). Used to gate creation surfaces before submit. + getModelEligibility = async (searchSpaceId: number) => { + const qs = new URLSearchParams({ search_space_id: String(searchSpaceId) }); + return baseApiService.get(`${BASE}/model-eligibility?${qs.toString()}`, modelEligibility); + }; + // ---- Triggers (sub-resource) -------------------------------------------- addTrigger = async (automationId: number, request: TriggerCreateRequest) => { diff --git a/surfsense_web/lib/query-client/cache-keys.ts b/surfsense_web/lib/query-client/cache-keys.ts index 35724cf94..49bcd8d0e 100644 --- a/surfsense_web/lib/query-client/cache-keys.ts +++ b/surfsense_web/lib/query-client/cache-keys.ts @@ -134,5 +134,7 @@ export const cacheKeys = { ["automations", "runs", automationId, limit, offset] as const, run: (automationId: number, runId: number) => ["automations", "runs", automationId, runId] as const, + modelEligibility: (searchSpaceId: number) => + ["automations", "model-eligibility", searchSpaceId] as const, }, }; From 43c66008e4284ce1d80614854c15b1c1b5d9f6f8 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Fri, 29 May 2026 03:30:22 -0700 Subject: [PATCH 161/198] fix(llm-role-manager): synchronize local state with preferences updates - Added useEffect to sync local assignments state with preferences when they change, ensuring the UI reflects the latest data. - Updated state initialization to use null instead of empty strings for clarity in role assignments. - Adjusted role assignment handling to correctly manage "unassigned" values and preserve Auto mode configuration during updates. --- .../components/settings/llm-role-manager.tsx | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/surfsense_web/components/settings/llm-role-manager.tsx b/surfsense_web/components/settings/llm-role-manager.tsx index 17370b8f2..9af9eec27 100644 --- a/surfsense_web/components/settings/llm-role-manager.tsx +++ b/surfsense_web/components/settings/llm-role-manager.tsx @@ -11,7 +11,7 @@ import { RefreshCw, ScanEye, } from "lucide-react"; -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; import { globalImageGenConfigsAtom, @@ -135,18 +135,39 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { const { mutateAsync: updatePreferences } = useAtomValue(updateLLMPreferencesMutationAtom); - const [assignments, setAssignments] = useState(() => ({ - agent_llm_id: preferences.agent_llm_id ?? "", - document_summary_llm_id: preferences.document_summary_llm_id ?? "", - image_generation_config_id: preferences.image_generation_config_id ?? "", - vision_llm_config_id: preferences.vision_llm_config_id ?? "", + const [assignments, setAssignments] = useState>(() => ({ + agent_llm_id: preferences.agent_llm_id ?? null, + document_summary_llm_id: preferences.document_summary_llm_id ?? null, + image_generation_config_id: preferences.image_generation_config_id ?? null, + vision_llm_config_id: preferences.vision_llm_config_id ?? null, })); + // Sync local state when preferences load/change. Without this, the selects + // stay on their initial (often empty) value while the query is in flight, + // so a saved assignment — including Auto mode (id 0) — never appears. + useEffect(() => { + setAssignments({ + agent_llm_id: preferences.agent_llm_id ?? null, + document_summary_llm_id: preferences.document_summary_llm_id ?? null, + image_generation_config_id: preferences.image_generation_config_id ?? null, + vision_llm_config_id: preferences.vision_llm_config_id ?? null, + }); + }, [ + preferences.agent_llm_id, + preferences.document_summary_llm_id, + preferences.image_generation_config_id, + preferences.vision_llm_config_id, + ]); + const [savingRole, setSavingRole] = useState(null); const handleRoleAssignment = useCallback( async (prefKey: string, configId: string) => { - const value = configId === "unassigned" ? "" : parseInt(configId); + // "unassigned" clears the role (null). Every other option — including + // Auto mode, whose config id is 0 — must be sent as-is. Using a falsy + // check here (e.g. `value || undefined`) would drop id 0 and silently + // fail to persist Auto mode. + const value = configId === "unassigned" ? null : Number(configId); setAssignments((prev) => ({ ...prev, [prefKey]: value })); setSavingRole(prefKey); @@ -154,7 +175,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { try { await updatePreferences({ search_space_id: searchSpaceId, - data: { [prefKey]: value || undefined }, + data: { [prefKey]: value }, }); toast.success("Role assignment updated"); } finally { @@ -325,7 +346,7 @@ export function LLMRoleManager({ searchSpaceId }: LLMRoleManagerProps) { Configuration onChange(Number(v))}> + + {selected ? ( + + {getProviderIcon(selected.provider)} + {selected.name} + + ) : ( + + )} + + + {premium.length > 0 ? ( + + Premium + {premium.map((option) => ( + + ))} + + ) : null} + {premium.length > 0 && byok.length > 0 ? : null} + {byok.length > 0 ? ( + + Your models + {byok.map((option) => ( + + ))} + + ) : null} + + + + ); +}); + +function ModelOption({ + option, + badge, +}: { + option: EligibleModelOption; + badge: "Premium" | "BYOK"; +}) { + return ( + + + {getProviderIcon(option.provider)} + {option.name} + {badge} + + + ); +} diff --git a/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx index a40b0a31b..7dc117aab 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/automations/new/automation-new-content.tsx @@ -1,7 +1,5 @@ "use client"; import { ShieldAlert } from "lucide-react"; -import { useAutomationModelEligibility } from "@/hooks/use-automation-model-eligibility"; -import { AutomationModelGateAlert } from "../components/automation-model-gate-alert"; import { AutomationBuilderForm } from "../components/builder/automation-builder-form"; import { useAutomationPermissions } from "../hooks/use-automation-permissions"; import { AutomationNewHeader } from "./components/automation-new-header"; @@ -15,12 +13,13 @@ interface AutomationNewContentProps { * who can't create don't even see the form; same panel as the detail page's * access-denied state for consistency. The builder defaults to the friendly * form with a raw-JSON escape hatch. + * + * Model eligibility is no longer gated here — the builder's own model pickers + * list eligible (premium/BYOK) models, surface a per-slot notice when none + * exist, and block submit until each slot resolves. */ export function AutomationNewContent({ searchSpaceId }: AutomationNewContentProps) { const perms = useAutomationPermissions(); - const { data: eligibility, isLoading: eligibilityLoading } = useAutomationModelEligibility( - perms.canCreate ? searchSpaceId : undefined - ); if (perms.loading) { return
; @@ -38,22 +37,10 @@ export function AutomationNewContent({ searchSpaceId }: AutomationNewContentProp ); } - const modelViolations = eligibility?.violations ?? []; - const submitDisabledReason = eligibilityLoading - ? "Checking model eligibility…" - : modelViolations[0]?.reason; - return ( <> - {modelViolations.length > 0 && ( - - )} - + ); } diff --git a/surfsense_web/components/tool-ui/automation/create-automation.tsx b/surfsense_web/components/tool-ui/automation/create-automation.tsx index b152f9055..1bd20bbde 100644 --- a/surfsense_web/components/tool-ui/automation/create-automation.tsx +++ b/surfsense_web/components/tool-ui/automation/create-automation.tsx @@ -5,6 +5,10 @@ import { useAtomValue } from "jotai"; import { AlertCircle, CornerDownLeftIcon, ExternalLink, Pencil, Workflow } from "lucide-react"; import Link from "next/link"; import { useCallback, useEffect, useMemo, useState } from "react"; +import { + AutomationModelFields, + type AutomationModelSelection, +} from "@/app/dashboard/[search_space_id]/automations/components/builder/automation-model-fields"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; import { JsonView } from "@/components/json-view"; import { TextShimmerLoader } from "@/components/prompt-kit/loader"; @@ -12,6 +16,7 @@ import { Button } from "@/components/ui/button"; import { automationCreateRequest } from "@/contracts/types/automation.types"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl"; +import { useAutomationEligibleModels } from "@/hooks/use-automation-eligible-models"; import { AutomationDraftPreview } from "./automation-draft-preview"; const editArgsSchema = automationCreateRequest.omit({ search_space_id: true }); @@ -94,17 +99,71 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { const effectiveArgs = pendingEdits ?? args; const draft = useMemo(() => extractDraft(effectiveArgs), [effectiveArgs]); + // Per-automation model selection. The card always supplies models (chosen + // here, not snapshotted from the search space), so Approve dispatches an + // `edit` decision carrying `definition.models`. + const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); + const eligibleModels = useAutomationEligibleModels(); + const [modelSelection, setModelSelection] = useState({ + agentLlmId: 0, + imageConfigId: 0, + visionConfigId: 0, + }); + // Resolve each slot during render: an explicit pick wins, else the eligible + // default. No effect seeds async hook data into state. + const resolvedModels = useMemo( + () => ({ + agentLlmId: modelSelection.agentLlmId || eligibleModels.llm.defaultId || 0, + imageConfigId: modelSelection.imageConfigId || eligibleModels.image.defaultId || 0, + visionConfigId: modelSelection.visionConfigId || eligibleModels.vision.defaultId || 0, + }), + [ + modelSelection, + eligibleModels.llm.defaultId, + eligibleModels.image.defaultId, + eligibleModels.vision.defaultId, + ] + ); + const modelsResolved = + resolvedModels.agentLlmId !== 0 && + resolvedModels.imageConfigId !== 0 && + resolvedModels.visionConfigId !== 0; + const handleApprove = useCallback(() => { - if (phase !== "pending" || !canApprove || isEditing) return; + if (phase !== "pending" || !canApprove || isEditing || !modelsResolved) return; setProcessing(); + const baseArgs = pendingEdits ?? args; + const baseDefinition = (baseArgs.definition ?? {}) as Record; + const mergedArgs = { + ...baseArgs, + definition: { + ...baseDefinition, + models: { + agent_llm_id: resolvedModels.agentLlmId, + image_generation_config_id: resolvedModels.imageConfigId, + vision_llm_config_id: resolvedModels.visionConfigId, + }, + }, + }; onDecision({ - type: pendingEdits ? "edit" : "approve", + type: "edit", edited_action: { name: interruptData.action_requests[0]?.name ?? "create_automation", - args: pendingEdits ?? args, + args: mergedArgs, }, }); - }, [phase, canApprove, isEditing, setProcessing, onDecision, interruptData, args, pendingEdits]); + }, [ + phase, + canApprove, + isEditing, + modelsResolved, + setProcessing, + onDecision, + interruptData, + args, + pendingEdits, + resolvedModels, + ]); const handleReject = useCallback(() => { if (phase !== "pending" || !canReject || isEditing) return; @@ -193,10 +252,24 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { {phase === "pending" && !isEditing && ( <> +
+
+

Models

+ setModelSelection((prev) => ({ ...prev, ...patch }))} + /> +
{canApprove && ( - diff --git a/surfsense_web/hooks/use-automation-eligible-models.ts b/surfsense_web/hooks/use-automation-eligible-models.ts new file mode 100644 index 000000000..e74994221 --- /dev/null +++ b/surfsense_web/hooks/use-automation-eligible-models.ts @@ -0,0 +1,150 @@ +"use client"; + +import { useAtomValue } from "jotai"; +import { useMemo } from "react"; +import { + globalImageGenConfigsAtom, + imageGenConfigsAtom, +} from "@/atoms/image-gen-config/image-gen-config-query.atoms"; +import { + globalNewLLMConfigsAtom, + llmPreferencesAtom, + newLLMConfigsAtom, +} from "@/atoms/new-llm-config/new-llm-config-query.atoms"; +import { + globalVisionLLMConfigsAtom, + visionLLMConfigsAtom, +} from "@/atoms/vision-llm-config/vision-llm-config-query.atoms"; + +/** + * A single model the user may pick for an automation slot. + */ +export interface EligibleModelOption { + id: number; + name: string; + /** Underlying model identifier (e.g. `gpt-4o`); shown as secondary text. */ + modelName: string; + provider: string; + /** `true` for user BYOK configs (positive ids), `false` for premium globals. */ + isBYOK: boolean; +} + +export interface EligibleModelKind { + options: EligibleModelOption[]; + /** Default selection: the search-space pref when eligible, else first option. */ + defaultId: number | null; + /** O(1) id → option lookup for trigger labels (avoids per-render `.find()`). */ + byId: Map; +} + +export interface AutomationEligibleModels { + llm: EligibleModelKind; + image: EligibleModelKind; + vision: EligibleModelKind; + isLoading: boolean; +} + +interface GlobalConfigLike { + id: number; + name: string; + model_name: string; + provider: string; + is_premium?: boolean; + is_auto_mode?: boolean; +} + +interface UserConfigLike { + id: number; + name: string; + model_name: string; + provider: string; +} + +/** + * Build the eligible option list for one model kind: premium globals + * (`is_premium === true`, never Auto mode) followed by all BYOK configs. + */ +function buildKind( + globals: GlobalConfigLike[] | undefined, + byok: UserConfigLike[] | undefined, + prefId: number | null | undefined +): EligibleModelKind { + const premiumGlobals: EligibleModelOption[] = (globals ?? []) + .filter((c) => c.is_premium === true && !c.is_auto_mode) + .map((c) => ({ + id: c.id, + name: c.name, + modelName: c.model_name, + provider: c.provider, + isBYOK: false, + })); + + const byokOptions: EligibleModelOption[] = (byok ?? []).map((c) => ({ + id: c.id, + name: c.name, + modelName: c.model_name, + provider: c.provider, + isBYOK: true, + })); + + const options = [...premiumGlobals, ...byokOptions]; + const byId = new Map(options.map((o) => [o.id, o])); + + let defaultId: number | null = null; + if (prefId != null && byId.has(prefId)) { + defaultId = prefId; + } else if (options.length > 0) { + defaultId = options[0].id; + } + + return { options, defaultId, byId }; +} + +/** + * Lists the LLM / image / vision models that are eligible for automations + * (premium globals + user BYOK — never free globals or Auto mode), with a + * default selection seeded from the search space's role preferences. + * + * Everything is derived during render from the existing config query atoms; + * there are no effects, so option lists/maps keep stable references. + */ +export function useAutomationEligibleModels(): AutomationEligibleModels { + const { data: llmUserConfigs, isLoading: llmUserLoading } = useAtomValue(newLLMConfigsAtom); + const { data: llmGlobalConfigs, isLoading: llmGlobalLoading } = + useAtomValue(globalNewLLMConfigsAtom); + const { data: preferences, isLoading: prefsLoading } = useAtomValue(llmPreferencesAtom); + const { data: imageGlobalConfigs, isLoading: imageGlobalLoading } = + useAtomValue(globalImageGenConfigsAtom); + const { data: imageUserConfigs, isLoading: imageUserLoading } = useAtomValue(imageGenConfigsAtom); + const { data: visionGlobalConfigs, isLoading: visionGlobalLoading } = useAtomValue( + globalVisionLLMConfigsAtom + ); + const { data: visionUserConfigs, isLoading: visionUserLoading } = + useAtomValue(visionLLMConfigsAtom); + + const llm = useMemo( + () => buildKind(llmGlobalConfigs, llmUserConfigs, preferences?.agent_llm_id), + [llmGlobalConfigs, llmUserConfigs, preferences?.agent_llm_id] + ); + + const image = useMemo( + () => buildKind(imageGlobalConfigs, imageUserConfigs, preferences?.image_generation_config_id), + [imageGlobalConfigs, imageUserConfigs, preferences?.image_generation_config_id] + ); + + const vision = useMemo( + () => buildKind(visionGlobalConfigs, visionUserConfigs, preferences?.vision_llm_config_id), + [visionGlobalConfigs, visionUserConfigs, preferences?.vision_llm_config_id] + ); + + const isLoading = + llmUserLoading || + llmGlobalLoading || + prefsLoading || + imageGlobalLoading || + imageUserLoading || + visionGlobalLoading || + visionUserLoading; + + return useMemo(() => ({ llm, image, vision, isLoading }), [llm, image, vision, isLoading]); +} diff --git a/surfsense_web/lib/automations/builder-schema.ts b/surfsense_web/lib/automations/builder-schema.ts index a9349c56f..c2bd69209 100644 --- a/surfsense_web/lib/automations/builder-schema.ts +++ b/surfsense_web/lib/automations/builder-schema.ts @@ -66,6 +66,19 @@ export const builderExecutionSchema = z.object({ }); export type BuilderExecution = z.infer; +/** + * Per-automation model selection. ``0`` means "unset" — the builder resolves it + * to the eligible default during render, and the resolved (non-zero) ids are + * written onto ``definition.models`` at submit so the run is insulated from + * later chat/search-space model changes. + */ +export const builderModelsSchema = z.object({ + agentLlmId: z.number().int(), + imageConfigId: z.number().int(), + visionConfigId: z.number().int(), +}); +export type BuilderModels = z.infer; + export const builderFormSchema = z.object({ name: z.string().trim().min(1, "Give your automation a name").max(200), description: z.string().trim().max(2000).nullable(), @@ -77,6 +90,8 @@ export const builderFormSchema = z.object({ tags: z.array(z.string()), /** Carried through from an edited definition so we don't drop it. */ goal: z.string().nullable(), + /** Selected agent/image/vision models (``0`` = use the eligible default). */ + models: builderModelsSchema, }); export type BuilderForm = z.infer; @@ -132,6 +147,7 @@ export function createEmptyForm(): BuilderForm { }, tags: [], goal: null, + models: { agentLlmId: 0, imageConfigId: 0, visionConfigId: 0 }, }; } @@ -218,9 +234,26 @@ function buildDefinition(form: BuilderForm): AutomationDefinition { on_failure: [], }, metadata: { tags: form.tags }, + // Only emit models when fully resolved (the builder seeds non-zero + // defaults before submit). A zero/unset triple is omitted so the + // backend falls back to the search-space snapshot. + ...(hasResolvedModels(form.models) + ? { + models: { + agent_llm_id: form.models.agentLlmId, + image_generation_config_id: form.models.imageConfigId, + vision_llm_config_id: form.models.visionConfigId, + }, + } + : {}), } as unknown as AutomationDefinition; } +/** True once every model slot holds a concrete (non-zero) id. */ +export function hasResolvedModels(models: BuilderModels): boolean { + return models.agentLlmId !== 0 && models.imageConfigId !== 0 && models.visionConfigId !== 0; +} + /** The desired schedule trigger for this form, or ``null`` if none. */ export function buildScheduleTrigger(form: BuilderForm): TriggerCreateRequest | null { if (!form.schedule) return null; @@ -434,6 +467,8 @@ export function hydrateForm( ? metadata.tags.filter((tag): tag is string => typeof tag === "string") : []; + const models = modelsFromDefinition(d.models); + return { formable: true, form: { @@ -455,10 +490,22 @@ export function hydrateForm( }, tags, goal: typeof d.goal === "string" ? d.goal : null, + models, }, }; } +/** Read a captured ``definition.models`` snapshot into the form's model slots. */ +function modelsFromDefinition(raw: unknown): BuilderModels { + const m = asRecord(raw); + const num = (value: unknown) => (typeof value === "number" ? value : 0); + return { + agentLlmId: num(m.agent_llm_id), + imageConfigId: num(m.image_generation_config_id), + visionConfigId: num(m.vision_llm_config_id), + }; +} + /** * Project an existing automation into the builder form for editing. */ From 92b1d7a9f77d0480d1df6c5ad292f3c79aaf773b Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sat, 30 May 2026 01:13:21 -0700 Subject: [PATCH 186/198] feat(automations): enhance tracking for automation lifecycle events - Added tracking for automation creation, updates, deletions, and trigger modifications, including success and failure events. - Implemented event tracking in the automation creation process, including chat approval and rejection scenarios. - Updated the instrumentation client to ensure correct typing for PostHog integration. - Refactored existing mutation atoms to include tracking calls for automation-related actions, improving analytics capabilities. --- .../automations/automations-mutation.atoms.ts | 112 +++++++++++++- .../tool-ui/automation/create-automation.tsx | 66 +++++++- surfsense_web/instrumentation-client.ts | 7 +- surfsense_web/lib/posthog/events.ts | 145 +++++++++++++++++- 4 files changed, 316 insertions(+), 14 deletions(-) diff --git a/surfsense_web/atoms/automations/automations-mutation.atoms.ts b/surfsense_web/atoms/automations/automations-mutation.atoms.ts index f5e4fd5f4..a81cd1578 100644 --- a/surfsense_web/atoms/automations/automations-mutation.atoms.ts +++ b/surfsense_web/atoms/automations/automations-mutation.atoms.ts @@ -7,6 +7,21 @@ import type { TriggerUpdateRequest, } from "@/contracts/types/automation.types"; import { automationsApiService } from "@/lib/apis/automations-api.service"; +import { + trackAutomationCreated, + trackAutomationCreateFailed, + trackAutomationDeleted, + trackAutomationDeleteFailed, + trackAutomationStatusChanged, + trackAutomationTriggerAdded, + trackAutomationTriggerAddFailed, + trackAutomationTriggerRemoved, + trackAutomationTriggerRemoveFailed, + trackAutomationTriggerUpdated, + trackAutomationTriggerUpdateFailed, + trackAutomationUpdated, + trackAutomationUpdateFailed, +} from "@/lib/posthog/events"; import { cacheKeys } from "@/lib/query-client/cache-keys"; import { queryClient } from "@/lib/query-client/client"; @@ -33,13 +48,28 @@ export const createAutomationMutationAtom = atomWithMutation(() => ({ mutationFn: async (request: AutomationCreateRequest) => { return automationsApiService.createAutomation(request); }, - onSuccess: (_, variables) => { + onSuccess: (automation, variables) => { invalidateList(variables.search_space_id); toast.success("Automation created"); + trackAutomationCreated({ + search_space_id: variables.search_space_id, + automation_id: automation.id, + task_count: variables.definition.plan.length, + trigger_type: variables.triggers?.[0]?.type ?? "none", + has_schedule: (variables.triggers?.length ?? 0) > 0, + agent_llm_id: variables.definition.models?.agent_llm_id, + image_generation_config_id: variables.definition.models?.image_generation_config_id, + vision_llm_config_id: variables.definition.models?.vision_llm_config_id, + tags_count: variables.definition.metadata?.tags?.length, + }); }, - onError: (error: Error) => { + onError: (error: Error, variables) => { console.error("Error creating automation:", error); toast.error("Failed to create automation"); + trackAutomationCreateFailed({ + search_space_id: variables.search_space_id, + error: error.message, + }); }, })); @@ -52,10 +82,32 @@ export const updateAutomationMutationAtom = atomWithMutation(() => ({ invalidateDetail(vars.automationId); invalidateList(automation.search_space_id); toast.success("Automation updated"); + // A status-only patch (pause/resume/archive) is a distinct action from a + // definition/name edit, so split it into its own event. + if (vars.patch.status && !vars.patch.definition) { + trackAutomationStatusChanged({ + automation_id: vars.automationId, + search_space_id: automation.search_space_id, + next_status: vars.patch.status, + }); + } else { + trackAutomationUpdated({ + automation_id: vars.automationId, + search_space_id: automation.search_space_id, + has_definition_change: !!vars.patch.definition, + has_name_change: vars.patch.name != null, + has_description_change: vars.patch.description !== undefined, + task_count: vars.patch.definition?.plan?.length, + }); + } }, - onError: (error: Error) => { + onError: (error: Error, vars) => { console.error("Error updating automation:", error); toast.error("Failed to update automation"); + trackAutomationUpdateFailed({ + automation_id: vars.automationId, + error: error.message, + }); }, })); @@ -69,10 +121,18 @@ export const deleteAutomationMutationAtom = atomWithMutation(() => ({ invalidateList(vars.searchSpaceId); invalidateDetail(vars.automationId); toast.success("Automation deleted"); + trackAutomationDeleted({ + automation_id: vars.automationId, + search_space_id: vars.searchSpaceId, + }); }, - onError: (error: Error) => { + onError: (error: Error, vars) => { console.error("Error deleting automation:", error); toast.error("Failed to delete automation"); + trackAutomationDeleteFailed({ + automation_id: vars.automationId, + error: error.message, + }); }, })); @@ -81,13 +141,24 @@ export const addTriggerMutationAtom = atomWithMutation(() => ({ mutationFn: async (vars: { automationId: number; payload: TriggerCreateRequest }) => { return automationsApiService.addTrigger(vars.automationId, vars.payload); }, - onSuccess: (_, vars) => { + onSuccess: (trigger, vars) => { invalidateDetail(vars.automationId); toast.success("Trigger added"); + trackAutomationTriggerAdded({ + automation_id: vars.automationId, + trigger_id: trigger.id, + trigger_type: trigger.type, + enabled: trigger.enabled, + has_cron: !!trigger.params?.cron, + }); }, - onError: (error: Error) => { + onError: (error: Error, vars) => { console.error("Error adding trigger:", error); toast.error("Failed to add trigger"); + trackAutomationTriggerAddFailed({ + automation_id: vars.automationId, + error: error.message, + }); }, })); @@ -103,10 +174,26 @@ export const updateTriggerMutationAtom = atomWithMutation(() => ({ onSuccess: (_, vars) => { invalidateDetail(vars.automationId); toast.success("Trigger updated"); + const change: "enabled" | "params" | "other" = vars.patch.params + ? "params" + : vars.patch.enabled !== undefined && vars.patch.enabled !== null + ? "enabled" + : "other"; + trackAutomationTriggerUpdated({ + automation_id: vars.automationId, + trigger_id: vars.triggerId, + change, + enabled: vars.patch.enabled ?? undefined, + }); }, - onError: (error: Error) => { + onError: (error: Error, vars) => { console.error("Error updating trigger:", error); toast.error("Failed to update trigger"); + trackAutomationTriggerUpdateFailed({ + automation_id: vars.automationId, + trigger_id: vars.triggerId, + error: error.message, + }); }, })); @@ -119,9 +206,18 @@ export const removeTriggerMutationAtom = atomWithMutation(() => ({ onSuccess: (vars) => { invalidateDetail(vars.automationId); toast.success("Trigger removed"); + trackAutomationTriggerRemoved({ + automation_id: vars.automationId, + trigger_id: vars.triggerId, + }); }, - onError: (error: Error) => { + onError: (error: Error, vars) => { console.error("Error removing trigger:", error); toast.error("Failed to remove trigger"); + trackAutomationTriggerRemoveFailed({ + automation_id: vars.automationId, + trigger_id: vars.triggerId, + error: error.message, + }); }, })); diff --git a/surfsense_web/components/tool-ui/automation/create-automation.tsx b/surfsense_web/components/tool-ui/automation/create-automation.tsx index 1bd20bbde..d4cc0ec4d 100644 --- a/surfsense_web/components/tool-ui/automation/create-automation.tsx +++ b/surfsense_web/components/tool-ui/automation/create-automation.tsx @@ -4,7 +4,7 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react"; import { useAtomValue } from "jotai"; import { AlertCircle, CornerDownLeftIcon, ExternalLink, Pencil, Workflow } from "lucide-react"; import Link from "next/link"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { AutomationModelFields, type AutomationModelSelection, @@ -17,6 +17,13 @@ import { automationCreateRequest } from "@/contracts/types/automation.types"; import type { HitlDecision, InterruptResult } from "@/features/chat-messages/hitl"; import { isInterruptResult, useHitlDecision, useHitlPhase } from "@/features/chat-messages/hitl"; import { useAutomationEligibleModels } from "@/hooks/use-automation-eligible-models"; +import { + trackAutomationChatApproved, + trackAutomationChatCreateFailed, + trackAutomationChatCreateSucceeded, + trackAutomationChatDraftEdited, + trackAutomationChatRejected, +} from "@/lib/posthog/events"; import { AutomationDraftPreview } from "./automation-draft-preview"; const editArgsSchema = automationCreateRequest.omit({ search_space_id: true }); @@ -145,6 +152,19 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { }, }, }; + const plan = Array.isArray(baseDefinition.plan) ? baseDefinition.plan : []; + const triggers = Array.isArray(baseArgs.triggers) ? baseArgs.triggers : []; + trackAutomationChatApproved({ + search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined, + edited: pendingEdits !== null, + task_count: plan.length, + trigger_type: + (triggers[0] as { type?: string } | undefined)?.type ?? + (triggers.length ? undefined : "none"), + agent_llm_id: resolvedModels.agentLlmId, + image_generation_config_id: resolvedModels.imageConfigId, + vision_llm_config_id: resolvedModels.visionConfigId, + }); onDecision({ type: "edit", edited_action: { @@ -163,13 +183,17 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { args, pendingEdits, resolvedModels, + searchSpaceId, ]); const handleReject = useCallback(() => { if (phase !== "pending" || !canReject || isEditing) return; setRejected(); + trackAutomationChatRejected({ + search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined, + }); onDecision({ type: "reject", message: "User rejected the automation draft." }); - }, [phase, canReject, isEditing, setRejected, onDecision]); + }, [phase, canReject, isEditing, setRejected, onDecision, searchSpaceId]); useEffect(() => { if (isEditing) return; @@ -242,6 +266,9 @@ function ApprovalCard({ args, interruptData, onDecision }: ApprovalCardProps) { onSave={(parsed) => { setPendingEdits(parsed); setIsEditing(false); + trackAutomationChatDraftEdited({ + search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined, + }); }} onCancel={() => setIsEditing(false)} /> @@ -356,6 +383,17 @@ function JsonEditor({ initialValue, onSave, onCancel }: JsonEditorProps) { function SavedCard({ result }: { result: SavedResult }) { const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); + const tracked = useRef(false); + useEffect(() => { + if (tracked.current) return; + tracked.current = true; + trackAutomationChatCreateSucceeded({ + automation_id: result.automation_id, + name: result.name, + search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined, + }); + }, [result.automation_id, result.name, searchSpaceId]); + const detailHref = searchSpaceId ? `/dashboard/${searchSpaceId}/automations/${result.automation_id}` : null; @@ -388,6 +426,18 @@ function SavedCard({ result }: { result: SavedResult }) { } function InvalidCard({ result }: { result: InvalidResult }) { + const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); + const tracked = useRef(false); + useEffect(() => { + if (tracked.current) return; + tracked.current = true; + trackAutomationChatCreateFailed({ + reason: "invalid", + issue_count: result.issues.length, + search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined, + }); + }, [result.issues.length, searchSpaceId]); + return (
@@ -411,6 +461,18 @@ function InvalidCard({ result }: { result: InvalidResult }) { } function ErrorCard({ result }: { result: ErrorResult }) { + const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); + const tracked = useRef(false); + useEffect(() => { + if (tracked.current) return; + tracked.current = true; + trackAutomationChatCreateFailed({ + reason: "error", + message: result.message, + search_space_id: searchSpaceId ? Number(searchSpaceId) : undefined, + }); + }, [result.message, searchSpaceId]); + return (
diff --git a/surfsense_web/instrumentation-client.ts b/surfsense_web/instrumentation-client.ts index 3ae97fc0b..3eab87dec 100644 --- a/surfsense_web/instrumentation-client.ts +++ b/surfsense_web/instrumentation-client.ts @@ -88,9 +88,12 @@ async function initPostHog() { } return event; }, - loaded: (ph) => { + loaded: () => { if (typeof window !== "undefined") { - window.posthog = ph; + // `loaded` hands back a `PostHogInterface`, but it's the same + // singleton as the default import (typed `PostHog`); use that to + // keep `window.posthog` correctly typed. + window.posthog = posthog; } }, }); diff --git a/surfsense_web/lib/posthog/events.ts b/surfsense_web/lib/posthog/events.ts index a584f9b6f..4dc644d5e 100644 --- a/surfsense_web/lib/posthog/events.ts +++ b/surfsense_web/lib/posthog/events.ts @@ -1,5 +1,5 @@ +import posthog from "posthog-js"; import type { ChatErrorKind, ChatErrorSeverity, ChatFlow } from "@/lib/chat/chat-error-classifier"; -import type { ConnectorTelemetryMeta } from "@/lib/connector-telemetry"; import { getConnectorTelemetryMeta } from "@/lib/connector-telemetry"; /** @@ -19,6 +19,7 @@ import { getConnectorTelemetryMeta } from "@/lib/connector-telemetry"; * - connector: External connector events (all lifecycle stages) * - contact: Contact form events * - settings: Settings changes + * - automation: Automation lifecycle (create/update/delete/trigger/chat) * - marketing: Marketing/referral tracking */ @@ -33,7 +34,7 @@ function safeCapture(event: string, properties?: Record) { /** * Drop undefined values so PostHog doesn't log `"foo": undefined` noise. */ -function compact>(obj: T): Record { +function compact(obj: T): Record { const out: Record = {}; for (const [k, v] of Object.entries(obj)) { if (v !== undefined) out[k] = v; @@ -598,6 +599,146 @@ export function trackReferralLanding(refCode: string, landingUrl: string) { }); } +// ============================================ +// AUTOMATION EVENTS +// ============================================ + +interface AutomationCreatedProps { + search_space_id: number; + automation_id: number; + task_count?: number; + trigger_type?: string; + has_schedule?: boolean; + agent_llm_id?: number; + image_generation_config_id?: number; + vision_llm_config_id?: number; + tags_count?: number; +} + +export function trackAutomationCreated(props: AutomationCreatedProps) { + safeCapture("automation_created", compact(props)); +} + +export function trackAutomationCreateFailed(props: { search_space_id?: number; error?: string }) { + safeCapture("automation_create_failed", compact(props)); +} + +export function trackAutomationUpdated(props: { + automation_id: number; + search_space_id?: number; + has_definition_change?: boolean; + has_name_change?: boolean; + has_description_change?: boolean; + task_count?: number; +}) { + safeCapture("automation_updated", compact(props)); +} + +export function trackAutomationStatusChanged(props: { + automation_id: number; + search_space_id?: number; + next_status: string; +}) { + safeCapture("automation_status_changed", compact(props)); +} + +export function trackAutomationUpdateFailed(props: { automation_id: number; error?: string }) { + safeCapture("automation_update_failed", compact(props)); +} + +export function trackAutomationDeleted(props: { automation_id: number; search_space_id?: number }) { + safeCapture("automation_deleted", compact(props)); +} + +export function trackAutomationDeleteFailed(props: { automation_id: number; error?: string }) { + safeCapture("automation_delete_failed", compact(props)); +} + +export function trackAutomationTriggerAdded(props: { + automation_id: number; + trigger_id?: number; + trigger_type?: string; + enabled?: boolean; + has_cron?: boolean; +}) { + safeCapture("automation_trigger_added", compact(props)); +} + +export function trackAutomationTriggerAddFailed(props: { automation_id: number; error?: string }) { + safeCapture("automation_trigger_add_failed", compact(props)); +} + +export function trackAutomationTriggerUpdated(props: { + automation_id: number; + trigger_id: number; + change?: "enabled" | "params" | "other"; + enabled?: boolean; +}) { + safeCapture("automation_trigger_updated", compact(props)); +} + +export function trackAutomationTriggerUpdateFailed(props: { + automation_id: number; + trigger_id: number; + error?: string; +}) { + safeCapture("automation_trigger_update_failed", compact(props)); +} + +export function trackAutomationTriggerRemoved(props: { + automation_id: number; + trigger_id: number; +}) { + safeCapture("automation_trigger_removed", compact(props)); +} + +export function trackAutomationTriggerRemoveFailed(props: { + automation_id: number; + trigger_id: number; + error?: string; +}) { + safeCapture("automation_trigger_remove_failed", compact(props)); +} + +interface AutomationChatDecisionProps { + search_space_id?: number; + edited?: boolean; + task_count?: number; + trigger_type?: string; + agent_llm_id?: number; + image_generation_config_id?: number; + vision_llm_config_id?: number; +} + +export function trackAutomationChatApproved(props: AutomationChatDecisionProps) { + safeCapture("automation_chat_approved", compact(props)); +} + +export function trackAutomationChatRejected(props: { search_space_id?: number }) { + safeCapture("automation_chat_rejected", compact(props)); +} + +export function trackAutomationChatDraftEdited(props: { search_space_id?: number }) { + safeCapture("automation_chat_draft_edited", compact(props)); +} + +export function trackAutomationChatCreateSucceeded(props: { + automation_id: number; + name?: string; + search_space_id?: number; +}) { + safeCapture("automation_chat_create_succeeded", compact(props)); +} + +export function trackAutomationChatCreateFailed(props: { + reason: "invalid" | "error"; + search_space_id?: number; + issue_count?: number; + message?: string; +}) { + safeCapture("automation_chat_create_failed", compact(props)); +} + // ============================================ // USER IDENTIFICATION // ============================================ From 0f2e3c765556036b4faca4ff713785886ce0180f Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 15:58:21 -0700 Subject: [PATCH 187/198] refactor: anonymous/free chat experience - Enhanced lambda function formatting in `_after_commit` for better clarity. - Simplified generator expression in `_match_condition` for improved readability. - Streamlined function signature in `_eligible` for consistency. - Updated imports and refactored anonymous chat routes to use a new agent creation method. - Added a new function `_load_anon_document` to handle document loading from Redis. - Improved UI components by replacing legacy structures with modern alternatives, including alerts and separators. - Refactored quota-related components to utilize new alert structures for better user feedback. - Cleaned up unused variables and optimized component states for performance. --- .../app/agents/new_chat/anonymous_agent.py | 168 +++++++++++++++ .../triggers/builtin/event/filter.py | 3 +- .../triggers/builtin/event/selector.py | 4 +- .../app/routes/anonymous_chat_routes.py | 57 +++-- surfsense_backend/app/session_events.py | 8 +- .../tests/unit/event_bus/test_catalog.py | 4 + .../assistant-ui/inline-citation.tsx | 4 +- .../components/free-chat/free-chat-page.tsx | 41 ++-- .../components/free-chat/free-composer.tsx | 18 +- .../free-chat/free-model-selector.tsx | 199 ++++++------------ .../components/free-chat/free-right-panel.tsx | 32 ++- .../components/free-chat/quota-bar.tsx | 33 ++- .../free-chat/quota-warning-banner.tsx | 86 ++++---- .../layout/ui/sidebar/DocumentsSidebar.tsx | 5 +- .../new-chat/document-mention-picker.tsx | 8 +- surfsense_web/components/ui/empty.tsx | 94 +++++++++ surfsense_web/contexts/anonymous-mode.tsx | 7 +- 17 files changed, 493 insertions(+), 278 deletions(-) create mode 100644 surfsense_backend/app/agents/new_chat/anonymous_agent.py create mode 100644 surfsense_web/components/ui/empty.tsx diff --git a/surfsense_backend/app/agents/new_chat/anonymous_agent.py b/surfsense_backend/app/agents/new_chat/anonymous_agent.py new file mode 100644 index 000000000..c783d9a45 --- /dev/null +++ b/surfsense_backend/app/agents/new_chat/anonymous_agent.py @@ -0,0 +1,168 @@ +"""Minimal anonymous / free-chat agent. + +The no-login chat experience must stay dead simple: the user asks a question +and the model answers, optionally using ``web_search`` and an optionally +uploaded **read-only** document. We deliberately bypass the full SurfSense deep +agent stack (filesystem, file-intent, knowledge-base persistence, subagents, +skills, memory) because those middlewares stage or persist "documents" that an +anonymous session can never see again -- which produced phantom +"I saved it to a file" answers for free users. + +For any other SurfSense capability the model is instructed (via the system +prompt built here) to tell the user to create a free account instead of +pretending to perform the action. +""" + +from __future__ import annotations + +from datetime import UTC, datetime +from typing import Any + +from deepagents.backends import StateBackend +from langchain.agents import create_agent +from langchain.agents.middleware import ( + ModelCallLimitMiddleware, + ToolCallLimitMiddleware, +) +from langchain_core.language_models import BaseChatModel +from langgraph.types import Checkpointer + +from app.agents.new_chat.context import SurfSenseContextSchema +from app.agents.new_chat.middleware import ( + RetryAfterMiddleware, + create_surfsense_compaction_middleware, +) +from app.agents.new_chat.tools.web_search import create_web_search_tool + +# Cap how much of an uploaded document we inline into the system prompt. The +# upload endpoint allows files up to several MB, but the doc is re-sent on +# every turn and counts against the anonymous token quota, so we bound it. +_MAX_DOC_CHARS = 50_000 + + +def build_anonymous_system_prompt(anon_doc: dict[str, Any] | None = None) -> str: + """Build the system prompt for the minimal anonymous chat agent. + + The prompt keeps the assistant focused on plain Q/A + web search, inlines + any uploaded document as read-only context, and redirects every other + SurfSense feature to account registration. + """ + today = datetime.now(UTC).strftime("%A, %B %d, %Y") + + doc_section = "" + if anon_doc: + title = str(anon_doc.get("title") or "uploaded_document") + content = str(anon_doc.get("content") or "") + truncated = content[:_MAX_DOC_CHARS] + truncation_note = "" + if len(content) > _MAX_DOC_CHARS: + truncation_note = ( + "\n\n[Note: the document was truncated because it is large; " + "only the beginning is shown.]" + ) + doc_section = ( + "\n\n## Uploaded document (read-only)\n" + f'The user uploaded a document named "{title}". Its contents are ' + "provided below for reference only. You may read it and answer " + "questions about it, but you cannot modify, save, or store it.\n\n" + f'\n' + f"{truncated}{truncation_note}\n" + "" + ) + + return ( + "You are SurfSense's free AI assistant, available to everyone without " + "login.\n\n" + f"Today's date is {today}.\n\n" + "## How to help\n" + "- Answer the user's questions directly and conversationally. You are " + "a straightforward question-and-answer assistant.\n" + "- When a question needs current, real-time, or factual information " + "from the internet (news, prices, weather, recent events, live data), " + "use the `web_search` tool. Otherwise, answer directly from your own " + "knowledge.\n" + "- Be concise, accurate, and helpful. Use Markdown formatting when it " + "improves readability." + f"{doc_section}\n\n" + "## What is not available here\n" + "This is the free, no-login experience. You CANNOT save files or " + "notes, generate reports, podcasts, resumes, presentations, or images, " + "search or build a knowledge base, connect to apps (Gmail, Google " + "Drive, Notion, Slack, Calendar, Discord, and similar), set up " + "automations, or remember anything across sessions.\n\n" + "If the user asks for any of these, do NOT pretend to do them and " + "never claim you saved, created, or stored anything. Instead, briefly " + "let them know the feature requires a free SurfSense account and " + "invite them to create one at https://www.surfsense.com. Then offer to " + "help with what you can do here (answering questions and searching the " + "web)." + ) + + +async def create_anonymous_chat_agent( + *, + llm: BaseChatModel, + checkpointer: Checkpointer, + anon_session_id: str | None = None, + anon_doc: dict[str, Any] | None = None, + enable_web_search: bool = True, +): + """Create a minimal Q/A agent for anonymous / free chat. + + Unlike :func:`create_surfsense_deep_agent`, this agent has no filesystem, + file-intent, knowledge-base persistence, subagent, skills, or memory + middleware. Its only tool is ``web_search`` (when ``enable_web_search`` is + True), and any uploaded document is injected into the system prompt as + read-only context. + + Args: + llm: The chat model to use (already built by the caller). + checkpointer: LangGraph checkpointer for the ephemeral anon thread. + anon_session_id: Anonymous session id (used only for telemetry/metadata). + anon_doc: Optional ``{"title", "content"}`` for an uploaded document. + enable_web_search: When False, the agent runs as a pure LLM with no + tools (used when the user toggles web search off). + """ + tools = ( + [create_web_search_tool(search_space_id=None, available_connectors=None)] + if enable_web_search + else [] + ) + + # Reliability-only middleware. Nothing here touches the database or + # filesystem: call limits guard against loops, compaction summarises long + # histories into in-graph state, and retry handles provider rate limits. + middleware: list[Any] = [ + ModelCallLimitMiddleware(thread_limit=120, run_limit=80, exit_behavior="end"), + ] + if tools: + middleware.append( + ToolCallLimitMiddleware( + thread_limit=300, run_limit=80, exit_behavior="continue" + ) + ) + middleware.append(create_surfsense_compaction_middleware(llm, StateBackend)) + middleware.append(RetryAfterMiddleware(max_retries=3)) + + system_prompt = build_anonymous_system_prompt(anon_doc) + + agent = create_agent( + llm, + system_prompt=system_prompt, + tools=tools, + middleware=middleware, + context_schema=SurfSenseContextSchema, + checkpointer=checkpointer, + ) + return agent.with_config( + { + "recursion_limit": 40, + "metadata": { + "ls_integration": "surfsense_anonymous_chat", + "anon_session_id": anon_session_id, + }, + } + ) + + +__all__ = ["build_anonymous_system_prompt", "create_anonymous_chat_agent"] diff --git a/surfsense_backend/app/automations/triggers/builtin/event/filter.py b/surfsense_backend/app/automations/triggers/builtin/event/filter.py index 9f13cd51e..742281fc6 100644 --- a/surfsense_backend/app/automations/triggers/builtin/event/filter.py +++ b/surfsense_backend/app/automations/triggers/builtin/event/filter.py @@ -65,8 +65,7 @@ def _match_condition(condition: Any, actual: Any) -> bool: return False if isinstance(condition, dict): return all( - _apply_operator(op, operand, actual) - for op, operand in condition.items() + _apply_operator(op, operand, actual) for op, operand in condition.items() ) return actual == condition diff --git a/surfsense_backend/app/automations/triggers/builtin/event/selector.py b/surfsense_backend/app/automations/triggers/builtin/event/selector.py index 9c000e716..ee00a6094 100644 --- a/surfsense_backend/app/automations/triggers/builtin/event/selector.py +++ b/surfsense_backend/app/automations/triggers/builtin/event/selector.py @@ -41,9 +41,7 @@ async def _select_and_start(event_dict: dict[str, Any]) -> None: await _start_one(session, trigger=trigger, event=event) -async def _eligible( - session: AsyncSession, *, event: Event -) -> list[AutomationTrigger]: +async def _eligible(session: AsyncSession, *, event: Event) -> list[AutomationTrigger]: """Enabled ``event`` triggers for this event type whose filter matches.""" stmt = select(AutomationTrigger).where( AutomationTrigger.type == TriggerType.EVENT, diff --git a/surfsense_backend/app/routes/anonymous_chat_routes.py b/surfsense_backend/app/routes/anonymous_chat_routes.py index f9d694e5a..eb952e684 100644 --- a/surfsense_backend/app/routes/anonymous_chat_routes.py +++ b/surfsense_backend/app/routes/anonymous_chat_routes.py @@ -351,10 +351,9 @@ async def stream_anonymous_chat( async def _generate(): from langchain_core.messages import AIMessage, HumanMessage - from app.agents.new_chat.chat_deepagent import create_surfsense_deep_agent + from app.agents.new_chat.anonymous_agent import create_anonymous_chat_agent from app.agents.new_chat.checkpointer import get_checkpointer from app.db import shielded_async_session - from app.services.connector_service import ConnectorService from app.services.new_streaming_service import VercelStreamingService from app.services.token_tracking_service import start_turn from app.tasks.chat.stream_new_chat import StreamResult, _stream_agent_events @@ -363,24 +362,23 @@ async def stream_anonymous_chat( streaming_service = VercelStreamingService() try: - async with shielded_async_session() as session: - connector_service = ConnectorService(session, search_space_id=None) + async with shielded_async_session(): checkpointer = await get_checkpointer() anon_thread_id = f"anon-{session_id}-{request_id}" - agent = await create_surfsense_deep_agent( + # Load the optional uploaded document as read-only context. + anon_doc = await _load_anon_document(session_id) + + # Minimal Q/A agent: web_search only (when enabled), no + # filesystem / persistence / subagents. The uploaded document + # is injected into the system prompt as read-only context. + agent = await create_anonymous_chat_agent( llm=llm, - search_space_id=0, - db_session=session, - connector_service=connector_service, checkpointer=checkpointer, - user_id=None, - thread_id=None, - agent_config=agent_config, - enabled_tools=list(enabled_for_agent), - disabled_tools=None, anon_session_id=session_id, + anon_doc=anon_doc, + enable_web_search="web_search" in enabled_for_agent, ) langchain_messages = [] @@ -396,7 +394,6 @@ async def stream_anonymous_chat( input_state = { "messages": langchain_messages, - "search_space_id": 0, } langgraph_config = { @@ -500,6 +497,38 @@ ANON_ALLOWED_EXTENSIONS = PLAINTEXT_EXTENSIONS | DIRECT_CONVERT_EXTENSIONS ANON_DOC_REDIS_PREFIX = "anon:doc:" +async def _load_anon_document(session_id: str) -> dict[str, Any] | None: + """Read the anonymous session's uploaded document from Redis. + + Returns ``{"title", "content"}`` for read-only injection into the agent's + system prompt, or ``None`` when nothing was uploaded for this session. + """ + import json as _json + + import redis.asyncio as aioredis + + redis_client = aioredis.from_url(config.REDIS_APP_URL, decode_responses=True) + redis_key = f"{ANON_DOC_REDIS_PREFIX}{session_id}" + try: + data = await redis_client.get(redis_key) + if not data: + return None + payload = _json.loads(data) + except Exception as exc: # pragma: no cover - defensive + logger.warning("Failed to load anonymous document from Redis: %s", exc) + return None + finally: + await redis_client.aclose() + + content = str(payload.get("content") or "") + if not content: + return None + return { + "title": str(payload.get("filename") or "uploaded_document"), + "content": content, + } + + class AnonDocResponse(BaseModel): filename: str size_bytes: int diff --git a/surfsense_backend/app/session_events.py b/surfsense_backend/app/session_events.py index b2ec57fcc..048df2b46 100644 --- a/surfsense_backend/app/session_events.py +++ b/surfsense_backend/app/session_events.py @@ -79,9 +79,11 @@ def _after_commit(session: Session) -> None: ] for task in tasks: task.add_done_callback( - lambda t: logger.error("event publish failed: %s", t.exception()) - if not t.cancelled() and t.exception() - else None + lambda t: ( + logger.error("event publish failed: %s", t.exception()) + if not t.cancelled() and t.exception() + else None + ) ) diff --git a/surfsense_backend/tests/unit/event_bus/test_catalog.py b/surfsense_backend/tests/unit/event_bus/test_catalog.py index cbd377b02..b09482bea 100644 --- a/surfsense_backend/tests/unit/event_bus/test_catalog.py +++ b/surfsense_backend/tests/unit/event_bus/test_catalog.py @@ -24,6 +24,7 @@ def _event_type(type_: str = "test.thing") -> EventType: def test_register_then_get_returns_the_event_type(isolated_event_catalog: None) -> None: from app.event_bus.catalog import catalog + catalog.register(_event_type()) assert catalog.get("test.thing") is not None @@ -32,12 +33,14 @@ def test_register_then_get_returns_the_event_type(isolated_event_catalog: None) def test_get_unknown_type_returns_none(isolated_event_catalog: None) -> None: from app.event_bus.catalog import catalog + assert catalog.get("does.not.exist") is None def test_register_duplicate_type_raises(isolated_event_catalog: None) -> None: """A type is a contract; registering it twice is a bug, not an override.""" from app.event_bus.catalog import catalog + catalog.register(_event_type()) with pytest.raises(ValueError, match="already registered"): @@ -47,6 +50,7 @@ def test_register_duplicate_type_raises(isolated_event_catalog: None) -> None: def test_all_is_a_defensive_snapshot(isolated_event_catalog: None) -> None: """Mutating the returned dict must not corrupt the registry.""" from app.event_bus.catalog import catalog + catalog.register(_event_type()) snapshot = catalog.all() diff --git a/surfsense_web/components/assistant-ui/inline-citation.tsx b/surfsense_web/components/assistant-ui/inline-citation.tsx index cbf3c82d6..6a8f2e035 100644 --- a/surfsense_web/components/assistant-ui/inline-citation.tsx +++ b/surfsense_web/components/assistant-ui/inline-citation.tsx @@ -51,7 +51,9 @@ export const InlineCitation: FC = ({ chunkId, isDocsChunk = doc - {isDocsChunk ? "Documentation reference" : "Uploaded document"} + + {isDocsChunk ? "Documentation reference" : "Uploaded document"} + ); } diff --git a/surfsense_web/components/free-chat/free-chat-page.tsx b/surfsense_web/components/free-chat/free-chat-page.tsx index 927eaef87..2ee026cc3 100644 --- a/surfsense_web/components/free-chat/free-chat-page.tsx +++ b/surfsense_web/components/free-chat/free-chat-page.tsx @@ -15,6 +15,7 @@ import { type TokenUsageData, TokenUsageProvider, } from "@/components/assistant-ui/token-usage-context"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { useAnonymousMode } from "@/contexts/anonymous-mode"; import { TimelineDataUI } from "@/features/chat-messages/timeline"; import { @@ -101,11 +102,16 @@ export function FreeChatPage() { const anonMode = useAnonymousMode(); const modelSlug = anonMode.isAnonymous ? anonMode.modelSlug : ""; const resetKey = anonMode.isAnonymous ? anonMode.resetKey : 0; + const webSearchEnabled = anonMode.isAnonymous ? anonMode.webSearchEnabled : true; const [messages, setMessages] = useState([]); const [isRunning, setIsRunning] = useState(false); const [tokenUsageStore] = useState(() => createTokenUsageStore()); const abortControllerRef = useRef(null); + // Mirror the latest messages into a ref so onNew stays a stable callback + // (it reads history on demand instead of depending on the array). + const messagesRef = useRef([]); + messagesRef.current = messages; // Turnstile CAPTCHA state const [captchaRequired, setCaptchaRequired] = useState(false); @@ -152,6 +158,7 @@ export function FreeChatPage() { model_slug: modelSlug, messages: messageHistory, }; + if (!webSearchEnabled) reqBody.disabled_tools = ["web_search"]; if (turnstileToken) reqBody.turnstile_token = turnstileToken; const response = await fetch(`${BACKEND_URL}/api/v1/public/anon-chat/stream`, { @@ -301,7 +308,7 @@ export function FreeChatPage() { throw err; } }, - [modelSlug, tokenUsageStore] + [modelSlug, tokenUsageStore, webSearchEnabled] ); const onNew = useCallback( @@ -345,7 +352,7 @@ export function FreeChatPage() { }, ]); - const messageHistory = messages + const messageHistory = messagesRef.current .filter((m) => m.role === "user" || m.role === "assistant") .map((m) => { let text = ""; @@ -395,7 +402,7 @@ export function FreeChatPage() { abortControllerRef.current = null; } }, - [messages, doStream] + [modelSlug, anonMode, doStream] ); /** Called when Turnstile resolves successfully. Stores the token and auto-retries. */ @@ -481,19 +488,21 @@ export function FreeChatPage() {
{captchaRequired && TURNSTILE_SITE_KEY && ( -
-
- - Quick verification to continue chatting -
- turnstileRef.current?.reset()} - onExpire={() => turnstileRef.current?.reset()} - options={{ theme: "auto", size: "normal" }} - /> +
+ + + Quick verification to continue chatting + + turnstileRef.current?.reset()} + onExpire={() => turnstileRef.current?.reset()} + options={{ theme: "auto", size: "normal" }} + /> + +
)} diff --git a/surfsense_web/components/free-chat/free-composer.tsx b/surfsense_web/components/free-chat/free-composer.tsx index 943ace9aa..46d9e0259 100644 --- a/surfsense_web/components/free-chat/free-composer.tsx +++ b/surfsense_web/components/free-chat/free-composer.tsx @@ -6,6 +6,7 @@ import { type FC, useCallback, useRef, useState } from "react"; import { toast } from "sonner"; import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; import { Switch } from "@/components/ui/switch"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useAnonymousMode } from "@/contexts/anonymous-mode"; @@ -71,10 +72,11 @@ export const FreeComposer: FC = () => { const { gate } = useLoginGate(); const anonMode = useAnonymousMode(); const [text, setText] = useState(""); - const [webSearchEnabled, setWebSearchEnabled] = useState(true); const fileInputRef = useRef(null); const hasUploadedDoc = anonMode.isAnonymous && anonMode.uploadedDoc !== null; + const webSearchEnabled = anonMode.isAnonymous ? anonMode.webSearchEnabled : true; + const setWebSearchEnabled = anonMode.isAnonymous ? anonMode.setWebSearchEnabled : () => {}; const handleTextChange = useCallback( (e: React.ChangeEvent) => { @@ -189,14 +191,11 @@ export const FreeComposer: FC = () => { @@ -207,13 +206,13 @@ export const FreeComposer: FC = () => { -
+ diff --git a/surfsense_web/components/free-chat/free-model-selector.tsx b/surfsense_web/components/free-chat/free-model-selector.tsx index b7ae60bd3..9bf4ecee5 100644 --- a/surfsense_web/components/free-chat/free-model-selector.tsx +++ b/surfsense_web/components/free-chat/free-model-selector.tsx @@ -1,10 +1,18 @@ "use client"; -import { Bot, Check, ChevronDown, Search } from "lucide-react"; +import { Bot, Check, ChevronDown } from "lucide-react"; import { useRouter } from "next/navigation"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { useAnonymousMode } from "@/contexts/anonymous-mode"; import type { AnonModel } from "@/contracts/types/anonymous-chat.types"; @@ -19,21 +27,18 @@ export function FreeModelSelector({ className }: { className?: string }) { const [open, setOpen] = useState(false); const [models, setModels] = useState([]); - const [searchQuery, setSearchQuery] = useState(""); - const [focusedIndex, setFocusedIndex] = useState(-1); - const searchInputRef = useRef(null); useEffect(() => { - anonymousChatApiService.getModels().then(setModels).catch(console.error); - }, []); - - const handleOpenChange = useCallback((next: boolean) => { - if (next) { - setSearchQuery(""); - setFocusedIndex(-1); - requestAnimationFrame(() => searchInputRef.current?.focus()); - } - setOpen(next); + const controller = new AbortController(); + anonymousChatApiService + .getModels() + .then((data) => { + if (!controller.signal.aborted) setModels(data); + }) + .catch((err) => { + if (!controller.signal.aborted) console.error(err); + }); + return () => controller.abort(); }, []); const currentModel = useMemo( @@ -41,22 +46,12 @@ export function FreeModelSelector({ className }: { className?: string }) { [models, currentSlug] ); + // Free models first, premium last; immutable sort to avoid mutating state. const sortedModels = useMemo( - () => [...models].sort((a, b) => Number(a.is_premium) - Number(b.is_premium)), + () => models.toSorted((a, b) => Number(a.is_premium) - Number(b.is_premium)), [models] ); - const filteredModels = useMemo(() => { - if (!searchQuery.trim()) return sortedModels; - const q = searchQuery.toLowerCase(); - return sortedModels.filter( - (m) => - m.name.toLowerCase().includes(q) || - m.model_name.toLowerCase().includes(q) || - m.provider.toLowerCase().includes(q) - ); - }, [sortedModels, searchQuery]); - const handleSelect = useCallback( (model: AnonModel) => { setOpen(false); @@ -70,42 +65,15 @@ export function FreeModelSelector({ className }: { className?: string }) { [currentSlug, anonMode, router] ); - const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { - const count = filteredModels.length; - if (count === 0) return; - switch (e.key) { - case "ArrowDown": - e.preventDefault(); - setFocusedIndex((p) => (p < count - 1 ? p + 1 : 0)); - break; - case "ArrowUp": - e.preventDefault(); - setFocusedIndex((p) => (p > 0 ? p - 1 : count - 1)); - break; - case "Enter": - e.preventDefault(); - if (focusedIndex >= 0 && focusedIndex < count) { - handleSelect(filteredModels[focusedIndex]); - } - break; - } - }, - [filteredModels, focusedIndex, handleSelect] - ); - return ( - + - e.preventDefault()} - > -
- - setSearchQuery(e.target.value)} - onKeyDown={handleKeyDown} - className="w-full pl-8 pr-3 py-2.5 text-sm bg-transparent focus:outline-none placeholder:text-muted-foreground" - /> -
-
- {filteredModels.length === 0 ? ( -
- -

No models found

-
- ) : ( - filteredModels.map((model, index) => { - const isSelected = model.seo_slug === currentSlug; - const isFocused = focusedIndex === index; - return ( -
handleSelect(model)} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - handleSelect(model); - } - }} - onMouseEnter={() => setFocusedIndex(index)} - className={cn( - "group flex items-center gap-2.5 px-3 py-2 rounded-xl cursor-pointer", - "transition-colors duration-150 mx-2", - "hover:bg-accent hover:text-accent-foreground", - isFocused && "bg-accent text-accent-foreground", - isSelected && "bg-accent text-accent-foreground" - )} - > -
- {getProviderIcon(model.provider, { className: "size-5" })} -
-
-
- {model.name} - {model.is_premium ? ( - - Premium - - ) : ( - - Free - - )} + + (value.toLowerCase().includes(search.toLowerCase()) ? 1 : 0)} + > + + + No models found. + + {sortedModels.map((model) => { + const isSelected = model.seo_slug === currentSlug; + return ( + handleSelect(model)} + className="gap-2.5" + > +
+ {getProviderIcon(model.provider, { className: "size-5" })}
- - {model.model_name} - -
- {isSelected && } -
- ); - }) - )} -
+
+
+ {model.name} + + {model.is_premium ? "Premium" : "Free"} + +
+ + {model.model_name} + +
+ {isSelected && } + + ); + })} + + + ); diff --git a/surfsense_web/components/free-chat/free-right-panel.tsx b/surfsense_web/components/free-chat/free-right-panel.tsx index f2b07815f..f37af6142 100644 --- a/surfsense_web/components/free-chat/free-right-panel.tsx +++ b/surfsense_web/components/free-chat/free-right-panel.tsx @@ -4,6 +4,14 @@ import { Lock } from "lucide-react"; import Link from "next/link"; import type { FC } from "react"; import { Button } from "@/components/ui/button"; +import { + Empty, + EmptyContent, + EmptyDescription, + EmptyHeader, + EmptyMedia, + EmptyTitle, +} from "@/components/ui/empty"; interface GatedTabProps { title: string; @@ -11,16 +19,20 @@ interface GatedTabProps { } const GatedTab: FC = ({ title, description }) => ( -
-
- -
-

{title}

-

{description}

- -
+ + + + + + {title} + {description} + + + + + ); export const ReportsGatedPlaceholder: FC = () => ( diff --git a/surfsense_web/components/free-chat/quota-bar.tsx b/surfsense_web/components/free-chat/quota-bar.tsx index 0693ef539..6b2368218 100644 --- a/surfsense_web/components/free-chat/quota-bar.tsx +++ b/surfsense_web/components/free-chat/quota-bar.tsx @@ -2,6 +2,7 @@ import { OctagonAlert, Orbit } from "lucide-react"; import Link from "next/link"; +import { Button } from "@/components/ui/button"; import { Progress } from "@/components/ui/progress"; import { cn } from "@/lib/utils"; @@ -19,38 +20,30 @@ export function QuotaBar({ used, limit, warningThreshold, className }: QuotaBarP const isExceeded = used >= limit; return ( -
-
+
+
{used.toLocaleString()} / {limit.toLocaleString()} tokens {isExceeded ? ( - Limit reached + Limit reached ) : isWarning ? ( - - + + {remaining.toLocaleString()} remaining ) : ( {percentage.toFixed(0)}% )}
- div]:bg-red-500", - isWarning && !isExceeded && "[&>div]:bg-amber-500" - )} - /> + {isExceeded && ( - - - Create free account for 5M more tokens - + )}
); diff --git a/surfsense_web/components/free-chat/quota-warning-banner.tsx b/surfsense_web/components/free-chat/quota-warning-banner.tsx index 828e8006e..e6aa89d42 100644 --- a/surfsense_web/components/free-chat/quota-warning-banner.tsx +++ b/surfsense_web/components/free-chat/quota-warning-banner.tsx @@ -3,6 +3,7 @@ import { OctagonAlert, Orbit, X } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; @@ -27,61 +28,46 @@ export function QuotaWarningBanner({ if (isExceeded) { return ( -
-
- -
-

- Free token limit reached -

-

- You've used all {limit.toLocaleString()} free tokens. Create a free account to - get $5 of premium credit and access to all models. -

- - + + + Free token limit reached + +

+ You've used all {limit.toLocaleString()} free tokens. Create a free account to get + $5 of premium credit and access to all models. +

+
-
-
+ + + ); } return ( -
-
- -

- You've used {used.toLocaleString()} of {limit.toLocaleString()} free tokens.{" "} - - Create an account - {" "} - for $5 of premium credit. -

- -
-
+ + + Running low on free tokens + + You've used {used.toLocaleString()} of {limit.toLocaleString()} free tokens.{" "} + + Create an account + {" "} + for $5 of premium credit. + + + ); } diff --git a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx index 881fbe2b0..a90d6b32e 100644 --- a/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/DocumentsSidebar.tsx @@ -89,10 +89,7 @@ const DesktopLocalTabContent = dynamic( { ssr: false } ); -const NON_DELETABLE_DOCUMENT_TYPES: readonly string[] = [ - "USER_MEMORY", - "TEAM_MEMORY", -]; +const NON_DELETABLE_DOCUMENT_TYPES: readonly string[] = ["USER_MEMORY", "TEAM_MEMORY"]; const MEMORY_DOCUMENTS: DocumentNodeDoc[] = [ { id: -1001, diff --git a/surfsense_web/components/new-chat/document-mention-picker.tsx b/surfsense_web/components/new-chat/document-mention-picker.tsx index 769327e1e..43a5cad74 100644 --- a/surfsense_web/components/new-chat/document-mention-picker.tsx +++ b/surfsense_web/components/new-chat/document-mention-picker.tsx @@ -220,13 +220,7 @@ export const DocumentMentionPicker = forwardRef< DocumentMentionPickerRef, DocumentMentionPickerProps >(function DocumentMentionPicker( - { - searchSpaceId, - onSelectionChange, - onDone, - initialSelectedDocuments = [], - externalSearch = "", - }, + { searchSpaceId, onSelectionChange, onDone, initialSelectedDocuments = [], externalSearch = "" }, ref ) { const search = externalSearch; diff --git a/surfsense_web/components/ui/empty.tsx b/surfsense_web/components/ui/empty.tsx new file mode 100644 index 000000000..79145502f --- /dev/null +++ b/surfsense_web/components/ui/empty.tsx @@ -0,0 +1,94 @@ +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +function Empty({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +const emptyMediaVariants = cva( + "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-transparent", + icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6", + }, + }, + defaultVariants: { + variant: "default", + }, + } +); + +function EmptyMedia({ + className, + variant = "default", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ); +} + +function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) { + return ( +
a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary", + className + )} + {...props} + /> + ); +} + +function EmptyContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +export { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia }; diff --git a/surfsense_web/contexts/anonymous-mode.tsx b/surfsense_web/contexts/anonymous-mode.tsx index eaf0996c3..c24a5cb1b 100644 --- a/surfsense_web/contexts/anonymous-mode.tsx +++ b/surfsense_web/contexts/anonymous-mode.tsx @@ -9,6 +9,8 @@ export interface AnonymousModeContextValue { setModelSlug: (slug: string) => void; uploadedDoc: { filename: string; sizeBytes: number } | null; setUploadedDoc: (doc: { filename: string; sizeBytes: number } | null) => void; + webSearchEnabled: boolean; + setWebSearchEnabled: (enabled: boolean) => void; resetKey: number; resetChat: () => void; } @@ -34,6 +36,7 @@ export function AnonymousModeProvider({ const [uploadedDoc, setUploadedDoc] = useState<{ filename: string; sizeBytes: number } | null>( null ); + const [webSearchEnabled, setWebSearchEnabled] = useState(true); const [resetKey, setResetKey] = useState(0); const resetChat = () => setResetKey((k) => k + 1); @@ -56,10 +59,12 @@ export function AnonymousModeProvider({ setModelSlug, uploadedDoc, setUploadedDoc, + webSearchEnabled, + setWebSearchEnabled, resetKey, resetChat, }), - [modelSlug, uploadedDoc, resetKey] + [modelSlug, uploadedDoc, webSearchEnabled, resetKey] ); return {children}; From 2dc4f6ade43b139297845af342b074d34bf22eec Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 17:14:01 -0700 Subject: [PATCH 188/198] feat(hero-section): restructure hero section with new use case categories and enhanced UI components - Introduced new use case categories for better organization of features. - Updated the hero section to include additional use cases with detailed descriptions and media sources. - Added new UI components such as Empty states, ScrollArea, and Tabs for improved user interaction. - Refactored existing use cases to align with the new structure and enhance clarity. --- .../components/homepage/hero-section.tsx | 605 ++++++++++++------ 1 file changed, 408 insertions(+), 197 deletions(-) diff --git a/surfsense_web/components/homepage/hero-section.tsx b/surfsense_web/components/homepage/hero-section.tsx index c0abd927f..def75b94e 100644 --- a/surfsense_web/components/homepage/hero-section.tsx +++ b/surfsense_web/components/homepage/hero-section.tsx @@ -1,6 +1,6 @@ "use client"; -import { ChevronDown, Download, Monitor } from "lucide-react"; -import { AnimatePresence, motion } from "motion/react"; +import { ChevronDown, Clock, Download, Monitor, Sparkles } from "lucide-react"; +import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import Link from "next/link"; import React, { memo, useCallback, useEffect, useRef, useState } from "react"; import Balancer from "react-wrap-balancer"; @@ -11,7 +11,18 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { + Empty, + EmptyDescription, + EmptyHeader, + EmptyMedia, + EmptyTitle, +} from "@/components/ui/empty"; import { ExpandedMediaOverlay, useExpandedMedia } from "@/components/ui/expanded-gif-overlay"; +import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; +import { Separator } from "@/components/ui/separator"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { GITHUB_RELEASES_URL, @@ -50,96 +61,193 @@ const GoogleLogo = ({ className }: { className?: string }) => ( ); -const TAB_ITEMS = [ +type HeroUseCase = { + id: string; + title: string; + description: string; + src: string | null; + comingSoon?: boolean; +}; + +type HeroCategory = { + id: string; + label: string; + desktopOnly?: boolean; + useCases: HeroUseCase[]; +}; + +const HERO_TUTORIAL = "/homepage/hero_tutorial"; +const HERO_REALTIME = "/homepage/hero_realtime"; + +const CATEGORIES: HeroCategory[] = [ { - title: "General Assist", - description: "Launch SurfSense instantly from any application.", - src: "/homepage/hero_tutorial/general_assist.mp4", - featured: true, + id: "desktop", + label: "Desktop App", + desktopOnly: true, + useCases: [ + { + id: "general", + title: "General Assist", + description: "Launch SurfSense instantly from any application with a global shortcut.", + src: `${HERO_TUTORIAL}/general_assist.mp4`, + }, + { + id: "quick", + title: "Quick Assist", + description: "Select text anywhere, then ask AI to explain, rewrite, or act on it.", + src: `${HERO_TUTORIAL}/quick_assist.mp4`, + }, + { + id: "screenshot", + title: "Screenshot Assist", + description: "Capture any region of your screen and ask AI about what’s in it.", + src: `${HERO_TUTORIAL}/screenshot_assist.mp4`, + }, + { + id: "watch-folder", + title: "Watch Local Folder", + description: "Auto-sync a local folder to your knowledge base. Great for Obsidian vaults.", + src: `${HERO_TUTORIAL}/folder_watch.mp4`, + }, + ], }, { - title: "Quick Assist", - description: "Select text anywhere, then ask AI to explain, rewrite, or act on it.", - src: "/homepage/hero_tutorial/quick_assist.mp4", - featured: true, + id: "deliverables", + label: "Deliverable Studio", + useCases: [ + { + id: "report", + title: "AI Report Generator", + description: + "Generate cited research reports from your documents, then export to PDF or Markdown.", + src: `${HERO_TUTORIAL}/ReportGenGif_compressed.mp4`, + }, + { + id: "podcast", + title: "AI Podcast Generator", + description: "Turn any document or folder into a two-host AI podcast in under 20 seconds.", + src: `${HERO_TUTORIAL}/PodcastGenGif.mp4`, + }, + { + id: "presentation", + title: "AI Presentation & Video Maker", + description: "Create editable slide decks and narrated video overviews from your sources.", + src: `${HERO_TUTORIAL}/video_gen_surf.mp4`, + }, + { + id: "image", + title: "AI Image Generator", + description: "Generate high-quality images straight from your chats and documents.", + src: `${HERO_TUTORIAL}/ImageGenGif.mp4`, + }, + { + id: "resume", + title: "AI Resume Builder", + description: "Draft and format an ATS-ready resume as a polished PDF.", + src: null, + comingSoon: true, + }, + ], }, { - title: "Screenshot Assist", - description: - "Use a global shortcut to select a region on your screen and attach it to your chat message.", - src: "/homepage/hero_tutorial/screenshot_assist.mp4", - featured: true, + id: "automations", + label: "Automations", + useCases: [ + { + id: "sort", + title: "AI Document Sorting", + description: "Let AI automatically organize files into the right folders as they arrive.", + src: null, + comingSoon: true, + }, + { + id: "schedule", + title: "Scheduled AI Workflows", + description: "Run an agent on a schedule: daily briefs, weekly digests, recurring reports.", + src: null, + comingSoon: true, + }, + { + id: "event", + title: "Event-Triggered Automations", + description: + "Fire an agent the moment a document lands in a folder, then post the result to your tools.", + src: null, + comingSoon: true, + }, + { + id: "chat-built", + title: "Chat-Built Automations", + description: "Describe an automation in plain English and SurfSense builds it for you.", + src: null, + comingSoon: true, + }, + ], }, { - title: "Watch Local Folder", - description: - "Watch a local folder and automatically sync file changes to your knowledge base. Works great with Obsidian vaults.", - src: "/homepage/hero_tutorial/folder_watch.mp4", - featured: true, - }, - // { - // title: "Connect & Sync", - // description: - // "Connect data sources like Notion, Drive and Gmail. Automatically sync to keep them updated.", - // src: "/homepage/hero_tutorial/ConnectorFlowGif.mp4", - // featured: true, - // }, - // { - // title: "Upload Documents", - // description: "Upload documents directly, from images to massive PDFs.", - // src: "/homepage/hero_tutorial/DocUploadGif.mp4", - // featured: true, - // }, - { - title: "Video & Presentations", - description: - "Create short videos and editable presentations with AI-generated visuals and narration from your sources.", - src: "/homepage/hero_tutorial/video_gen_surf.mp4", - featured: false, + id: "search-chat", + label: "Search & Chat", + useCases: [ + { + id: "chat-docs", + title: "Chat With Your PDFs & Docs", + description: "Ask questions across all your files and get answers with inline citations.", + src: `${HERO_TUTORIAL}/BQnaGif_compressed.mp4`, + }, + { + id: "search", + title: "AI Search With Citations", + description: "Hybrid semantic and keyword search across your entire knowledge base.", + src: `${HERO_TUTORIAL}/BSNCGif.mp4`, + }, + { + id: "collab", + title: "Collaborative AI Chat", + description: "Work on AI conversations with your team in real time.", + src: `${HERO_REALTIME}/RealTimeChatGif.mp4`, + }, + { + id: "comments", + title: "Comments & Mentions", + description: "Comment and tag teammates on any AI message.", + src: `${HERO_REALTIME}/RealTimeCommentsFlow.mp4`, + }, + ], }, { - title: "Search & Citation", - description: "Ask questions and get cited responses from your knowledge base.", - src: "/homepage/hero_tutorial/BSNCGif.mp4", - featured: false, + id: "connectors", + label: "Connectors & Integrations", + useCases: [ + { + id: "connect", + title: "Connect & Sync Your Tools", + description: + "Sync Notion, Slack, Google Drive, Gmail, GitHub, Linear and 25+ sources into one searchable corpus.", + src: `${HERO_TUTORIAL}/ConnectorFlowGif.mp4`, + }, + { + id: "upload", + title: "Chat With Uploaded Files", + description: "Drop in PDFs, Office docs, images and audio. Instantly searchable.", + src: `${HERO_TUTORIAL}/DocUploadGif.mp4`, + }, + { + id: "write-back", + title: "Connector Write-Back", + description: "Let the agent post results back to Notion, Slack, Linear and Drive.", + src: null, + comingSoon: true, + }, + { + id: "obsidian", + title: "Obsidian & Knowledge Base Sync", + description: "Keep your Obsidian vault and personal knowledge base in sync.", + src: null, + comingSoon: true, + }, + ], }, - { - title: "Document Q&A", - description: "Mention specific documents in chat for targeted answers.", - src: "/homepage/hero_tutorial/BQnaGif_compressed.mp4", - featured: false, - }, - { - title: "Reports", - description: "Generate reports from your sources in many formats.", - src: "/homepage/hero_tutorial/ReportGenGif_compressed.mp4", - featured: false, - }, - { - title: "Podcasts", - description: "Turn anything into a podcast in under 20 seconds.", - src: "/homepage/hero_tutorial/PodcastGenGif.mp4", - featured: false, - }, - { - title: "Image Generation", - description: "Generate high-quality images easily from your conversations.", - src: "/homepage/hero_tutorial/ImageGenGif.mp4", - featured: false, - }, - { - title: "Collaborative Chat", - description: "Collaborate on AI-powered conversations in realtime with your team.", - src: "/homepage/hero_realtime/RealTimeChatGif.mp4", - featured: false, - }, - { - title: "Comments", - description: "Add comments and tag teammates on any message.", - src: "/homepage/hero_realtime/RealTimeCommentsFlow.mp4", - featured: false, - }, -] as const; +]; export function HeroSection() { return ( @@ -279,117 +387,15 @@ function DownloadButton() { ); } -const BrowserWindow = () => { - const [selectedIndex, setSelectedIndex] = useState(0); - const selectedItem = TAB_ITEMS[selectedIndex]; - const { expanded, open, close } = useExpandedMedia(); - - return ( - <> - -
-
-
-
-
-
-
- {TAB_ITEMS.map((item, index) => ( - - - {index !== TAB_ITEMS.length - 1 && ( -
- )} - - ))} -
-
-
- - -
-
-

- {selectedItem.title} -

-

- {selectedItem.description} -

-
-
- -
-
-
- - - - {expanded && ( - - )} - - - ); -}; - -const TabVideo = memo(function TabVideo({ src }: { src: string }) { +const TabVideo = memo(function TabVideo({ + src, + title, + reduceMotion, +}: { + src: string; + title: string; + reduceMotion: boolean; +}) { const videoRef = useRef(null); const [hasLoaded, setHasLoaded] = useState(false); @@ -398,8 +404,11 @@ const TabVideo = memo(function TabVideo({ src }: { src: string }) { const video = videoRef.current; if (!video) return; video.currentTime = 0; - video.play().catch(() => {}); - }, []); + // Respect reduced-motion: show the first frame and expose controls instead of autoplaying. + if (!reduceMotion) { + video.play().catch(() => {}); + } + }, [reduceMotion]); const handleCanPlay = useCallback(() => { setHasLoaded(true); @@ -411,7 +420,10 @@ const TabVideo = memo(function TabVideo({ src }: { src: string }) { ref={videoRef} key={src} src={src} - preload="auto" + preload={reduceMotion ? "metadata" : "auto"} + aria-label={`${title} demo`} + autoPlay={!reduceMotion} + controls={reduceMotion} loop muted playsInline @@ -419,8 +431,207 @@ const TabVideo = memo(function TabVideo({ src }: { src: string }) { className="aspect-video w-full rounded-lg sm:rounded-xl" /> {!hasLoaded && ( -
+ )}
); }); + +const UseCasePlaceholder = ({ title }: { title: string }) => ( + + + + + Demo coming soon + {`A walkthrough of ${title} is on the way.`} + + +); + +const DesktopBadge = () => ( + + + + + + Desktop app only + +); + +const UseCasePane = memo(function UseCasePane({ + useCase, + reduceMotion, +}: { + useCase: HeroUseCase; + reduceMotion: boolean; +}) { + const { expanded, open, close } = useExpandedMedia(); + const hasVideo = !useCase.comingSoon && Boolean(useCase.src); + + const media = hasVideo ? ( + + ) : ( +
+
+ +
+
+ ); + + const card = ( +
+
+
+

+ {useCase.title} +

+

+ {useCase.description} +

+
+
+ {media} +
+ ); + + return ( + <> + {reduceMotion ? ( + card + ) : ( + + {card} + + )} + + + {expanded && hasVideo && ( + + )} + + + ); +}); + +const CategoryPanel = memo(function CategoryPanel({ + category, + reduceMotion, +}: { + category: HeroCategory; + reduceMotion: boolean; +}) { + return ( +
+ {category.desktopOnly && ( +
+
+ )} + + + + {category.useCases.map((useCase) => ( + + {useCase.title} + + ))} + + + +
+ {category.useCases.map((useCase) => ( + + + + ))} +
+
+
+ ); +}); + +const BrowserWindow = () => { + const [activeCategory, setActiveCategory] = useState(CATEGORIES[0].id); + const reduceMotion = useReducedMotion() ?? false; + + return ( + +
+
+
+
+
+
+ + + {CATEGORIES.map((category, index) => ( + + + {category.label} + {category.desktopOnly && } + + {index !== CATEGORIES.length - 1 && ( + + )} + + ))} + + + +
+
+ {CATEGORIES.map((category) => ( + + + + ))} +
+ + ); +}; From 891d7b2a88b2fe0342c93abbba619b20e6707165 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 17:21:24 -0700 Subject: [PATCH 189/198] feat(docs): update README files with new use case categories and detailed descriptions - Enhanced the structure of the README files across multiple languages to include new use case categories. - Added detailed descriptions and media for features such as AI Report Generator, AI Podcast Generator, and more. - Improved clarity and organization of existing features, ensuring consistency across all language versions. - Introduced upcoming features and automation capabilities, highlighting their benefits and functionalities. --- README.es.md | 70 ++++++++++++++++++++++++++++++++++++------------- README.hi.md | 70 ++++++++++++++++++++++++++++++++++++------------- README.md | 70 ++++++++++++++++++++++++++++++++++++------------- README.pt-BR.md | 70 ++++++++++++++++++++++++++++++++++++------------- README.zh-CN.md | 70 ++++++++++++++++++++++++++++++++++++------------- 5 files changed, 260 insertions(+), 90 deletions(-) diff --git a/README.es.md b/README.es.md index dea86a793..01bf95993 100644 --- a/README.es.md +++ b/README.es.md @@ -76,48 +76,82 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 4. Una vez que todo esté indexado, pregunta lo que quieras (Casos de uso): - - Aplicación de Escritorio — General Assist + **Aplicación de Escritorio** (extras nativos, además de todo lo de abajo, no un conjunto aparte) + + - General Assist: abre SurfSense al instante desde cualquier aplicación con un atajo global.

General Assist

- - Aplicación de Escritorio — Quick Assist + - Quick Assist: selecciona texto en cualquier lugar y pide a la IA que lo explique, reescriba o actúe sobre él.

Quick Assist

- - Aplicación de Escritorio — Screenshot Assist + - Screenshot Assist: captura cualquier región de tu pantalla y pregunta a la IA sobre lo que contiene.

Screenshot Assist

- - Aplicación de Escritorio — Watch Local Folder + - Watch Local Folder: sincroniza automáticamente una carpeta local con tu base de conocimiento. Ideal para bóvedas de Obsidian.

Watch Local Folder

- - Generación de videos + **Estudio de Entregables** -

Generación de Videos

+ - AI Report Generator: genera informes de investigación con citas y expórtalos a PDF, DOCX, HTML, LaTeX, EPUB, ODT o texto plano. - - Búsqueda básica y citaciones +

AI Report Generator

-

Búsqueda y Citación

+ - AI Podcast Generator: convierte cualquier documento o carpeta en un pódcast de IA con dos presentadores en menos de 20 segundos. - - QNA con mención de documentos +

AI Podcast Generator

-

QNA con Mención de Documentos

-

QNA con Mención de Documentos

+ - AI Presentation & Video Maker: crea presentaciones editables y videos narrados a partir de tus fuentes. - - Generación de informes y exportaciones (PDF, DOCX, HTML, LaTeX, EPUB, ODT, texto plano) +

AI Presentation and Video Maker

-

Generación de Informes

+ - AI Image Generator: genera imágenes de alta calidad directamente desde tus chats y documentos. - - Generación de podcasts +

AI Image Generator

-

Generación de Podcasts

+ - AI Resume Builder: redacta y da formato a un currículum listo para ATS en un PDF pulido. (próximamente) - - Generación de imágenes + **Búsqueda y Chat** -

Generación de Imágenes

+ - Chat With Your PDFs & Docs: haz preguntas sobre todos tus archivos y obtén respuestas con citas en línea. - - Y más próximamente. +

Chat With Your PDFs and Docs

+ + - AI Search With Citations: búsqueda híbrida semántica y por palabras clave en toda tu base de conocimiento. + +

AI Search With Citations

+ + - Collaborative AI Chat: trabaja en conversaciones de IA con tu equipo en tiempo real. + +

Collaborative AI Chat

+ + - Comments & Mentions: comenta y menciona a tus compañeros en cualquier mensaje de IA. + +

Comments and Mentions

+ + **Conectores e Integraciones** + + - Connect & Sync Your Tools: sincroniza Notion, Slack, Google Drive, Gmail, GitHub, Linear y más de 25 fuentes en un único corpus consultable. + +

Connect and Sync Your Tools

+ + - Chat With Uploaded Files: sube PDFs, documentos de Office, imágenes y audio. Consultables al instante. + +

Chat With Uploaded Files

+ + - Connector Write-Back: deja que el agente publique los resultados de vuelta en Notion, Slack, Linear y Drive. (próximamente) + + - Obsidian & Knowledge Base Sync: mantén tu bóveda de Obsidian y tu base de conocimiento personal sincronizadas. (próximamente) + + **Automatizaciones** + + - AI Document Sorting: deja que la IA organice automáticamente los archivos en las carpetas correctas a medida que llegan. (próximamente) + - Scheduled AI Workflows: ejecuta un agente según una programación: resúmenes diarios, boletines semanales, informes recurrentes. (próximamente) + - Event-Triggered Automations: lanza un agente en el momento en que un documento llega a una carpeta y publica el resultado en tus herramientas. (próximamente) + - Chat-Built Automations: describe una automatización en lenguaje sencillo y SurfSense la crea por ti. (próximamente) ### Auto-Hospedado diff --git a/README.hi.md b/README.hi.md index 43e24c3ee..c75f9453a 100644 --- a/README.hi.md +++ b/README.hi.md @@ -76,48 +76,82 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 4. सब कुछ इंडेक्स हो जाने के बाद, कुछ भी पूछें (उपयोग के मामले): - - डेस्कटॉप ऐप — General Assist + **डेस्कटॉप ऐप** (नीचे दी गई सभी सुविधाओं के अलावा नेटिव एक्स्ट्रा, कोई अलग सेट नहीं) + + - General Assist: किसी भी ऐप्लिकेशन से ग्लोबल शॉर्टकट के ज़रिए SurfSense तुरंत खोलें।

General Assist

- - डेस्कटॉप ऐप — Quick Assist + - Quick Assist: कहीं भी टेक्स्ट चुनें और AI से उसे समझाने, दोबारा लिखने या उस पर कार्रवाई करने को कहें।

Quick Assist

- - डेस्कटॉप ऐप — Screenshot Assist + - Screenshot Assist: अपनी स्क्रीन का कोई भी हिस्सा कैप्चर करें और AI से उसमें मौजूद चीज़ों के बारे में पूछें।

Screenshot Assist

- - डेस्कटॉप ऐप — Watch Local Folder + - Watch Local Folder: किसी लोकल फ़ोल्डर को अपने नॉलेज बेस के साथ अपने-आप सिंक करें। Obsidian vaults के लिए बढ़िया।

Watch Local Folder

- - वीडियो जनरेशन + **डिलीवरेबल स्टूडियो** -

वीडियो जनरेशन

+ - AI Report Generator: उद्धरण सहित रिसर्च रिपोर्ट बनाएं और PDF, DOCX, HTML, LaTeX, EPUB, ODT या सादे टेक्स्ट में एक्सपोर्ट करें। - - बेसिक सर्च और उद्धरण +

AI Report Generator

-

सर्च और उद्धरण

+ - AI Podcast Generator: किसी भी दस्तावेज़ या फ़ोल्डर को 20 सेकंड से भी कम में दो-होस्ट वाले AI पॉडकास्ट में बदलें। - - दस्तावेज़ मेंशन QNA +

AI Podcast Generator

-

दस्तावेज़ मेंशन QNA

-

दस्तावेज़ मेंशन QNA

+ - AI Presentation & Video Maker: अपने स्रोतों से एडिट करने योग्य स्लाइड डेक और नैरेटेड वीडियो बनाएं। - - रिपोर्ट जनरेशन और एक्सपोर्ट (PDF, DOCX, HTML, LaTeX, EPUB, ODT, सादा टेक्स्ट) +

AI Presentation and Video Maker

-

रिपोर्ट जनरेशन

+ - AI Image Generator: अपनी चैट और दस्तावेज़ों से सीधे उच्च-गुणवत्ता वाली इमेज बनाएं। - - पॉडकास्ट जनरेशन +

AI Image Generator

-

पॉडकास्ट जनरेशन

+ - AI Resume Builder: ATS-तैयार रिज़्यूमे को एक बेहतरीन PDF के रूप में बनाएं और फ़ॉर्मेट करें। (जल्द आ रहा है) - - इमेज जनरेशन + **सर्च और चैट** -

इमेज जनरेशन

+ - Chat With Your PDFs & Docs: अपनी सभी फ़ाइलों पर सवाल पूछें और इनलाइन उद्धरणों के साथ जवाब पाएं। - - और भी बहुत कुछ जल्द आ रहा है। +

Chat With Your PDFs and Docs

+ + - AI Search With Citations: अपने पूरे नॉलेज बेस में हाइब्रिड सेमांटिक और कीवर्ड सर्च। + +

AI Search With Citations

+ + - Collaborative AI Chat: अपनी टीम के साथ रियल टाइम में AI बातचीत पर काम करें। + +

Collaborative AI Chat

+ + - Comments & Mentions: किसी भी AI संदेश पर टिप्पणी करें और टीम के साथियों को टैग करें। + +

Comments and Mentions

+ + **कनेक्टर्स और इंटीग्रेशन** + + - Connect & Sync Your Tools: Notion, Slack, Google Drive, Gmail, GitHub, Linear और 25+ स्रोतों को एक खोजने योग्य कॉर्पस में सिंक करें। + +

Connect and Sync Your Tools

+ + - Chat With Uploaded Files: PDF, Office दस्तावेज़, इमेज और ऑडियो अपलोड करें। तुरंत खोजने योग्य। + +

Chat With Uploaded Files

+ + - Connector Write-Back: एजेंट को परिणाम वापस Notion, Slack, Linear और Drive में पोस्ट करने दें। (जल्द आ रहा है) + + - Obsidian & Knowledge Base Sync: अपने Obsidian vault और व्यक्तिगत नॉलेज बेस को सिंक रखें। (जल्द आ रहा है) + + **ऑटोमेशन** + + - AI Document Sorting: फ़ाइलें आते ही AI को उन्हें सही फ़ोल्डरों में अपने-आप व्यवस्थित करने दें। (जल्द आ रहा है) + - Scheduled AI Workflows: किसी एजेंट को शेड्यूल पर चलाएं: रोज़ाना ब्रीफ़, साप्ताहिक डाइजेस्ट, आवर्ती रिपोर्ट। (जल्द आ रहा है) + - Event-Triggered Automations: जैसे ही कोई दस्तावेज़ किसी फ़ोल्डर में आता है, एजेंट को चलाएं और परिणाम अपने टूल में पोस्ट करें। (जल्द आ रहा है) + - Chat-Built Automations: सरल भाषा में किसी ऑटोमेशन का वर्णन करें और SurfSense उसे आपके लिए बना देगा। (जल्द आ रहा है) ### सेल्फ-होस्टेड diff --git a/README.md b/README.md index ab9f9e221..c14e1b441 100644 --- a/README.md +++ b/README.md @@ -77,48 +77,82 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 4. Once everything is indexed, Ask Away (Use Cases): - - Desktop App — General Assist + **Desktop App** (native extras on top of everything below, not a separate feature set) + + - General Assist: launch SurfSense instantly from any application with a global shortcut.

General Assist

- - Desktop App — Quick Assist + - Quick Assist: select text anywhere, then ask AI to explain, rewrite, or act on it.

Quick Assist

- - Desktop App — Screenshot Assist + - Screenshot Assist: capture any region of your screen and ask AI about what's in it.

Screenshot Assist

- - Desktop App — Watch Local Folder + - Watch Local Folder: auto-sync a local folder to your knowledge base. Great for Obsidian vaults.

Watch Local Folder

- - Video Generation + **Deliverable Studio** -

Video Generation

+ - AI Report Generator: generate cited research reports and export to PDF, DOCX, HTML, LaTeX, EPUB, ODT, or plain text. - - Basic search and citation +

AI Report Generator

-

Search and Citation

+ - AI Podcast Generator: turn any document or folder into a two-host AI podcast in under 20 seconds. - - Document Mention QNA +

AI Podcast Generator

-

Document Mention QNA

-

Document Mention QNA

+ - AI Presentation & Video Maker: create editable slide decks and narrated video overviews from your sources. - - Report Generations and Exports (PDF, DOCX, HTML, LaTeX, EPUB, ODT, Plain Text) +

AI Presentation and Video Maker

-

Report Generation

+ - AI Image Generator: generate high-quality images straight from your chats and documents. - - Podcast Generations +

AI Image Generator

-

Podcast Generation

+ - AI Resume Builder: draft and format an ATS-ready resume as a polished PDF. (coming soon) - - Image Generations + **Search & Chat** -

Image Generation

+ - Chat With Your PDFs & Docs: ask questions across all your files and get answers with inline citations. - - And more coming soon. +

Chat With Your PDFs and Docs

+ + - AI Search With Citations: hybrid semantic and keyword search across your entire knowledge base. + +

AI Search With Citations

+ + - Collaborative AI Chat: work on AI conversations with your team in real time. + +

Collaborative AI Chat

+ + - Comments & Mentions: comment and tag teammates on any AI message. + +

Comments and Mentions

+ + **Connectors & Integrations** + + - Connect & Sync Your Tools: sync Notion, Slack, Google Drive, Gmail, GitHub, Linear and 25+ sources into one searchable corpus. + +

Connect and Sync Your Tools

+ + - Chat With Uploaded Files: drop in PDFs, Office docs, images and audio. Instantly searchable. + +

Chat With Uploaded Files

+ + - Connector Write-Back: let the agent post results back to Notion, Slack, Linear and Drive. (coming soon) + + - Obsidian & Knowledge Base Sync: keep your Obsidian vault and personal knowledge base in sync. (coming soon) + + **Automations** + + - AI Document Sorting: let AI automatically organize files into the right folders as they arrive. (coming soon) + - Scheduled AI Workflows: run an agent on a schedule: daily briefs, weekly digests, recurring reports. (coming soon) + - Event-Triggered Automations: fire an agent the moment a document lands in a folder, then post the result to your tools. (coming soon) + - Chat-Built Automations: describe an automation in plain English and SurfSense builds it for you. (coming soon) ### Self Hosted diff --git a/README.pt-BR.md b/README.pt-BR.md index fcb004cd6..e65b9bbdd 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -76,48 +76,82 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 4. Quando tudo estiver indexado, pergunte o que quiser (Casos de uso): - - Aplicativo Desktop — General Assist + **Aplicativo Desktop** (extras nativos, além de tudo o que está abaixo, não um conjunto separado) + + - General Assist: abra o SurfSense instantaneamente de qualquer aplicativo com um atalho global.

General Assist

- - Aplicativo Desktop — Quick Assist + - Quick Assist: selecione um texto em qualquer lugar e peça à IA para explicar, reescrever ou agir sobre ele.

Quick Assist

- - Aplicativo Desktop — Screenshot Assist + - Screenshot Assist: capture qualquer região da tela e pergunte à IA sobre o que está nela.

Screenshot Assist

- - Aplicativo Desktop — Watch Local Folder + - Watch Local Folder: sincronize automaticamente uma pasta local com sua base de conhecimento. Ótimo para cofres do Obsidian.

Watch Local Folder

- - Geração de vídeos + **Estúdio de Entregáveis** -

Geração de Vídeos

+ - AI Report Generator: gere relatórios de pesquisa com citações e exporte para PDF, DOCX, HTML, LaTeX, EPUB, ODT ou texto simples. - - Busca básica e citações +

AI Report Generator

-

Busca e Citação

+ - AI Podcast Generator: transforme qualquer documento ou pasta em um podcast de IA com dois apresentadores em menos de 20 segundos. - - QNA com menção de documentos +

AI Podcast Generator

-

QNA com Menção de Documentos

-

QNA com Menção de Documentos

+ - AI Presentation & Video Maker: crie apresentações editáveis e vídeos narrados a partir das suas fontes. - - Geração de relatórios e exportações (PDF, DOCX, HTML, LaTeX, EPUB, ODT, texto simples) +

AI Presentation and Video Maker

-

Geração de Relatórios

+ - AI Image Generator: gere imagens de alta qualidade diretamente das suas conversas e documentos. - - Geração de podcasts +

AI Image Generator

-

Geração de Podcasts

+ - AI Resume Builder: monte e formate um currículo pronto para ATS em um PDF caprichado. (em breve) - - Geração de imagens + **Busca e Chat** -

Geração de Imagens

+ - Chat With Your PDFs & Docs: faça perguntas sobre todos os seus arquivos e receba respostas com citações inline. - - E mais em breve. +

Chat With Your PDFs and Docs

+ + - AI Search With Citations: busca híbrida semântica e por palavra-chave em toda a sua base de conhecimento. + +

AI Search With Citations

+ + - Collaborative AI Chat: trabalhe em conversas de IA com sua equipe em tempo real. + +

Collaborative AI Chat

+ + - Comments & Mentions: comente e marque colegas em qualquer mensagem de IA. + +

Comments and Mentions

+ + **Conectores e Integrações** + + - Connect & Sync Your Tools: sincronize Notion, Slack, Google Drive, Gmail, GitHub, Linear e mais de 25 fontes em um único acervo pesquisável. + +

Connect and Sync Your Tools

+ + - Chat With Uploaded Files: envie PDFs, documentos do Office, imagens e áudio. Pesquisáveis instantaneamente. + +

Chat With Uploaded Files

+ + - Connector Write-Back: deixe o agente publicar os resultados de volta no Notion, Slack, Linear e Drive. (em breve) + + - Obsidian & Knowledge Base Sync: mantenha seu cofre do Obsidian e sua base de conhecimento pessoal sincronizados. (em breve) + + **Automações** + + - AI Document Sorting: deixe a IA organizar automaticamente os arquivos nas pastas certas conforme eles chegam. (em breve) + - Scheduled AI Workflows: execute um agente em uma programação: resumos diários, boletins semanais, relatórios recorrentes. (em breve) + - Event-Triggered Automations: dispare um agente no momento em que um documento chega a uma pasta e publique o resultado nas suas ferramentas. (em breve) + - Chat-Built Automations: descreva uma automação em linguagem simples e o SurfSense a cria para você. (em breve) ### Auto-Hospedado diff --git a/README.zh-CN.md b/README.zh-CN.md index a07f4afdc..e6f7b48f9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -76,48 +76,82 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 4. 一切索引完成后,尽管提问(使用场景): - - 桌面应用 — General Assist + **桌面应用**(在以下所有功能之外的原生附加功能,并非独立的功能集) + + - General Assist:通过全局快捷键,从任意应用中即刻打开 SurfSense。

General Assist

- - 桌面应用 — Quick Assist + - Quick Assist:在任意位置选中文本,让 AI 解释、改写或对其执行操作。

Quick Assist

- - 桌面应用 — Screenshot Assist + - Screenshot Assist:截取屏幕上任意区域,并就其中内容向 AI 提问。

Screenshot Assist

- - 桌面应用 — Watch Local Folder + - Watch Local Folder:将本地文件夹自动同步到你的知识库。非常适合 Obsidian 库。

Watch Local Folder

- - 视频生成 + **成果工作室** -

视频生成

+ - AI Report Generator:生成带引用的研究报告,并导出为 PDF、DOCX、HTML、LaTeX、EPUB、ODT 或纯文本。 - - 基本搜索和引用 +

AI Report Generator

-

搜索和引用

+ - AI Podcast Generator:在 20 秒内将任意文档或文件夹转换为双主持人 AI 播客。 - - 文档提及问答 +

AI Podcast Generator

-

文档提及问答

-

文档提及问答

+ - AI Presentation & Video Maker:根据你的资料创建可编辑的幻灯片和带旁白的视频概览。 - - 报告生成和导出(PDF、DOCX、HTML、LaTeX、EPUB、ODT、纯文本) +

AI Presentation and Video Maker

-

报告生成

+ - AI Image Generator:直接从你的聊天和文档生成高质量图像。 - - 播客生成 +

AI Image Generator

-

播客生成

+ - AI Resume Builder:撰写并排版一份适配 ATS 的精美 PDF 简历。(即将推出) - - 图像生成 + **搜索与聊天** -

图像生成

+ - Chat With Your PDFs & Docs:跨所有文件提问,并获得带内联引用的答案。 - - 更多功能即将推出。 +

Chat With Your PDFs and Docs

+ + - AI Search With Citations:在整个知识库中进行语义与关键词的混合搜索。 + +

AI Search With Citations

+ + - Collaborative AI Chat:与团队实时协作处理 AI 对话。 + +

Collaborative AI Chat

+ + - Comments & Mentions:在任意 AI 消息上评论并 @ 你的队友。 + +

Comments and Mentions

+ + **连接器与集成** + + - Connect & Sync Your Tools:将 Notion、Slack、Google Drive、Gmail、GitHub、Linear 等 25+ 数据源同步为一个可搜索的语料库。 + +

Connect and Sync Your Tools

+ + - Chat With Uploaded Files:上传 PDF、Office 文档、图像和音频。即刻可搜索。 + +

Chat With Uploaded Files

+ + - Connector Write-Back:让智能体将结果回写到 Notion、Slack、Linear 和 Drive。(即将推出) + + - Obsidian & Knowledge Base Sync:让你的 Obsidian 库与个人知识库保持同步。(即将推出) + + **自动化** + + - AI Document Sorting:在文件到达时,让 AI 自动将其整理到正确的文件夹中。(即将推出) + - Scheduled AI Workflows:按计划运行智能体:每日简报、每周摘要、周期性报告。(即将推出) + - Event-Triggered Automations:在文档进入文件夹的那一刻触发智能体,并将结果发布到你的工具中。(即将推出) + - Chat-Built Automations:用通俗的语言描述一个自动化,SurfSense 就会为你构建它。(即将推出) ### 自托管 From 8005e6facea5700ca5be8f122ab99f8065109d00 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 17:25:27 -0700 Subject: [PATCH 190/198] chore(docs): remove AI Document Sorting from README files across multiple languages - Removed the AI Document Sorting feature description from the README files in Spanish, Hindi, Portuguese, Chinese, and English. - Updated the automations section to streamline content and focus on upcoming features. --- README.es.md | 1 - README.hi.md | 1 - README.md | 1 - README.pt-BR.md | 1 - README.zh-CN.md | 1 - surfsense_web/components/homepage/hero-section.tsx | 7 ------- 6 files changed, 12 deletions(-) diff --git a/README.es.md b/README.es.md index 01bf95993..75f499ab0 100644 --- a/README.es.md +++ b/README.es.md @@ -148,7 +148,6 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 **Automatizaciones** - - AI Document Sorting: deja que la IA organice automáticamente los archivos en las carpetas correctas a medida que llegan. (próximamente) - Scheduled AI Workflows: ejecuta un agente según una programación: resúmenes diarios, boletines semanales, informes recurrentes. (próximamente) - Event-Triggered Automations: lanza un agente en el momento en que un documento llega a una carpeta y publica el resultado en tus herramientas. (próximamente) - Chat-Built Automations: describe una automatización en lenguaje sencillo y SurfSense la crea por ti. (próximamente) diff --git a/README.hi.md b/README.hi.md index c75f9453a..c03e8a21c 100644 --- a/README.hi.md +++ b/README.hi.md @@ -148,7 +148,6 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 **ऑटोमेशन** - - AI Document Sorting: फ़ाइलें आते ही AI को उन्हें सही फ़ोल्डरों में अपने-आप व्यवस्थित करने दें। (जल्द आ रहा है) - Scheduled AI Workflows: किसी एजेंट को शेड्यूल पर चलाएं: रोज़ाना ब्रीफ़, साप्ताहिक डाइजेस्ट, आवर्ती रिपोर्ट। (जल्द आ रहा है) - Event-Triggered Automations: जैसे ही कोई दस्तावेज़ किसी फ़ोल्डर में आता है, एजेंट को चलाएं और परिणाम अपने टूल में पोस्ट करें। (जल्द आ रहा है) - Chat-Built Automations: सरल भाषा में किसी ऑटोमेशन का वर्णन करें और SurfSense उसे आपके लिए बना देगा। (जल्द आ रहा है) diff --git a/README.md b/README.md index c14e1b441..93ad51263 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,6 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 **Automations** - - AI Document Sorting: let AI automatically organize files into the right folders as they arrive. (coming soon) - Scheduled AI Workflows: run an agent on a schedule: daily briefs, weekly digests, recurring reports. (coming soon) - Event-Triggered Automations: fire an agent the moment a document lands in a folder, then post the result to your tools. (coming soon) - Chat-Built Automations: describe an automation in plain English and SurfSense builds it for you. (coming soon) diff --git a/README.pt-BR.md b/README.pt-BR.md index e65b9bbdd..e7112bcaa 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -148,7 +148,6 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 **Automações** - - AI Document Sorting: deixe a IA organizar automaticamente os arquivos nas pastas certas conforme eles chegam. (em breve) - Scheduled AI Workflows: execute um agente em uma programação: resumos diários, boletins semanais, relatórios recorrentes. (em breve) - Event-Triggered Automations: dispare um agente no momento em que um documento chega a uma pasta e publique o resultado nas suas ferramentas. (em breve) - Chat-Built Automations: descreva uma automação em linguagem simples e o SurfSense a cria para você. (em breve) diff --git a/README.zh-CN.md b/README.zh-CN.md index e6f7b48f9..cc73fbe6a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -148,7 +148,6 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 **自动化** - - AI Document Sorting:在文件到达时,让 AI 自动将其整理到正确的文件夹中。(即将推出) - Scheduled AI Workflows:按计划运行智能体:每日简报、每周摘要、周期性报告。(即将推出) - Event-Triggered Automations:在文档进入文件夹的那一刻触发智能体,并将结果发布到你的工具中。(即将推出) - Chat-Built Automations:用通俗的语言描述一个自动化,SurfSense 就会为你构建它。(即将推出) diff --git a/surfsense_web/components/homepage/hero-section.tsx b/surfsense_web/components/homepage/hero-section.tsx index def75b94e..90b0b7c9e 100644 --- a/surfsense_web/components/homepage/hero-section.tsx +++ b/surfsense_web/components/homepage/hero-section.tsx @@ -153,13 +153,6 @@ const CATEGORIES: HeroCategory[] = [ id: "automations", label: "Automations", useCases: [ - { - id: "sort", - title: "AI Document Sorting", - description: "Let AI automatically organize files into the right folders as they arrive.", - src: null, - comingSoon: true, - }, { id: "schedule", title: "Scheduled AI Workflows", From 6b3e34aae0c073c90de67e8549585cb9bef9b430 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 17:30:24 -0700 Subject: [PATCH 191/198] chore(hero-section): remove Obsidian feature from hero section - Deleted the Obsidian & Knowledge Base Sync feature from the hero section to streamline content and focus on currently available features. --- surfsense_web/components/homepage/hero-section.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/surfsense_web/components/homepage/hero-section.tsx b/surfsense_web/components/homepage/hero-section.tsx index 90b0b7c9e..8a831d492 100644 --- a/surfsense_web/components/homepage/hero-section.tsx +++ b/surfsense_web/components/homepage/hero-section.tsx @@ -231,13 +231,6 @@ const CATEGORIES: HeroCategory[] = [ src: null, comingSoon: true, }, - { - id: "obsidian", - title: "Obsidian & Knowledge Base Sync", - description: "Keep your Obsidian vault and personal knowledge base in sync.", - src: null, - comingSoon: true, - }, ], }, ]; From 3b5cc22f94c5bfa157b719221e04732656f7a7d1 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 17:59:49 -0700 Subject: [PATCH 192/198] feat(release): bump version to 0.0.26 - Updated version number to 0.0.26 in VERSION, pyproject.toml, and package.json files for browser, desktop, and web components. - Ensured consistency in versioning across the project. --- VERSION | 2 +- surfsense_backend/pyproject.toml | 2 +- surfsense_backend/uv.lock | 8503 ++++++++--------- surfsense_browser_extension/package.json | 2 +- surfsense_desktop/package.json | 2 +- .../changelog/content/2026-05-31.mdx | 76 + surfsense_web/package.json | 2 +- 7 files changed, 4324 insertions(+), 4265 deletions(-) create mode 100644 surfsense_web/changelog/content/2026-05-31.mdx diff --git a/VERSION b/VERSION index 2678ff8d6..c4475d3bb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.25 +0.0.26 diff --git a/surfsense_backend/pyproject.toml b/surfsense_backend/pyproject.toml index 2ed0acca4..51405ec74 100644 --- a/surfsense_backend/pyproject.toml +++ b/surfsense_backend/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "surf-new-backend" -version = "0.0.25" +version = "0.0.26" description = "SurfSense Backend" requires-python = ">=3.12" dependencies = [ diff --git a/surfsense_backend/uv.lock b/surfsense_backend/uv.lock index ba88153c5..eae54b1d4 100644 --- a/surfsense_backend/uv.lock +++ b/surfsense_backend/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 1 requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14' and python_full_version < '4' and sys_platform == 'win32'", @@ -28,36 +28,36 @@ dependencies = [ { name = "safetensors" }, { name = "torch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/14/787e5498cd062640f0f3d92ef4ae4063174f76f9afd29d13fc52a319daae/accelerate-1.13.0.tar.gz", hash = "sha256:d631b4e0f5b3de4aff2d7e9e6857d164810dfc3237d54d017f075122d057b236", size = 402835, upload-time = "2026-03-04T19:34:12.359Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/14/787e5498cd062640f0f3d92ef4ae4063174f76f9afd29d13fc52a319daae/accelerate-1.13.0.tar.gz", hash = "sha256:d631b4e0f5b3de4aff2d7e9e6857d164810dfc3237d54d017f075122d057b236", size = 402835 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/46/02ac5e262d4af18054b3e922b2baedbb2a03289ee792162de60a865defc5/accelerate-1.13.0-py3-none-any.whl", hash = "sha256:cf1a3efb96c18f7b152eb0fa7490f3710b19c3f395699358f08decca2b8b62e0", size = 383744, upload-time = "2026-03-04T19:34:10.313Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/02ac5e262d4af18054b3e922b2baedbb2a03289ee792162de60a865defc5/accelerate-1.13.0-py3-none-any.whl", hash = "sha256:cf1a3efb96c18f7b152eb0fa7490f3710b19c3f395699358f08decca2b8b62e0", size = 383744 }, ] [[package]] name = "addict" version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/ef/fd7649da8af11d93979831e8f1f8097e85e82d5bfeabc8c68b39175d8e75/addict-2.4.0.tar.gz", hash = "sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494", size = 9186, upload-time = "2020-11-21T16:21:31.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/ef/fd7649da8af11d93979831e8f1f8097e85e82d5bfeabc8c68b39175d8e75/addict-2.4.0.tar.gz", hash = "sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494", size = 9186 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/00/b08f23b7d7e1e14ce01419a467b583edbb93c6cdb8654e54a9cc579cd61f/addict-2.4.0-py3-none-any.whl", hash = "sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc", size = 3832, upload-time = "2020-11-21T16:21:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/6a/00/b08f23b7d7e1e14ce01419a467b583edbb93c6cdb8654e54a9cc579cd61f/addict-2.4.0-py3-none-any.whl", hash = "sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc", size = 3832 }, ] [[package]] name = "aiofiles" version = "24.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, + { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896 }, ] [[package]] name = "aiohappyeyeballs" version = "2.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265 }, ] [[package]] @@ -73,76 +73,76 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/45/4a/064321452809dae953c1ed6e017504e72551a26b6f5708a5a80e4bf556ff/aiohttp-3.13.4.tar.gz", hash = "sha256:d97a6d09c66087890c2ab5d49069e1e570583f7ac0314ecf98294c1b6aaebd38", size = 7859748, upload-time = "2026-03-28T17:19:40.6Z" } +sdist = { url = "https://files.pythonhosted.org/packages/45/4a/064321452809dae953c1ed6e017504e72551a26b6f5708a5a80e4bf556ff/aiohttp-3.13.4.tar.gz", hash = "sha256:d97a6d09c66087890c2ab5d49069e1e570583f7ac0314ecf98294c1b6aaebd38", size = 7859748 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/bd/ede278648914cabbabfdf95e436679b5d4156e417896a9b9f4587169e376/aiohttp-3.13.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ee62d4471ce86b108b19c3364db4b91180d13fe3510144872d6bad5401957360", size = 752158, upload-time = "2026-03-28T17:16:06.901Z" }, - { url = "https://files.pythonhosted.org/packages/90/de/581c053253c07b480b03785196ca5335e3c606a37dc73e95f6527f1591fe/aiohttp-3.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c0fd8f41b54b58636402eb493afd512c23580456f022c1ba2db0f810c959ed0d", size = 501037, upload-time = "2026-03-28T17:16:08.82Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f9/a5ede193c08f13cc42c0a5b50d1e246ecee9115e4cf6e900d8dbd8fd6acb/aiohttp-3.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4baa48ce49efd82d6b1a0be12d6a36b35e5594d1dd42f8bfba96ea9f8678b88c", size = 501556, upload-time = "2026-03-28T17:16:10.63Z" }, - { url = "https://files.pythonhosted.org/packages/d6/10/88ff67cd48a6ec36335b63a640abe86135791544863e0cfe1f065d6cef7a/aiohttp-3.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d738ebab9f71ee652d9dbd0211057690022201b11197f9a7324fd4dba128aa97", size = 1757314, upload-time = "2026-03-28T17:16:12.498Z" }, - { url = "https://files.pythonhosted.org/packages/8b/15/fdb90a5cf5a1f52845c276e76298c75fbbcc0ac2b4a86551906d54529965/aiohttp-3.13.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0ce692c3468fa831af7dceed52edf51ac348cebfc8d3feb935927b63bd3e8576", size = 1731819, upload-time = "2026-03-28T17:16:14.558Z" }, - { url = "https://files.pythonhosted.org/packages/ec/df/28146785a007f7820416be05d4f28cc207493efd1e8c6c1068e9bdc29198/aiohttp-3.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e08abcfe752a454d2cb89ff0c08f2d1ecd057ae3e8cc6d84638de853530ebab", size = 1793279, upload-time = "2026-03-28T17:16:16.594Z" }, - { url = "https://files.pythonhosted.org/packages/10/47/689c743abf62ea7a77774d5722f220e2c912a77d65d368b884d9779ef41b/aiohttp-3.13.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5977f701b3fff36367a11087f30ea73c212e686d41cd363c50c022d48b011d8d", size = 1891082, upload-time = "2026-03-28T17:16:18.71Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b6/f7f4f318c7e58c23b761c9b13b9a3c9b394e0f9d5d76fbc6622fa98509f6/aiohttp-3.13.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54203e10405c06f8b6020bd1e076ae0fe6c194adcee12a5a78af3ffa3c57025e", size = 1773938, upload-time = "2026-03-28T17:16:21.125Z" }, - { url = "https://files.pythonhosted.org/packages/aa/06/f207cb3121852c989586a6fc16ff854c4fcc8651b86c5d3bd1fc83057650/aiohttp-3.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:358a6af0145bc4dda037f13167bef3cce54b132087acc4c295c739d05d16b1c3", size = 1579548, upload-time = "2026-03-28T17:16:23.588Z" }, - { url = "https://files.pythonhosted.org/packages/6c/58/e1289661a32161e24c1fe479711d783067210d266842523752869cc1d9c2/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:898ea1850656d7d61832ef06aa9846ab3ddb1621b74f46de78fbc5e1a586ba83", size = 1714669, upload-time = "2026-03-28T17:16:25.713Z" }, - { url = "https://files.pythonhosted.org/packages/96/0a/3e86d039438a74a86e6a948a9119b22540bae037d6ba317a042ae3c22711/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7bc30cceb710cf6a44e9617e43eebb6e3e43ad855a34da7b4b6a73537d8a6763", size = 1754175, upload-time = "2026-03-28T17:16:28.18Z" }, - { url = "https://files.pythonhosted.org/packages/f4/30/e717fc5df83133ba467a560b6d8ef20197037b4bb5d7075b90037de1018e/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4a31c0c587a8a038f19a4c7e60654a6c899c9de9174593a13e7cc6e15ff271f9", size = 1762049, upload-time = "2026-03-28T17:16:30.941Z" }, - { url = "https://files.pythonhosted.org/packages/e4/28/8f7a2d4492e336e40005151bdd94baf344880a4707573378579f833a64c1/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2062f675f3fe6e06d6113eb74a157fb9df58953ffed0cdb4182554b116545758", size = 1570861, upload-time = "2026-03-28T17:16:32.953Z" }, - { url = "https://files.pythonhosted.org/packages/78/45/12e1a3d0645968b1c38de4b23fdf270b8637735ea057d4f84482ff918ad9/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d1ba8afb847ff80626d5e408c1fdc99f942acc877d0702fe137015903a220a9", size = 1790003, upload-time = "2026-03-28T17:16:35.468Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0f/60374e18d590de16dcb39d6ff62f39c096c1b958e6f37727b5870026ea30/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b08149419994cdd4d5eecf7fd4bc5986b5a9380285bcd01ab4c0d6bfca47b79d", size = 1737289, upload-time = "2026-03-28T17:16:38.187Z" }, - { url = "https://files.pythonhosted.org/packages/02/bf/535e58d886cfbc40a8b0013c974afad24ef7632d645bca0b678b70033a60/aiohttp-3.13.4-cp312-cp312-win32.whl", hash = "sha256:fc432f6a2c4f720180959bc19aa37259651c1a4ed8af8afc84dd41c60f15f791", size = 434185, upload-time = "2026-03-28T17:16:40.735Z" }, - { url = "https://files.pythonhosted.org/packages/1e/1a/d92e3325134ebfff6f4069f270d3aac770d63320bd1fcd0eca023e74d9a8/aiohttp-3.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:6148c9ae97a3e8bff9a1fc9c757fa164116f86c100468339730e717590a3fb77", size = 461285, upload-time = "2026-03-28T17:16:42.713Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ac/892f4162df9b115b4758d615f32ec63d00f3084c705ff5526630887b9b42/aiohttp-3.13.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:63dd5e5b1e43b8fb1e91b79b7ceba1feba588b317d1edff385084fcc7a0a4538", size = 745744, upload-time = "2026-03-28T17:16:44.67Z" }, - { url = "https://files.pythonhosted.org/packages/97/a9/c5b87e4443a2f0ea88cb3000c93a8fdad1ee63bffc9ded8d8c8e0d66efc6/aiohttp-3.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:746ac3cc00b5baea424dacddea3ec2c2702f9590de27d837aa67004db1eebc6e", size = 498178, upload-time = "2026-03-28T17:16:46.766Z" }, - { url = "https://files.pythonhosted.org/packages/94/42/07e1b543a61250783650df13da8ddcdc0d0a5538b2bd15cef6e042aefc61/aiohttp-3.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bda8f16ea99d6a6705e5946732e48487a448be874e54a4f73d514660ff7c05d3", size = 498331, upload-time = "2026-03-28T17:16:48.9Z" }, - { url = "https://files.pythonhosted.org/packages/20/d6/492f46bf0328534124772d0cf58570acae5b286ea25006900650f69dae0e/aiohttp-3.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b061e7b5f840391e3f64d0ddf672973e45c4cfff7a0feea425ea24e51530fc2", size = 1744414, upload-time = "2026-03-28T17:16:50.968Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4d/e02627b2683f68051246215d2d62b2d2f249ff7a285e7a858dc47d6b6a14/aiohttp-3.13.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b252e8d5cd66184b570d0d010de742736e8a4fab22c58299772b0c5a466d4b21", size = 1719226, upload-time = "2026-03-28T17:16:53.173Z" }, - { url = "https://files.pythonhosted.org/packages/7b/6c/5d0a3394dd2b9f9aeba6e1b6065d0439e4b75d41f1fb09a3ec010b43552b/aiohttp-3.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20af8aad61d1803ff11152a26146d8d81c266aa8c5aa9b4504432abb965c36a0", size = 1782110, upload-time = "2026-03-28T17:16:55.362Z" }, - { url = "https://files.pythonhosted.org/packages/0d/2d/c20791e3437700a7441a7edfb59731150322424f5aadf635602d1d326101/aiohttp-3.13.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:13a5cc924b59859ad2adb1478e31f410a7ed46e92a2a619d6d1dd1a63c1a855e", size = 1884809, upload-time = "2026-03-28T17:16:57.734Z" }, - { url = "https://files.pythonhosted.org/packages/c8/94/d99dbfbd1924a87ef643833932eb2a3d9e5eee87656efea7d78058539eff/aiohttp-3.13.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:534913dfb0a644d537aebb4123e7d466d94e3be5549205e6a31f72368980a81a", size = 1764938, upload-time = "2026-03-28T17:17:00.221Z" }, - { url = "https://files.pythonhosted.org/packages/49/61/3ce326a1538781deb89f6cf5e094e2029cd308ed1e21b2ba2278b08426f6/aiohttp-3.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:320e40192a2dcc1cf4b5576936e9652981ab596bf81eb309535db7e2f5b5672f", size = 1570697, upload-time = "2026-03-28T17:17:02.985Z" }, - { url = "https://files.pythonhosted.org/packages/b6/77/4ab5a546857bb3028fbaf34d6eea180267bdab022ee8b1168b1fcde4bfdd/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9e587fcfce2bcf06526a43cb705bdee21ac089096f2e271d75de9c339db3100c", size = 1702258, upload-time = "2026-03-28T17:17:05.28Z" }, - { url = "https://files.pythonhosted.org/packages/79/63/d8f29021e39bc5af8e5d5e9da1b07976fb9846487a784e11e4f4eeda4666/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9eb9c2eea7278206b5c6c1441fdd9dc420c278ead3f3b2cc87f9b693698cc500", size = 1740287, upload-time = "2026-03-28T17:17:07.712Z" }, - { url = "https://files.pythonhosted.org/packages/55/3a/cbc6b3b124859a11bc8055d3682c26999b393531ef926754a3445b99dfef/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:29be00c51972b04bf9d5c8f2d7f7314f48f96070ca40a873a53056e652e805f7", size = 1753011, upload-time = "2026-03-28T17:17:10.053Z" }, - { url = "https://files.pythonhosted.org/packages/e0/30/836278675205d58c1368b21520eab9572457cf19afd23759216c04483048/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90c06228a6c3a7c9f776fe4fc0b7ff647fffd3bed93779a6913c804ae00c1073", size = 1566359, upload-time = "2026-03-28T17:17:12.433Z" }, - { url = "https://files.pythonhosted.org/packages/50/b4/8032cc9b82d17e4277704ba30509eaccb39329dc18d6a35f05e424439e32/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a533ec132f05fd9a1d959e7f34184cd7d5e8511584848dab85faefbaac573069", size = 1785537, upload-time = "2026-03-28T17:17:14.721Z" }, - { url = "https://files.pythonhosted.org/packages/17/7d/5873e98230bde59f493bf1f7c3e327486a4b5653fa401144704df5d00211/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1c946f10f413836f82ea4cfb90200d2a59578c549f00857e03111cf45ad01ca5", size = 1740752, upload-time = "2026-03-28T17:17:17.387Z" }, - { url = "https://files.pythonhosted.org/packages/7b/f2/13e46e0df051494d7d3c68b7f72d071f48c384c12716fc294f75d5b1a064/aiohttp-3.13.4-cp313-cp313-win32.whl", hash = "sha256:48708e2706106da6967eff5908c78ca3943f005ed6bcb75da2a7e4da94ef8c70", size = 433187, upload-time = "2026-03-28T17:17:19.523Z" }, - { url = "https://files.pythonhosted.org/packages/ea/c0/649856ee655a843c8f8664592cfccb73ac80ede6a8c8db33a25d810c12db/aiohttp-3.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:74a2eb058da44fa3a877a49e2095b591d4913308bb424c418b77beb160c55ce3", size = 459778, upload-time = "2026-03-28T17:17:21.964Z" }, - { url = "https://files.pythonhosted.org/packages/6d/29/6657cc37ae04cacc2dbf53fb730a06b6091cc4cbe745028e047c53e6d840/aiohttp-3.13.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:e0a2c961fc92abeff61d6444f2ce6ad35bb982db9fc8ff8a47455beacf454a57", size = 749363, upload-time = "2026-03-28T17:17:24.044Z" }, - { url = "https://files.pythonhosted.org/packages/90/7f/30ccdf67ca3d24b610067dc63d64dcb91e5d88e27667811640644aa4a85d/aiohttp-3.13.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:153274535985a0ff2bff1fb6c104ed547cec898a09213d21b0f791a44b14d933", size = 499317, upload-time = "2026-03-28T17:17:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/93/13/e372dd4e68ad04ee25dafb050c7f98b0d91ea643f7352757e87231102555/aiohttp-3.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:351f3171e2458da3d731ce83f9e6b9619e325c45cbd534c7759750cabf453ad7", size = 500477, upload-time = "2026-03-28T17:17:28.279Z" }, - { url = "https://files.pythonhosted.org/packages/e5/fe/ee6298e8e586096fb6f5eddd31393d8544f33ae0792c71ecbb4c2bef98ac/aiohttp-3.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f989ac8bc5595ff761a5ccd32bdb0768a117f36dd1504b1c2c074ed5d3f4df9c", size = 1737227, upload-time = "2026-03-28T17:17:30.587Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b9/a7a0463a09e1a3fe35100f74324f23644bfc3383ac5fd5effe0722a5f0b7/aiohttp-3.13.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d36fc1709110ec1e87a229b201dd3ddc32aa01e98e7868083a794609b081c349", size = 1694036, upload-time = "2026-03-28T17:17:33.29Z" }, - { url = "https://files.pythonhosted.org/packages/57/7c/8972ae3fb7be00a91aee6b644b2a6a909aedb2c425269a3bfd90115e6f8f/aiohttp-3.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42adaeea83cbdf069ab94f5103ce0787c21fb1a0153270da76b59d5578302329", size = 1786814, upload-time = "2026-03-28T17:17:36.035Z" }, - { url = "https://files.pythonhosted.org/packages/93/01/c81e97e85c774decbaf0d577de7d848934e8166a3a14ad9f8aa5be329d28/aiohttp-3.13.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:92deb95469928cc41fd4b42a95d8012fa6df93f6b1c0a83af0ffbc4a5e218cde", size = 1866676, upload-time = "2026-03-28T17:17:38.441Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5f/5b46fe8694a639ddea2cd035bf5729e4677ea882cb251396637e2ef1590d/aiohttp-3.13.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0c7c07c4257ef3a1df355f840bc62d133bcdef5c1c5ba75add3c08553e2eed", size = 1740842, upload-time = "2026-03-28T17:17:40.783Z" }, - { url = "https://files.pythonhosted.org/packages/20/a2/0d4b03d011cca6b6b0acba8433193c1e484efa8d705ea58295590fe24203/aiohttp-3.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f062c45de8a1098cb137a1898819796a2491aec4e637a06b03f149315dff4d8f", size = 1566508, upload-time = "2026-03-28T17:17:43.235Z" }, - { url = "https://files.pythonhosted.org/packages/98/17/e689fd500da52488ec5f889effd6404dece6a59de301e380f3c64f167beb/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:76093107c531517001114f0ebdb4f46858ce818590363e3e99a4a2280334454a", size = 1700569, upload-time = "2026-03-28T17:17:46.165Z" }, - { url = "https://files.pythonhosted.org/packages/d8/0d/66402894dbcf470ef7db99449e436105ea862c24f7ea4c95c683e635af35/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6f6ec32162d293b82f8b63a16edc80769662fbd5ae6fbd4936d3206a2c2cc63b", size = 1707407, upload-time = "2026-03-28T17:17:48.825Z" }, - { url = "https://files.pythonhosted.org/packages/2f/eb/af0ab1a3650092cbd8e14ef29e4ab0209e1460e1c299996c3f8288b3f1ff/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5903e2db3d202a00ad9f0ec35a122c005e85d90c9836ab4cda628f01edf425e2", size = 1752214, upload-time = "2026-03-28T17:17:51.206Z" }, - { url = "https://files.pythonhosted.org/packages/5a/bf/72326f8a98e4c666f292f03c385545963cc65e358835d2a7375037a97b57/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2d5bea57be7aca98dbbac8da046d99b5557c5cf4e28538c4c786313078aca09e", size = 1562162, upload-time = "2026-03-28T17:17:53.634Z" }, - { url = "https://files.pythonhosted.org/packages/67/9f/13b72435f99151dd9a5469c96b3b5f86aa29b7e785ca7f35cf5e538f74c0/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:bcf0c9902085976edc0232b75006ef38f89686901249ce14226b6877f88464fb", size = 1768904, upload-time = "2026-03-28T17:17:55.991Z" }, - { url = "https://files.pythonhosted.org/packages/18/bc/28d4970e7d5452ac7776cdb5431a1164a0d9cf8bd2fffd67b4fb463aa56d/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3295f98bfeed2e867cab588f2a146a9db37a85e3ae9062abf46ba062bd29165", size = 1723378, upload-time = "2026-03-28T17:17:58.348Z" }, - { url = "https://files.pythonhosted.org/packages/53/74/b32458ca1a7f34d65bdee7aef2036adbe0438123d3d53e2b083c453c24dd/aiohttp-3.13.4-cp314-cp314-win32.whl", hash = "sha256:a598a5c5767e1369d8f5b08695cab1d8160040f796c4416af76fd773d229b3c9", size = 438711, upload-time = "2026-03-28T17:18:00.728Z" }, - { url = "https://files.pythonhosted.org/packages/40/b2/54b487316c2df3e03a8f3435e9636f8a81a42a69d942164830d193beb56a/aiohttp-3.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:c555db4bc7a264bead5a7d63d92d41a1122fcd39cc62a4db815f45ad46f9c2c8", size = 464977, upload-time = "2026-03-28T17:18:03.367Z" }, - { url = "https://files.pythonhosted.org/packages/47/fb/e41b63c6ce71b07a59243bb8f3b457ee0c3402a619acb9d2c0d21ef0e647/aiohttp-3.13.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45abbbf09a129825d13c18c7d3182fecd46d9da3cfc383756145394013604ac1", size = 781549, upload-time = "2026-03-28T17:18:05.779Z" }, - { url = "https://files.pythonhosted.org/packages/97/53/532b8d28df1e17e44c4d9a9368b78dcb6bf0b51037522136eced13afa9e8/aiohttp-3.13.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:74c80b2bc2c2adb7b3d1941b2b60701ee2af8296fc8aad8b8bc48bc25767266c", size = 514383, upload-time = "2026-03-28T17:18:08.096Z" }, - { url = "https://files.pythonhosted.org/packages/1b/1f/62e5d400603e8468cd635812d99cb81cfdc08127a3dc474c647615f31339/aiohttp-3.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c97989ae40a9746650fa196894f317dafc12227c808c774929dda0ff873a5954", size = 518304, upload-time = "2026-03-28T17:18:10.642Z" }, - { url = "https://files.pythonhosted.org/packages/90/57/2326b37b10896447e3c6e0cbef4fe2486d30913639a5cfd1332b5d870f82/aiohttp-3.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dae86be9811493f9990ef44fff1685f5c1a3192e9061a71a109d527944eed551", size = 1893433, upload-time = "2026-03-28T17:18:13.121Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b4/a24d82112c304afdb650167ef2fe190957d81cbddac7460bedd245f765aa/aiohttp-3.13.4-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1db491abe852ca2fa6cc48a3341985b0174b3741838e1341b82ac82c8bd9e871", size = 1755901, upload-time = "2026-03-28T17:18:16.21Z" }, - { url = "https://files.pythonhosted.org/packages/9e/2d/0883ef9d878d7846287f036c162a951968f22aabeef3ac97b0bea6f76d5d/aiohttp-3.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e5d701c0aad02a7dce72eef6b93226cf3734330f1a31d69ebbf69f33b86666e", size = 1876093, upload-time = "2026-03-28T17:18:18.703Z" }, - { url = "https://files.pythonhosted.org/packages/ad/52/9204bb59c014869b71971addad6778f005daa72a96eed652c496789d7468/aiohttp-3.13.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8ac32a189081ae0a10ba18993f10f338ec94341f0d5df8fff348043962f3c6f8", size = 1970815, upload-time = "2026-03-28T17:18:21.858Z" }, - { url = "https://files.pythonhosted.org/packages/d6/b5/e4eb20275a866dde0f570f411b36c6b48f7b53edfe4f4071aa1b0728098a/aiohttp-3.13.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98e968cdaba43e45c73c3f306fca418c8009a957733bac85937c9f9cf3f4de27", size = 1816223, upload-time = "2026-03-28T17:18:24.729Z" }, - { url = "https://files.pythonhosted.org/packages/d8/23/e98075c5bb146aa61a1239ee1ac7714c85e814838d6cebbe37d3fe19214a/aiohttp-3.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca114790c9144c335d538852612d3e43ea0f075288f4849cf4b05d6cd2238ce7", size = 1649145, upload-time = "2026-03-28T17:18:27.269Z" }, - { url = "https://files.pythonhosted.org/packages/d6/c1/7bad8be33bb06c2bb224b6468874346026092762cbec388c3bdb65a368ee/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ea2e071661ba9cfe11eabbc81ac5376eaeb3061f6e72ec4cc86d7cdd1ffbdbbb", size = 1816562, upload-time = "2026-03-28T17:18:29.847Z" }, - { url = "https://files.pythonhosted.org/packages/5c/10/c00323348695e9a5e316825969c88463dcc24c7e9d443244b8a2c9cf2eae/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:34e89912b6c20e0fd80e07fa401fd218a410aa1ce9f1c2f1dad6db1bd0ce0927", size = 1800333, upload-time = "2026-03-28T17:18:32.269Z" }, - { url = "https://files.pythonhosted.org/packages/84/43/9b2147a1df3559f49bd723e22905b46a46c068a53adb54abdca32c4de180/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0e217cf9f6a42908c52b46e42c568bd57adc39c9286ced31aaace614b6087965", size = 1820617, upload-time = "2026-03-28T17:18:35.238Z" }, - { url = "https://files.pythonhosted.org/packages/a9/7f/b3481a81e7a586d02e99387b18c6dafff41285f6efd3daa2124c01f87eae/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:0c296f1221e21ba979f5ac1964c3b78cfde15c5c5f855ffd2caab337e9cd9182", size = 1643417, upload-time = "2026-03-28T17:18:37.949Z" }, - { url = "https://files.pythonhosted.org/packages/8f/72/07181226bc99ce1124e0f89280f5221a82d3ae6a6d9d1973ce429d48e52b/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d99a9d168ebaffb74f36d011750e490085ac418f4db926cce3989c8fe6cb6b1b", size = 1849286, upload-time = "2026-03-28T17:18:40.534Z" }, - { url = "https://files.pythonhosted.org/packages/1a/e6/1b3566e103eca6da5be4ae6713e112a053725c584e96574caf117568ffef/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cb19177205d93b881f3f89e6081593676043a6828f59c78c17a0fd6c1fbed2ba", size = 1782635, upload-time = "2026-03-28T17:18:43.073Z" }, - { url = "https://files.pythonhosted.org/packages/37/58/1b11c71904b8d079eb0c39fe664180dd1e14bebe5608e235d8bfbadc8929/aiohttp-3.13.4-cp314-cp314t-win32.whl", hash = "sha256:c606aa5656dab6552e52ca368e43869c916338346bfaf6304e15c58fb113ea30", size = 472537, upload-time = "2026-03-28T17:18:46.286Z" }, - { url = "https://files.pythonhosted.org/packages/bc/8f/87c56a1a1977d7dddea5b31e12189665a140fdb48a71e9038ff90bb564ec/aiohttp-3.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:014dcc10ec8ab8db681f0d68e939d1e9286a5aa2b993cbbdb0db130853e02144", size = 506381, upload-time = "2026-03-28T17:18:48.74Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bd/ede278648914cabbabfdf95e436679b5d4156e417896a9b9f4587169e376/aiohttp-3.13.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ee62d4471ce86b108b19c3364db4b91180d13fe3510144872d6bad5401957360", size = 752158 }, + { url = "https://files.pythonhosted.org/packages/90/de/581c053253c07b480b03785196ca5335e3c606a37dc73e95f6527f1591fe/aiohttp-3.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c0fd8f41b54b58636402eb493afd512c23580456f022c1ba2db0f810c959ed0d", size = 501037 }, + { url = "https://files.pythonhosted.org/packages/fa/f9/a5ede193c08f13cc42c0a5b50d1e246ecee9115e4cf6e900d8dbd8fd6acb/aiohttp-3.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4baa48ce49efd82d6b1a0be12d6a36b35e5594d1dd42f8bfba96ea9f8678b88c", size = 501556 }, + { url = "https://files.pythonhosted.org/packages/d6/10/88ff67cd48a6ec36335b63a640abe86135791544863e0cfe1f065d6cef7a/aiohttp-3.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d738ebab9f71ee652d9dbd0211057690022201b11197f9a7324fd4dba128aa97", size = 1757314 }, + { url = "https://files.pythonhosted.org/packages/8b/15/fdb90a5cf5a1f52845c276e76298c75fbbcc0ac2b4a86551906d54529965/aiohttp-3.13.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0ce692c3468fa831af7dceed52edf51ac348cebfc8d3feb935927b63bd3e8576", size = 1731819 }, + { url = "https://files.pythonhosted.org/packages/ec/df/28146785a007f7820416be05d4f28cc207493efd1e8c6c1068e9bdc29198/aiohttp-3.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e08abcfe752a454d2cb89ff0c08f2d1ecd057ae3e8cc6d84638de853530ebab", size = 1793279 }, + { url = "https://files.pythonhosted.org/packages/10/47/689c743abf62ea7a77774d5722f220e2c912a77d65d368b884d9779ef41b/aiohttp-3.13.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5977f701b3fff36367a11087f30ea73c212e686d41cd363c50c022d48b011d8d", size = 1891082 }, + { url = "https://files.pythonhosted.org/packages/b0/b6/f7f4f318c7e58c23b761c9b13b9a3c9b394e0f9d5d76fbc6622fa98509f6/aiohttp-3.13.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54203e10405c06f8b6020bd1e076ae0fe6c194adcee12a5a78af3ffa3c57025e", size = 1773938 }, + { url = "https://files.pythonhosted.org/packages/aa/06/f207cb3121852c989586a6fc16ff854c4fcc8651b86c5d3bd1fc83057650/aiohttp-3.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:358a6af0145bc4dda037f13167bef3cce54b132087acc4c295c739d05d16b1c3", size = 1579548 }, + { url = "https://files.pythonhosted.org/packages/6c/58/e1289661a32161e24c1fe479711d783067210d266842523752869cc1d9c2/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:898ea1850656d7d61832ef06aa9846ab3ddb1621b74f46de78fbc5e1a586ba83", size = 1714669 }, + { url = "https://files.pythonhosted.org/packages/96/0a/3e86d039438a74a86e6a948a9119b22540bae037d6ba317a042ae3c22711/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7bc30cceb710cf6a44e9617e43eebb6e3e43ad855a34da7b4b6a73537d8a6763", size = 1754175 }, + { url = "https://files.pythonhosted.org/packages/f4/30/e717fc5df83133ba467a560b6d8ef20197037b4bb5d7075b90037de1018e/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4a31c0c587a8a038f19a4c7e60654a6c899c9de9174593a13e7cc6e15ff271f9", size = 1762049 }, + { url = "https://files.pythonhosted.org/packages/e4/28/8f7a2d4492e336e40005151bdd94baf344880a4707573378579f833a64c1/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2062f675f3fe6e06d6113eb74a157fb9df58953ffed0cdb4182554b116545758", size = 1570861 }, + { url = "https://files.pythonhosted.org/packages/78/45/12e1a3d0645968b1c38de4b23fdf270b8637735ea057d4f84482ff918ad9/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d1ba8afb847ff80626d5e408c1fdc99f942acc877d0702fe137015903a220a9", size = 1790003 }, + { url = "https://files.pythonhosted.org/packages/eb/0f/60374e18d590de16dcb39d6ff62f39c096c1b958e6f37727b5870026ea30/aiohttp-3.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b08149419994cdd4d5eecf7fd4bc5986b5a9380285bcd01ab4c0d6bfca47b79d", size = 1737289 }, + { url = "https://files.pythonhosted.org/packages/02/bf/535e58d886cfbc40a8b0013c974afad24ef7632d645bca0b678b70033a60/aiohttp-3.13.4-cp312-cp312-win32.whl", hash = "sha256:fc432f6a2c4f720180959bc19aa37259651c1a4ed8af8afc84dd41c60f15f791", size = 434185 }, + { url = "https://files.pythonhosted.org/packages/1e/1a/d92e3325134ebfff6f4069f270d3aac770d63320bd1fcd0eca023e74d9a8/aiohttp-3.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:6148c9ae97a3e8bff9a1fc9c757fa164116f86c100468339730e717590a3fb77", size = 461285 }, + { url = "https://files.pythonhosted.org/packages/e3/ac/892f4162df9b115b4758d615f32ec63d00f3084c705ff5526630887b9b42/aiohttp-3.13.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:63dd5e5b1e43b8fb1e91b79b7ceba1feba588b317d1edff385084fcc7a0a4538", size = 745744 }, + { url = "https://files.pythonhosted.org/packages/97/a9/c5b87e4443a2f0ea88cb3000c93a8fdad1ee63bffc9ded8d8c8e0d66efc6/aiohttp-3.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:746ac3cc00b5baea424dacddea3ec2c2702f9590de27d837aa67004db1eebc6e", size = 498178 }, + { url = "https://files.pythonhosted.org/packages/94/42/07e1b543a61250783650df13da8ddcdc0d0a5538b2bd15cef6e042aefc61/aiohttp-3.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bda8f16ea99d6a6705e5946732e48487a448be874e54a4f73d514660ff7c05d3", size = 498331 }, + { url = "https://files.pythonhosted.org/packages/20/d6/492f46bf0328534124772d0cf58570acae5b286ea25006900650f69dae0e/aiohttp-3.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b061e7b5f840391e3f64d0ddf672973e45c4cfff7a0feea425ea24e51530fc2", size = 1744414 }, + { url = "https://files.pythonhosted.org/packages/e2/4d/e02627b2683f68051246215d2d62b2d2f249ff7a285e7a858dc47d6b6a14/aiohttp-3.13.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b252e8d5cd66184b570d0d010de742736e8a4fab22c58299772b0c5a466d4b21", size = 1719226 }, + { url = "https://files.pythonhosted.org/packages/7b/6c/5d0a3394dd2b9f9aeba6e1b6065d0439e4b75d41f1fb09a3ec010b43552b/aiohttp-3.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20af8aad61d1803ff11152a26146d8d81c266aa8c5aa9b4504432abb965c36a0", size = 1782110 }, + { url = "https://files.pythonhosted.org/packages/0d/2d/c20791e3437700a7441a7edfb59731150322424f5aadf635602d1d326101/aiohttp-3.13.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:13a5cc924b59859ad2adb1478e31f410a7ed46e92a2a619d6d1dd1a63c1a855e", size = 1884809 }, + { url = "https://files.pythonhosted.org/packages/c8/94/d99dbfbd1924a87ef643833932eb2a3d9e5eee87656efea7d78058539eff/aiohttp-3.13.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:534913dfb0a644d537aebb4123e7d466d94e3be5549205e6a31f72368980a81a", size = 1764938 }, + { url = "https://files.pythonhosted.org/packages/49/61/3ce326a1538781deb89f6cf5e094e2029cd308ed1e21b2ba2278b08426f6/aiohttp-3.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:320e40192a2dcc1cf4b5576936e9652981ab596bf81eb309535db7e2f5b5672f", size = 1570697 }, + { url = "https://files.pythonhosted.org/packages/b6/77/4ab5a546857bb3028fbaf34d6eea180267bdab022ee8b1168b1fcde4bfdd/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9e587fcfce2bcf06526a43cb705bdee21ac089096f2e271d75de9c339db3100c", size = 1702258 }, + { url = "https://files.pythonhosted.org/packages/79/63/d8f29021e39bc5af8e5d5e9da1b07976fb9846487a784e11e4f4eeda4666/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9eb9c2eea7278206b5c6c1441fdd9dc420c278ead3f3b2cc87f9b693698cc500", size = 1740287 }, + { url = "https://files.pythonhosted.org/packages/55/3a/cbc6b3b124859a11bc8055d3682c26999b393531ef926754a3445b99dfef/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:29be00c51972b04bf9d5c8f2d7f7314f48f96070ca40a873a53056e652e805f7", size = 1753011 }, + { url = "https://files.pythonhosted.org/packages/e0/30/836278675205d58c1368b21520eab9572457cf19afd23759216c04483048/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90c06228a6c3a7c9f776fe4fc0b7ff647fffd3bed93779a6913c804ae00c1073", size = 1566359 }, + { url = "https://files.pythonhosted.org/packages/50/b4/8032cc9b82d17e4277704ba30509eaccb39329dc18d6a35f05e424439e32/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a533ec132f05fd9a1d959e7f34184cd7d5e8511584848dab85faefbaac573069", size = 1785537 }, + { url = "https://files.pythonhosted.org/packages/17/7d/5873e98230bde59f493bf1f7c3e327486a4b5653fa401144704df5d00211/aiohttp-3.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1c946f10f413836f82ea4cfb90200d2a59578c549f00857e03111cf45ad01ca5", size = 1740752 }, + { url = "https://files.pythonhosted.org/packages/7b/f2/13e46e0df051494d7d3c68b7f72d071f48c384c12716fc294f75d5b1a064/aiohttp-3.13.4-cp313-cp313-win32.whl", hash = "sha256:48708e2706106da6967eff5908c78ca3943f005ed6bcb75da2a7e4da94ef8c70", size = 433187 }, + { url = "https://files.pythonhosted.org/packages/ea/c0/649856ee655a843c8f8664592cfccb73ac80ede6a8c8db33a25d810c12db/aiohttp-3.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:74a2eb058da44fa3a877a49e2095b591d4913308bb424c418b77beb160c55ce3", size = 459778 }, + { url = "https://files.pythonhosted.org/packages/6d/29/6657cc37ae04cacc2dbf53fb730a06b6091cc4cbe745028e047c53e6d840/aiohttp-3.13.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:e0a2c961fc92abeff61d6444f2ce6ad35bb982db9fc8ff8a47455beacf454a57", size = 749363 }, + { url = "https://files.pythonhosted.org/packages/90/7f/30ccdf67ca3d24b610067dc63d64dcb91e5d88e27667811640644aa4a85d/aiohttp-3.13.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:153274535985a0ff2bff1fb6c104ed547cec898a09213d21b0f791a44b14d933", size = 499317 }, + { url = "https://files.pythonhosted.org/packages/93/13/e372dd4e68ad04ee25dafb050c7f98b0d91ea643f7352757e87231102555/aiohttp-3.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:351f3171e2458da3d731ce83f9e6b9619e325c45cbd534c7759750cabf453ad7", size = 500477 }, + { url = "https://files.pythonhosted.org/packages/e5/fe/ee6298e8e586096fb6f5eddd31393d8544f33ae0792c71ecbb4c2bef98ac/aiohttp-3.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f989ac8bc5595ff761a5ccd32bdb0768a117f36dd1504b1c2c074ed5d3f4df9c", size = 1737227 }, + { url = "https://files.pythonhosted.org/packages/b0/b9/a7a0463a09e1a3fe35100f74324f23644bfc3383ac5fd5effe0722a5f0b7/aiohttp-3.13.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d36fc1709110ec1e87a229b201dd3ddc32aa01e98e7868083a794609b081c349", size = 1694036 }, + { url = "https://files.pythonhosted.org/packages/57/7c/8972ae3fb7be00a91aee6b644b2a6a909aedb2c425269a3bfd90115e6f8f/aiohttp-3.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42adaeea83cbdf069ab94f5103ce0787c21fb1a0153270da76b59d5578302329", size = 1786814 }, + { url = "https://files.pythonhosted.org/packages/93/01/c81e97e85c774decbaf0d577de7d848934e8166a3a14ad9f8aa5be329d28/aiohttp-3.13.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:92deb95469928cc41fd4b42a95d8012fa6df93f6b1c0a83af0ffbc4a5e218cde", size = 1866676 }, + { url = "https://files.pythonhosted.org/packages/5a/5f/5b46fe8694a639ddea2cd035bf5729e4677ea882cb251396637e2ef1590d/aiohttp-3.13.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0c7c07c4257ef3a1df355f840bc62d133bcdef5c1c5ba75add3c08553e2eed", size = 1740842 }, + { url = "https://files.pythonhosted.org/packages/20/a2/0d4b03d011cca6b6b0acba8433193c1e484efa8d705ea58295590fe24203/aiohttp-3.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f062c45de8a1098cb137a1898819796a2491aec4e637a06b03f149315dff4d8f", size = 1566508 }, + { url = "https://files.pythonhosted.org/packages/98/17/e689fd500da52488ec5f889effd6404dece6a59de301e380f3c64f167beb/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:76093107c531517001114f0ebdb4f46858ce818590363e3e99a4a2280334454a", size = 1700569 }, + { url = "https://files.pythonhosted.org/packages/d8/0d/66402894dbcf470ef7db99449e436105ea862c24f7ea4c95c683e635af35/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6f6ec32162d293b82f8b63a16edc80769662fbd5ae6fbd4936d3206a2c2cc63b", size = 1707407 }, + { url = "https://files.pythonhosted.org/packages/2f/eb/af0ab1a3650092cbd8e14ef29e4ab0209e1460e1c299996c3f8288b3f1ff/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5903e2db3d202a00ad9f0ec35a122c005e85d90c9836ab4cda628f01edf425e2", size = 1752214 }, + { url = "https://files.pythonhosted.org/packages/5a/bf/72326f8a98e4c666f292f03c385545963cc65e358835d2a7375037a97b57/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2d5bea57be7aca98dbbac8da046d99b5557c5cf4e28538c4c786313078aca09e", size = 1562162 }, + { url = "https://files.pythonhosted.org/packages/67/9f/13b72435f99151dd9a5469c96b3b5f86aa29b7e785ca7f35cf5e538f74c0/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:bcf0c9902085976edc0232b75006ef38f89686901249ce14226b6877f88464fb", size = 1768904 }, + { url = "https://files.pythonhosted.org/packages/18/bc/28d4970e7d5452ac7776cdb5431a1164a0d9cf8bd2fffd67b4fb463aa56d/aiohttp-3.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3295f98bfeed2e867cab588f2a146a9db37a85e3ae9062abf46ba062bd29165", size = 1723378 }, + { url = "https://files.pythonhosted.org/packages/53/74/b32458ca1a7f34d65bdee7aef2036adbe0438123d3d53e2b083c453c24dd/aiohttp-3.13.4-cp314-cp314-win32.whl", hash = "sha256:a598a5c5767e1369d8f5b08695cab1d8160040f796c4416af76fd773d229b3c9", size = 438711 }, + { url = "https://files.pythonhosted.org/packages/40/b2/54b487316c2df3e03a8f3435e9636f8a81a42a69d942164830d193beb56a/aiohttp-3.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:c555db4bc7a264bead5a7d63d92d41a1122fcd39cc62a4db815f45ad46f9c2c8", size = 464977 }, + { url = "https://files.pythonhosted.org/packages/47/fb/e41b63c6ce71b07a59243bb8f3b457ee0c3402a619acb9d2c0d21ef0e647/aiohttp-3.13.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45abbbf09a129825d13c18c7d3182fecd46d9da3cfc383756145394013604ac1", size = 781549 }, + { url = "https://files.pythonhosted.org/packages/97/53/532b8d28df1e17e44c4d9a9368b78dcb6bf0b51037522136eced13afa9e8/aiohttp-3.13.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:74c80b2bc2c2adb7b3d1941b2b60701ee2af8296fc8aad8b8bc48bc25767266c", size = 514383 }, + { url = "https://files.pythonhosted.org/packages/1b/1f/62e5d400603e8468cd635812d99cb81cfdc08127a3dc474c647615f31339/aiohttp-3.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c97989ae40a9746650fa196894f317dafc12227c808c774929dda0ff873a5954", size = 518304 }, + { url = "https://files.pythonhosted.org/packages/90/57/2326b37b10896447e3c6e0cbef4fe2486d30913639a5cfd1332b5d870f82/aiohttp-3.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dae86be9811493f9990ef44fff1685f5c1a3192e9061a71a109d527944eed551", size = 1893433 }, + { url = "https://files.pythonhosted.org/packages/d2/b4/a24d82112c304afdb650167ef2fe190957d81cbddac7460bedd245f765aa/aiohttp-3.13.4-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1db491abe852ca2fa6cc48a3341985b0174b3741838e1341b82ac82c8bd9e871", size = 1755901 }, + { url = "https://files.pythonhosted.org/packages/9e/2d/0883ef9d878d7846287f036c162a951968f22aabeef3ac97b0bea6f76d5d/aiohttp-3.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e5d701c0aad02a7dce72eef6b93226cf3734330f1a31d69ebbf69f33b86666e", size = 1876093 }, + { url = "https://files.pythonhosted.org/packages/ad/52/9204bb59c014869b71971addad6778f005daa72a96eed652c496789d7468/aiohttp-3.13.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8ac32a189081ae0a10ba18993f10f338ec94341f0d5df8fff348043962f3c6f8", size = 1970815 }, + { url = "https://files.pythonhosted.org/packages/d6/b5/e4eb20275a866dde0f570f411b36c6b48f7b53edfe4f4071aa1b0728098a/aiohttp-3.13.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98e968cdaba43e45c73c3f306fca418c8009a957733bac85937c9f9cf3f4de27", size = 1816223 }, + { url = "https://files.pythonhosted.org/packages/d8/23/e98075c5bb146aa61a1239ee1ac7714c85e814838d6cebbe37d3fe19214a/aiohttp-3.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca114790c9144c335d538852612d3e43ea0f075288f4849cf4b05d6cd2238ce7", size = 1649145 }, + { url = "https://files.pythonhosted.org/packages/d6/c1/7bad8be33bb06c2bb224b6468874346026092762cbec388c3bdb65a368ee/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ea2e071661ba9cfe11eabbc81ac5376eaeb3061f6e72ec4cc86d7cdd1ffbdbbb", size = 1816562 }, + { url = "https://files.pythonhosted.org/packages/5c/10/c00323348695e9a5e316825969c88463dcc24c7e9d443244b8a2c9cf2eae/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:34e89912b6c20e0fd80e07fa401fd218a410aa1ce9f1c2f1dad6db1bd0ce0927", size = 1800333 }, + { url = "https://files.pythonhosted.org/packages/84/43/9b2147a1df3559f49bd723e22905b46a46c068a53adb54abdca32c4de180/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0e217cf9f6a42908c52b46e42c568bd57adc39c9286ced31aaace614b6087965", size = 1820617 }, + { url = "https://files.pythonhosted.org/packages/a9/7f/b3481a81e7a586d02e99387b18c6dafff41285f6efd3daa2124c01f87eae/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:0c296f1221e21ba979f5ac1964c3b78cfde15c5c5f855ffd2caab337e9cd9182", size = 1643417 }, + { url = "https://files.pythonhosted.org/packages/8f/72/07181226bc99ce1124e0f89280f5221a82d3ae6a6d9d1973ce429d48e52b/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d99a9d168ebaffb74f36d011750e490085ac418f4db926cce3989c8fe6cb6b1b", size = 1849286 }, + { url = "https://files.pythonhosted.org/packages/1a/e6/1b3566e103eca6da5be4ae6713e112a053725c584e96574caf117568ffef/aiohttp-3.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cb19177205d93b881f3f89e6081593676043a6828f59c78c17a0fd6c1fbed2ba", size = 1782635 }, + { url = "https://files.pythonhosted.org/packages/37/58/1b11c71904b8d079eb0c39fe664180dd1e14bebe5608e235d8bfbadc8929/aiohttp-3.13.4-cp314-cp314t-win32.whl", hash = "sha256:c606aa5656dab6552e52ca368e43869c916338346bfaf6304e15c58fb113ea30", size = 472537 }, + { url = "https://files.pythonhosted.org/packages/bc/8f/87c56a1a1977d7dddea5b31e12189665a140fdb48a71e9038ff90bb564ec/aiohttp-3.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:014dcc10ec8ab8db681f0d68e939d1e9286a5aa2b993cbbdb0db130853e02144", size = 506381 }, ] [[package]] @@ -152,18 +152,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/61/ebda4d8e3d8cfa1fd3db0fb428db2dd7461d5742cea35178277ad180b033/aiohttp_retry-2.9.1.tar.gz", hash = "sha256:8eb75e904ed4ee5c2ec242fefe85bf04240f685391c4879d8f541d6028ff01f1", size = 13608, upload-time = "2024-11-06T10:44:54.574Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/61/ebda4d8e3d8cfa1fd3db0fb428db2dd7461d5742cea35178277ad180b033/aiohttp_retry-2.9.1.tar.gz", hash = "sha256:8eb75e904ed4ee5c2ec242fefe85bf04240f685391c4879d8f541d6028ff01f1", size = 13608 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/99/84ba7273339d0f3dfa57901b846489d2e5c2cd731470167757f1935fffbd/aiohttp_retry-2.9.1-py3-none-any.whl", hash = "sha256:66d2759d1921838256a05a3f80ad7e724936f083e35be5abb5e16eed6be6dc54", size = 9981, upload-time = "2024-11-06T10:44:52.917Z" }, + { url = "https://files.pythonhosted.org/packages/1a/99/84ba7273339d0f3dfa57901b846489d2e5c2cd731470167757f1935fffbd/aiohttp_retry-2.9.1-py3-none-any.whl", hash = "sha256:66d2759d1921838256a05a3f80ad7e724936f083e35be5abb5e16eed6be6dc54", size = 9981 }, ] [[package]] name = "aiolimiter" version = "1.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/23/b52debf471f7a1e42e362d959a3982bdcb4fe13a5d46e63d28868807a79c/aiolimiter-1.2.1.tar.gz", hash = "sha256:e02a37ea1a855d9e832252a105420ad4d15011505512a1a1d814647451b5cca9", size = 7185, upload-time = "2024-12-08T15:31:51.496Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/23/b52debf471f7a1e42e362d959a3982bdcb4fe13a5d46e63d28868807a79c/aiolimiter-1.2.1.tar.gz", hash = "sha256:e02a37ea1a855d9e832252a105420ad4d15011505512a1a1d814647451b5cca9", size = 7185 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/ba/df6e8e1045aebc4778d19b8a3a9bc1808adb1619ba94ca354d9ba17d86c3/aiolimiter-1.2.1-py3-none-any.whl", hash = "sha256:d3f249e9059a20badcb56b61601a83556133655c11d1eb3dd3e04ff069e5f3c7", size = 6711, upload-time = "2024-12-08T15:31:49.874Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ba/df6e8e1045aebc4778d19b8a3a9bc1808adb1619ba94ca354d9ba17d86c3/aiolimiter-1.2.1-py3-none-any.whl", hash = "sha256:d3f249e9059a20badcb56b61601a83556133655c11d1eb3dd3e04ff069e5f3c7", size = 6711 }, ] [[package]] @@ -174,18 +174,18 @@ dependencies = [ { name = "frozenlist" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490 }, ] [[package]] name = "aiosqlite" version = "0.22.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821 } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" }, + { url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405 }, ] [[package]] @@ -197,9 +197,9 @@ dependencies = [ { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725, upload-time = "2026-02-10T16:00:47.195Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/13/8b084e0f2efb0275a1d534838844926f798bd766566b1375174e2448cd31/alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc", size = 2056725 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893, upload-time = "2026-02-10T16:00:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/d2/29/6533c317b74f707ea28f8d633734dbda2119bbadfc61b2f3640ba835d0f7/alembic-1.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a", size = 263893 }, ] [[package]] @@ -209,27 +209,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "vine" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013, upload-time = "2024-11-12T19:55:44.051Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013 } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944, upload-time = "2024-11-12T19:55:41.782Z" }, + { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944 }, ] [[package]] name = "annotated-doc" version = "0.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303 }, ] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, ] [[package]] @@ -246,16 +246,16 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/7a/8b390dc47945d3169875d342847431e5f7d5fa716b2e37494d57cfc1db10/anthropic-0.86.0.tar.gz", hash = "sha256:60023a7e879aa4fbb1fed99d487fe407b2ebf6569603e5047cfe304cebdaa0e5", size = 583820, upload-time = "2026-03-18T18:43:08.017Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7a/8b390dc47945d3169875d342847431e5f7d5fa716b2e37494d57cfc1db10/anthropic-0.86.0.tar.gz", hash = "sha256:60023a7e879aa4fbb1fed99d487fe407b2ebf6569603e5047cfe304cebdaa0e5", size = 583820 } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/5f/67db29c6e5d16c8c9c4652d3efb934d89cb750cad201539141781d8eae14/anthropic-0.86.0-py3-none-any.whl", hash = "sha256:9d2bbd339446acce98858c5627d33056efe01f70435b22b63546fe7edae0cd57", size = 469400, upload-time = "2026-03-18T18:43:06.526Z" }, + { url = "https://files.pythonhosted.org/packages/63/5f/67db29c6e5d16c8c9c4652d3efb934d89cb750cad201539141781d8eae14/anthropic-0.86.0-py3-none-any.whl", hash = "sha256:9d2bbd339446acce98858c5627d33056efe01f70435b22b63546fe7edae0cd57", size = 469400 }, ] [[package]] name = "antlr4-python3-runtime" version = "4.9.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034, upload-time = "2021-11-06T17:52:23.524Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034 } [[package]] name = "anyio" @@ -265,9 +265,9 @@ dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622 } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353 }, ] [[package]] @@ -277,9 +277,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argon2-cffi-bindings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657 }, ] [[package]] @@ -289,142 +289,142 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441 } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, - { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, - { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, - { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, - { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, - { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, - { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, - { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, - { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, - { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, - { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, - { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, - { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, - { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, - { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, - { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, - { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, - { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, - { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, + { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393 }, + { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328 }, + { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269 }, + { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558 }, + { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364 }, + { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637 }, + { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934 }, + { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158 }, + { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597 }, + { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231 }, + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121 }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177 }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090 }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246 }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126 }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343 }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777 }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180 }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715 }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149 }, ] [[package]] name = "asgiref" version = "3.11.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550, upload-time = "2026-02-03T13:30:14.33Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345, upload-time = "2026-02-03T13:30:13.039Z" }, + { url = "https://files.pythonhosted.org/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345 }, ] [[package]] name = "asyncpg" version = "0.31.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667, upload-time = "2025-11-24T23:27:00.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cc/d18065ce2380d80b1bcce927c24a2642efd38918e33fd724bc4bca904877/asyncpg-0.31.0.tar.gz", hash = "sha256:c989386c83940bfbd787180f2b1519415e2d3d6277a70d9d0f0145ac73500735", size = 993667 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/a6/59d0a146e61d20e18db7396583242e32e0f120693b67a8de43f1557033e2/asyncpg-0.31.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad", size = 662042, upload-time = "2025-11-24T23:25:49.578Z" }, - { url = "https://files.pythonhosted.org/packages/36/01/ffaa189dcb63a2471720615e60185c3f6327716fdc0fc04334436fbb7c65/asyncpg-0.31.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d", size = 638504, upload-time = "2025-11-24T23:25:51.501Z" }, - { url = "https://files.pythonhosted.org/packages/9f/62/3f699ba45d8bd24c5d65392190d19656d74ff0185f42e19d0bbd973bb371/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a", size = 3426241, upload-time = "2025-11-24T23:25:53.278Z" }, - { url = "https://files.pythonhosted.org/packages/8c/d1/a867c2150f9c6e7af6462637f613ba67f78a314b00db220cd26ff559d532/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:aad7a33913fb8bcb5454313377cc330fbb19a0cd5faa7272407d8a0c4257b671", size = 3520321, upload-time = "2025-11-24T23:25:54.982Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1a/cce4c3f246805ecd285a3591222a2611141f1669d002163abef999b60f98/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3df118d94f46d85b2e434fd62c84cb66d5834d5a890725fe625f498e72e4d5ec", size = 3316685, upload-time = "2025-11-24T23:25:57.43Z" }, - { url = "https://files.pythonhosted.org/packages/40/ae/0fc961179e78cc579e138fad6eb580448ecae64908f95b8cb8ee2f241f67/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5b6efff3c17c3202d4b37189969acf8927438a238c6257f66be3c426beba20", size = 3471858, upload-time = "2025-11-24T23:25:59.636Z" }, - { url = "https://files.pythonhosted.org/packages/52/b2/b20e09670be031afa4cbfabd645caece7f85ec62d69c312239de568e058e/asyncpg-0.31.0-cp312-cp312-win32.whl", hash = "sha256:027eaa61361ec735926566f995d959ade4796f6a49d3bde17e5134b9964f9ba8", size = 527852, upload-time = "2025-11-24T23:26:01.084Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f0/f2ed1de154e15b107dc692262395b3c17fc34eafe2a78fc2115931561730/asyncpg-0.31.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d6bdcbc93d608a1158f17932de2321f68b1a967a13e014998db87a72ed3186", size = 597175, upload-time = "2025-11-24T23:26:02.564Z" }, - { url = "https://files.pythonhosted.org/packages/95/11/97b5c2af72a5d0b9bc3fa30cd4b9ce22284a9a943a150fdc768763caf035/asyncpg-0.31.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c204fab1b91e08b0f47e90a75d1b3c62174dab21f670ad6c5d0f243a228f015b", size = 661111, upload-time = "2025-11-24T23:26:04.467Z" }, - { url = "https://files.pythonhosted.org/packages/1b/71/157d611c791a5e2d0423f09f027bd499935f0906e0c2a416ce712ba51ef3/asyncpg-0.31.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:54a64f91839ba59008eccf7aad2e93d6e3de688d796f35803235ea1c4898ae1e", size = 636928, upload-time = "2025-11-24T23:26:05.944Z" }, - { url = "https://files.pythonhosted.org/packages/2e/fc/9e3486fb2bbe69d4a867c0b76d68542650a7ff1574ca40e84c3111bb0c6e/asyncpg-0.31.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0e0822b1038dc7253b337b0f3f676cadc4ac31b126c5d42691c39691962e403", size = 3424067, upload-time = "2025-11-24T23:26:07.957Z" }, - { url = "https://files.pythonhosted.org/packages/12/c6/8c9d076f73f07f995013c791e018a1cd5f31823c2a3187fc8581706aa00f/asyncpg-0.31.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bef056aa502ee34204c161c72ca1f3c274917596877f825968368b2c33f585f4", size = 3518156, upload-time = "2025-11-24T23:26:09.591Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3b/60683a0baf50fbc546499cfb53132cb6835b92b529a05f6a81471ab60d0c/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0bfbcc5b7ffcd9b75ab1558f00db2ae07db9c80637ad1b2469c43df79d7a5ae2", size = 3319636, upload-time = "2025-11-24T23:26:11.168Z" }, - { url = "https://files.pythonhosted.org/packages/50/dc/8487df0f69bd398a61e1792b3cba0e47477f214eff085ba0efa7eac9ce87/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22bc525ebbdc24d1261ecbf6f504998244d4e3be1721784b5f64664d61fbe602", size = 3472079, upload-time = "2025-11-24T23:26:13.164Z" }, - { url = "https://files.pythonhosted.org/packages/13/a1/c5bbeeb8531c05c89135cb8b28575ac2fac618bcb60119ee9696c3faf71c/asyncpg-0.31.0-cp313-cp313-win32.whl", hash = "sha256:f890de5e1e4f7e14023619399a471ce4b71f5418cd67a51853b9910fdfa73696", size = 527606, upload-time = "2025-11-24T23:26:14.78Z" }, - { url = "https://files.pythonhosted.org/packages/91/66/b25ccb84a246b470eb943b0107c07edcae51804912b824054b3413995a10/asyncpg-0.31.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc5f2fa9916f292e5c5c8b2ac2813763bcd7f58e130055b4ad8a0531314201ab", size = 596569, upload-time = "2025-11-24T23:26:16.189Z" }, - { url = "https://files.pythonhosted.org/packages/3c/36/e9450d62e84a13aea6580c83a47a437f26c7ca6fa0f0fd40b6670793ea30/asyncpg-0.31.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f6b56b91bb0ffc328c4e3ed113136cddd9deefdf5f79ab448598b9772831df44", size = 660867, upload-time = "2025-11-24T23:26:17.631Z" }, - { url = "https://files.pythonhosted.org/packages/82/4b/1d0a2b33b3102d210439338e1beea616a6122267c0df459ff0265cd5807a/asyncpg-0.31.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:334dec28cf20d7f5bb9e45b39546ddf247f8042a690bff9b9573d00086e69cb5", size = 638349, upload-time = "2025-11-24T23:26:19.689Z" }, - { url = "https://files.pythonhosted.org/packages/41/aa/e7f7ac9a7974f08eff9183e392b2d62516f90412686532d27e196c0f0eeb/asyncpg-0.31.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98cc158c53f46de7bb677fd20c417e264fc02b36d901cc2a43bd6cb0dc6dbfd2", size = 3410428, upload-time = "2025-11-24T23:26:21.275Z" }, - { url = "https://files.pythonhosted.org/packages/6f/de/bf1b60de3dede5c2731e6788617a512bc0ebd9693eac297ee74086f101d7/asyncpg-0.31.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9322b563e2661a52e3cdbc93eed3be7748b289f792e0011cb2720d278b366ce2", size = 3471678, upload-time = "2025-11-24T23:26:23.627Z" }, - { url = "https://files.pythonhosted.org/packages/46/78/fc3ade003e22d8bd53aaf8f75f4be48f0b460fa73738f0391b9c856a9147/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19857a358fc811d82227449b7ca40afb46e75b33eb8897240c3839dd8b744218", size = 3313505, upload-time = "2025-11-24T23:26:25.235Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e9/73eb8a6789e927816f4705291be21f2225687bfa97321e40cd23055e903a/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba5f8886e850882ff2c2ace5732300e99193823e8107e2c53ef01c1ebfa1e85d", size = 3434744, upload-time = "2025-11-24T23:26:26.944Z" }, - { url = "https://files.pythonhosted.org/packages/08/4b/f10b880534413c65c5b5862f79b8e81553a8f364e5238832ad4c0af71b7f/asyncpg-0.31.0-cp314-cp314-win32.whl", hash = "sha256:cea3a0b2a14f95834cee29432e4ddc399b95700eb1d51bbc5bfee8f31fa07b2b", size = 532251, upload-time = "2025-11-24T23:26:28.404Z" }, - { url = "https://files.pythonhosted.org/packages/d3/2d/7aa40750b7a19efa5d66e67fc06008ca0f27ba1bd082e457ad82f59aba49/asyncpg-0.31.0-cp314-cp314-win_amd64.whl", hash = "sha256:04d19392716af6b029411a0264d92093b6e5e8285ae97a39957b9a9c14ea72be", size = 604901, upload-time = "2025-11-24T23:26:30.34Z" }, - { url = "https://files.pythonhosted.org/packages/ce/fe/b9dfe349b83b9dee28cc42360d2c86b2cdce4cb551a2c2d27e156bcac84d/asyncpg-0.31.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bdb957706da132e982cc6856bb2f7b740603472b54c3ebc77fe60ea3e57e1bd2", size = 702280, upload-time = "2025-11-24T23:26:32Z" }, - { url = "https://files.pythonhosted.org/packages/6a/81/e6be6e37e560bd91e6c23ea8a6138a04fd057b08cf63d3c5055c98e81c1d/asyncpg-0.31.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6d11b198111a72f47154fa03b85799f9be63701e068b43f84ac25da0bda9cb31", size = 682931, upload-time = "2025-11-24T23:26:33.572Z" }, - { url = "https://files.pythonhosted.org/packages/a6/45/6009040da85a1648dd5bc75b3b0a062081c483e75a1a29041ae63a0bf0dc/asyncpg-0.31.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18c83b03bc0d1b23e6230f5bf8d4f217dc9bc08644ce0502a9d91dc9e634a9c7", size = 3581608, upload-time = "2025-11-24T23:26:35.638Z" }, - { url = "https://files.pythonhosted.org/packages/7e/06/2e3d4d7608b0b2b3adbee0d0bd6a2d29ca0fc4d8a78f8277df04e2d1fd7b/asyncpg-0.31.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e009abc333464ff18b8f6fd146addffd9aaf63e79aa3bb40ab7a4c332d0c5e9e", size = 3498738, upload-time = "2025-11-24T23:26:37.275Z" }, - { url = "https://files.pythonhosted.org/packages/7d/aa/7d75ede780033141c51d83577ea23236ba7d3a23593929b32b49db8ed36e/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3b1fbcb0e396a5ca435a8826a87e5c2c2cc0c8c68eb6fadf82168056b0e53a8c", size = 3401026, upload-time = "2025-11-24T23:26:39.423Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7a/15e37d45e7f7c94facc1e9148c0e455e8f33c08f0b8a0b1deb2c5171771b/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8df714dba348efcc162d2adf02d213e5fab1bd9f557e1305633e851a61814a7a", size = 3429426, upload-time = "2025-11-24T23:26:41.032Z" }, - { url = "https://files.pythonhosted.org/packages/13/d5/71437c5f6ae5f307828710efbe62163974e71237d5d46ebd2869ea052d10/asyncpg-0.31.0-cp314-cp314t-win32.whl", hash = "sha256:1b41f1afb1033f2b44f3234993b15096ddc9cd71b21a42dbd87fc6a57b43d65d", size = 614495, upload-time = "2025-11-24T23:26:42.659Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d7/8fb3044eaef08a310acfe23dae9a8e2e07d305edc29a53497e52bc76eca7/asyncpg-0.31.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3", size = 706062, upload-time = "2025-11-24T23:26:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a6/59d0a146e61d20e18db7396583242e32e0f120693b67a8de43f1557033e2/asyncpg-0.31.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b44c31e1efc1c15188ef183f287c728e2046abb1d26af4d20858215d50d91fad", size = 662042 }, + { url = "https://files.pythonhosted.org/packages/36/01/ffaa189dcb63a2471720615e60185c3f6327716fdc0fc04334436fbb7c65/asyncpg-0.31.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0c89ccf741c067614c9b5fc7f1fc6f3b61ab05ae4aaa966e6fd6b93097c7d20d", size = 638504 }, + { url = "https://files.pythonhosted.org/packages/9f/62/3f699ba45d8bd24c5d65392190d19656d74ff0185f42e19d0bbd973bb371/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:12b3b2e39dc5470abd5e98c8d3373e4b1d1234d9fbdedf538798b2c13c64460a", size = 3426241 }, + { url = "https://files.pythonhosted.org/packages/8c/d1/a867c2150f9c6e7af6462637f613ba67f78a314b00db220cd26ff559d532/asyncpg-0.31.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:aad7a33913fb8bcb5454313377cc330fbb19a0cd5faa7272407d8a0c4257b671", size = 3520321 }, + { url = "https://files.pythonhosted.org/packages/7a/1a/cce4c3f246805ecd285a3591222a2611141f1669d002163abef999b60f98/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3df118d94f46d85b2e434fd62c84cb66d5834d5a890725fe625f498e72e4d5ec", size = 3316685 }, + { url = "https://files.pythonhosted.org/packages/40/ae/0fc961179e78cc579e138fad6eb580448ecae64908f95b8cb8ee2f241f67/asyncpg-0.31.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd5b6efff3c17c3202d4b37189969acf8927438a238c6257f66be3c426beba20", size = 3471858 }, + { url = "https://files.pythonhosted.org/packages/52/b2/b20e09670be031afa4cbfabd645caece7f85ec62d69c312239de568e058e/asyncpg-0.31.0-cp312-cp312-win32.whl", hash = "sha256:027eaa61361ec735926566f995d959ade4796f6a49d3bde17e5134b9964f9ba8", size = 527852 }, + { url = "https://files.pythonhosted.org/packages/b5/f0/f2ed1de154e15b107dc692262395b3c17fc34eafe2a78fc2115931561730/asyncpg-0.31.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d6bdcbc93d608a1158f17932de2321f68b1a967a13e014998db87a72ed3186", size = 597175 }, + { url = "https://files.pythonhosted.org/packages/95/11/97b5c2af72a5d0b9bc3fa30cd4b9ce22284a9a943a150fdc768763caf035/asyncpg-0.31.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c204fab1b91e08b0f47e90a75d1b3c62174dab21f670ad6c5d0f243a228f015b", size = 661111 }, + { url = "https://files.pythonhosted.org/packages/1b/71/157d611c791a5e2d0423f09f027bd499935f0906e0c2a416ce712ba51ef3/asyncpg-0.31.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:54a64f91839ba59008eccf7aad2e93d6e3de688d796f35803235ea1c4898ae1e", size = 636928 }, + { url = "https://files.pythonhosted.org/packages/2e/fc/9e3486fb2bbe69d4a867c0b76d68542650a7ff1574ca40e84c3111bb0c6e/asyncpg-0.31.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0e0822b1038dc7253b337b0f3f676cadc4ac31b126c5d42691c39691962e403", size = 3424067 }, + { url = "https://files.pythonhosted.org/packages/12/c6/8c9d076f73f07f995013c791e018a1cd5f31823c2a3187fc8581706aa00f/asyncpg-0.31.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bef056aa502ee34204c161c72ca1f3c274917596877f825968368b2c33f585f4", size = 3518156 }, + { url = "https://files.pythonhosted.org/packages/ae/3b/60683a0baf50fbc546499cfb53132cb6835b92b529a05f6a81471ab60d0c/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0bfbcc5b7ffcd9b75ab1558f00db2ae07db9c80637ad1b2469c43df79d7a5ae2", size = 3319636 }, + { url = "https://files.pythonhosted.org/packages/50/dc/8487df0f69bd398a61e1792b3cba0e47477f214eff085ba0efa7eac9ce87/asyncpg-0.31.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22bc525ebbdc24d1261ecbf6f504998244d4e3be1721784b5f64664d61fbe602", size = 3472079 }, + { url = "https://files.pythonhosted.org/packages/13/a1/c5bbeeb8531c05c89135cb8b28575ac2fac618bcb60119ee9696c3faf71c/asyncpg-0.31.0-cp313-cp313-win32.whl", hash = "sha256:f890de5e1e4f7e14023619399a471ce4b71f5418cd67a51853b9910fdfa73696", size = 527606 }, + { url = "https://files.pythonhosted.org/packages/91/66/b25ccb84a246b470eb943b0107c07edcae51804912b824054b3413995a10/asyncpg-0.31.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc5f2fa9916f292e5c5c8b2ac2813763bcd7f58e130055b4ad8a0531314201ab", size = 596569 }, + { url = "https://files.pythonhosted.org/packages/3c/36/e9450d62e84a13aea6580c83a47a437f26c7ca6fa0f0fd40b6670793ea30/asyncpg-0.31.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f6b56b91bb0ffc328c4e3ed113136cddd9deefdf5f79ab448598b9772831df44", size = 660867 }, + { url = "https://files.pythonhosted.org/packages/82/4b/1d0a2b33b3102d210439338e1beea616a6122267c0df459ff0265cd5807a/asyncpg-0.31.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:334dec28cf20d7f5bb9e45b39546ddf247f8042a690bff9b9573d00086e69cb5", size = 638349 }, + { url = "https://files.pythonhosted.org/packages/41/aa/e7f7ac9a7974f08eff9183e392b2d62516f90412686532d27e196c0f0eeb/asyncpg-0.31.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98cc158c53f46de7bb677fd20c417e264fc02b36d901cc2a43bd6cb0dc6dbfd2", size = 3410428 }, + { url = "https://files.pythonhosted.org/packages/6f/de/bf1b60de3dede5c2731e6788617a512bc0ebd9693eac297ee74086f101d7/asyncpg-0.31.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9322b563e2661a52e3cdbc93eed3be7748b289f792e0011cb2720d278b366ce2", size = 3471678 }, + { url = "https://files.pythonhosted.org/packages/46/78/fc3ade003e22d8bd53aaf8f75f4be48f0b460fa73738f0391b9c856a9147/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19857a358fc811d82227449b7ca40afb46e75b33eb8897240c3839dd8b744218", size = 3313505 }, + { url = "https://files.pythonhosted.org/packages/bf/e9/73eb8a6789e927816f4705291be21f2225687bfa97321e40cd23055e903a/asyncpg-0.31.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba5f8886e850882ff2c2ace5732300e99193823e8107e2c53ef01c1ebfa1e85d", size = 3434744 }, + { url = "https://files.pythonhosted.org/packages/08/4b/f10b880534413c65c5b5862f79b8e81553a8f364e5238832ad4c0af71b7f/asyncpg-0.31.0-cp314-cp314-win32.whl", hash = "sha256:cea3a0b2a14f95834cee29432e4ddc399b95700eb1d51bbc5bfee8f31fa07b2b", size = 532251 }, + { url = "https://files.pythonhosted.org/packages/d3/2d/7aa40750b7a19efa5d66e67fc06008ca0f27ba1bd082e457ad82f59aba49/asyncpg-0.31.0-cp314-cp314-win_amd64.whl", hash = "sha256:04d19392716af6b029411a0264d92093b6e5e8285ae97a39957b9a9c14ea72be", size = 604901 }, + { url = "https://files.pythonhosted.org/packages/ce/fe/b9dfe349b83b9dee28cc42360d2c86b2cdce4cb551a2c2d27e156bcac84d/asyncpg-0.31.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bdb957706da132e982cc6856bb2f7b740603472b54c3ebc77fe60ea3e57e1bd2", size = 702280 }, + { url = "https://files.pythonhosted.org/packages/6a/81/e6be6e37e560bd91e6c23ea8a6138a04fd057b08cf63d3c5055c98e81c1d/asyncpg-0.31.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6d11b198111a72f47154fa03b85799f9be63701e068b43f84ac25da0bda9cb31", size = 682931 }, + { url = "https://files.pythonhosted.org/packages/a6/45/6009040da85a1648dd5bc75b3b0a062081c483e75a1a29041ae63a0bf0dc/asyncpg-0.31.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18c83b03bc0d1b23e6230f5bf8d4f217dc9bc08644ce0502a9d91dc9e634a9c7", size = 3581608 }, + { url = "https://files.pythonhosted.org/packages/7e/06/2e3d4d7608b0b2b3adbee0d0bd6a2d29ca0fc4d8a78f8277df04e2d1fd7b/asyncpg-0.31.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e009abc333464ff18b8f6fd146addffd9aaf63e79aa3bb40ab7a4c332d0c5e9e", size = 3498738 }, + { url = "https://files.pythonhosted.org/packages/7d/aa/7d75ede780033141c51d83577ea23236ba7d3a23593929b32b49db8ed36e/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3b1fbcb0e396a5ca435a8826a87e5c2c2cc0c8c68eb6fadf82168056b0e53a8c", size = 3401026 }, + { url = "https://files.pythonhosted.org/packages/ba/7a/15e37d45e7f7c94facc1e9148c0e455e8f33c08f0b8a0b1deb2c5171771b/asyncpg-0.31.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8df714dba348efcc162d2adf02d213e5fab1bd9f557e1305633e851a61814a7a", size = 3429426 }, + { url = "https://files.pythonhosted.org/packages/13/d5/71437c5f6ae5f307828710efbe62163974e71237d5d46ebd2869ea052d10/asyncpg-0.31.0-cp314-cp314t-win32.whl", hash = "sha256:1b41f1afb1033f2b44f3234993b15096ddc9cd71b21a42dbd87fc6a57b43d65d", size = 614495 }, + { url = "https://files.pythonhosted.org/packages/3c/d7/8fb3044eaef08a310acfe23dae9a8e2e07d305edc29a53497e52bc76eca7/asyncpg-0.31.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bd4107bb7cdd0e9e65fae66a62afd3a249663b844fa34d479f6d5b3bef9c04c3", size = 706062 }, ] [[package]] name = "attrs" version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055 } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548 }, ] [[package]] name = "audioop-lts" version = "0.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686, upload-time = "2025-08-05T16:43:17.409Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686 } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523, upload-time = "2025-08-05T16:42:20.836Z" }, - { url = "https://files.pythonhosted.org/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455, upload-time = "2025-08-05T16:42:22.283Z" }, - { url = "https://files.pythonhosted.org/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997, upload-time = "2025-08-05T16:42:23.849Z" }, - { url = "https://files.pythonhosted.org/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844, upload-time = "2025-08-05T16:42:25.208Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056, upload-time = "2025-08-05T16:42:26.559Z" }, - { url = "https://files.pythonhosted.org/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892, upload-time = "2025-08-05T16:42:27.902Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660, upload-time = "2025-08-05T16:42:28.9Z" }, - { url = "https://files.pythonhosted.org/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143, upload-time = "2025-08-05T16:42:29.929Z" }, - { url = "https://files.pythonhosted.org/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313, upload-time = "2025-08-05T16:42:30.949Z" }, - { url = "https://files.pythonhosted.org/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044, upload-time = "2025-08-05T16:42:31.959Z" }, - { url = "https://files.pythonhosted.org/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766, upload-time = "2025-08-05T16:42:33.302Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640, upload-time = "2025-08-05T16:42:34.854Z" }, - { url = "https://files.pythonhosted.org/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052, upload-time = "2025-08-05T16:42:35.839Z" }, - { url = "https://files.pythonhosted.org/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185, upload-time = "2025-08-05T16:42:37.04Z" }, - { url = "https://files.pythonhosted.org/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503, upload-time = "2025-08-05T16:42:38.427Z" }, - { url = "https://files.pythonhosted.org/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173, upload-time = "2025-08-05T16:42:39.704Z" }, - { url = "https://files.pythonhosted.org/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096, upload-time = "2025-08-05T16:42:40.684Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748, upload-time = "2025-08-05T16:42:41.992Z" }, - { url = "https://files.pythonhosted.org/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329, upload-time = "2025-08-05T16:42:42.987Z" }, - { url = "https://files.pythonhosted.org/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407, upload-time = "2025-08-05T16:42:44.336Z" }, - { url = "https://files.pythonhosted.org/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811, upload-time = "2025-08-05T16:42:45.325Z" }, - { url = "https://files.pythonhosted.org/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470, upload-time = "2025-08-05T16:42:46.468Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878, upload-time = "2025-08-05T16:42:47.576Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867, upload-time = "2025-08-05T16:42:49.003Z" }, - { url = "https://files.pythonhosted.org/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001, upload-time = "2025-08-05T16:42:50.038Z" }, - { url = "https://files.pythonhosted.org/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046, upload-time = "2025-08-05T16:42:51.111Z" }, - { url = "https://files.pythonhosted.org/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788, upload-time = "2025-08-05T16:42:52.198Z" }, - { url = "https://files.pythonhosted.org/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472, upload-time = "2025-08-05T16:42:53.59Z" }, - { url = "https://files.pythonhosted.org/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279, upload-time = "2025-08-05T16:42:54.632Z" }, - { url = "https://files.pythonhosted.org/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568, upload-time = "2025-08-05T16:42:55.627Z" }, - { url = "https://files.pythonhosted.org/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942, upload-time = "2025-08-05T16:42:56.674Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603, upload-time = "2025-08-05T16:42:57.571Z" }, - { url = "https://files.pythonhosted.org/packages/5c/73/413b5a2804091e2c7d5def1d618e4837f1cb82464e230f827226278556b7/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f9ee9b52f5f857fbaf9d605a360884f034c92c1c23021fb90b2e39b8e64bede6", size = 47104, upload-time = "2025-08-05T16:42:58.518Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8c/daa3308dc6593944410c2c68306a5e217f5c05b70a12e70228e7dd42dc5c/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:49ee1a41738a23e98d98b937a0638357a2477bc99e61b0f768a8f654f45d9b7a", size = 27754, upload-time = "2025-08-05T16:43:00.132Z" }, - { url = "https://files.pythonhosted.org/packages/4e/86/c2e0f627168fcf61781a8f72cab06b228fe1da4b9fa4ab39cfb791b5836b/audioop_lts-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b00be98ccd0fc123dcfad31d50030d25fcf31488cde9e61692029cd7394733b", size = 27332, upload-time = "2025-08-05T16:43:01.666Z" }, - { url = "https://files.pythonhosted.org/packages/c7/bd/35dce665255434f54e5307de39e31912a6f902d4572da7c37582809de14f/audioop_lts-0.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d2e0f9f7a69403e388894d4ca5ada5c47230716a03f2847cfc7bd1ecb589d6", size = 92396, upload-time = "2025-08-05T16:43:02.991Z" }, - { url = "https://files.pythonhosted.org/packages/2d/d2/deeb9f51def1437b3afa35aeb729d577c04bcd89394cb56f9239a9f50b6f/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9b0b8a03ef474f56d1a842af1a2e01398b8f7654009823c6d9e0ecff4d5cfbf", size = 91811, upload-time = "2025-08-05T16:43:04.096Z" }, - { url = "https://files.pythonhosted.org/packages/76/3b/09f8b35b227cee28cc8231e296a82759ed80c1a08e349811d69773c48426/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b267b70747d82125f1a021506565bdc5609a2b24bcb4773c16d79d2bb260bbd", size = 100483, upload-time = "2025-08-05T16:43:05.085Z" }, - { url = "https://files.pythonhosted.org/packages/0b/15/05b48a935cf3b130c248bfdbdea71ce6437f5394ee8533e0edd7cfd93d5e/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0337d658f9b81f4cd0fdb1f47635070cc084871a3d4646d9de74fdf4e7c3d24a", size = 103885, upload-time = "2025-08-05T16:43:06.197Z" }, - { url = "https://files.pythonhosted.org/packages/83/80/186b7fce6d35b68d3d739f228dc31d60b3412105854edb975aa155a58339/audioop_lts-0.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:167d3b62586faef8b6b2275c3218796b12621a60e43f7e9d5845d627b9c9b80e", size = 84899, upload-time = "2025-08-05T16:43:07.291Z" }, - { url = "https://files.pythonhosted.org/packages/49/89/c78cc5ac6cb5828f17514fb12966e299c850bc885e80f8ad94e38d450886/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0d9385e96f9f6da847f4d571ce3cb15b5091140edf3db97276872647ce37efd7", size = 89998, upload-time = "2025-08-05T16:43:08.335Z" }, - { url = "https://files.pythonhosted.org/packages/4c/4b/6401888d0c010e586c2ca50fce4c903d70a6bb55928b16cfbdfd957a13da/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:48159d96962674eccdca9a3df280e864e8ac75e40a577cc97c5c42667ffabfc5", size = 99046, upload-time = "2025-08-05T16:43:09.367Z" }, - { url = "https://files.pythonhosted.org/packages/de/f8/c874ca9bb447dae0e2ef2e231f6c4c2b0c39e31ae684d2420b0f9e97ee68/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fefe5868cd082db1186f2837d64cfbfa78b548ea0d0543e9b28935ccce81ce9", size = 84843, upload-time = "2025-08-05T16:43:10.749Z" }, - { url = "https://files.pythonhosted.org/packages/3e/c0/0323e66f3daebc13fd46b36b30c3be47e3fc4257eae44f1e77eb828c703f/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:58cf54380c3884fb49fdd37dfb7a772632b6701d28edd3e2904743c5e1773602", size = 94490, upload-time = "2025-08-05T16:43:12.131Z" }, - { url = "https://files.pythonhosted.org/packages/98/6b/acc7734ac02d95ab791c10c3f17ffa3584ccb9ac5c18fd771c638ed6d1f5/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:088327f00488cdeed296edd9215ca159f3a5a5034741465789cad403fcf4bec0", size = 92297, upload-time = "2025-08-05T16:43:13.139Z" }, - { url = "https://files.pythonhosted.org/packages/13/c3/c3dc3f564ce6877ecd2a05f8d751b9b27a8c320c2533a98b0c86349778d0/audioop_lts-0.2.2-cp314-cp314t-win32.whl", hash = "sha256:068aa17a38b4e0e7de771c62c60bbca2455924b67a8814f3b0dee92b5820c0b3", size = 27331, upload-time = "2025-08-05T16:43:14.19Z" }, - { url = "https://files.pythonhosted.org/packages/72/bb/b4608537e9ffcb86449091939d52d24a055216a36a8bf66b936af8c3e7ac/audioop_lts-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a5bf613e96f49712073de86f20dbdd4014ca18efd4d34ed18c75bd808337851b", size = 31697, upload-time = "2025-08-05T16:43:15.193Z" }, - { url = "https://files.pythonhosted.org/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206, upload-time = "2025-08-05T16:43:16.444Z" }, + { url = "https://files.pythonhosted.org/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523 }, + { url = "https://files.pythonhosted.org/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455 }, + { url = "https://files.pythonhosted.org/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997 }, + { url = "https://files.pythonhosted.org/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844 }, + { url = "https://files.pythonhosted.org/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056 }, + { url = "https://files.pythonhosted.org/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892 }, + { url = "https://files.pythonhosted.org/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660 }, + { url = "https://files.pythonhosted.org/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143 }, + { url = "https://files.pythonhosted.org/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313 }, + { url = "https://files.pythonhosted.org/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044 }, + { url = "https://files.pythonhosted.org/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766 }, + { url = "https://files.pythonhosted.org/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640 }, + { url = "https://files.pythonhosted.org/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052 }, + { url = "https://files.pythonhosted.org/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185 }, + { url = "https://files.pythonhosted.org/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503 }, + { url = "https://files.pythonhosted.org/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173 }, + { url = "https://files.pythonhosted.org/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096 }, + { url = "https://files.pythonhosted.org/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748 }, + { url = "https://files.pythonhosted.org/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329 }, + { url = "https://files.pythonhosted.org/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407 }, + { url = "https://files.pythonhosted.org/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811 }, + { url = "https://files.pythonhosted.org/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470 }, + { url = "https://files.pythonhosted.org/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878 }, + { url = "https://files.pythonhosted.org/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867 }, + { url = "https://files.pythonhosted.org/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001 }, + { url = "https://files.pythonhosted.org/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046 }, + { url = "https://files.pythonhosted.org/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788 }, + { url = "https://files.pythonhosted.org/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472 }, + { url = "https://files.pythonhosted.org/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279 }, + { url = "https://files.pythonhosted.org/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568 }, + { url = "https://files.pythonhosted.org/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942 }, + { url = "https://files.pythonhosted.org/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603 }, + { url = "https://files.pythonhosted.org/packages/5c/73/413b5a2804091e2c7d5def1d618e4837f1cb82464e230f827226278556b7/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f9ee9b52f5f857fbaf9d605a360884f034c92c1c23021fb90b2e39b8e64bede6", size = 47104 }, + { url = "https://files.pythonhosted.org/packages/ae/8c/daa3308dc6593944410c2c68306a5e217f5c05b70a12e70228e7dd42dc5c/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:49ee1a41738a23e98d98b937a0638357a2477bc99e61b0f768a8f654f45d9b7a", size = 27754 }, + { url = "https://files.pythonhosted.org/packages/4e/86/c2e0f627168fcf61781a8f72cab06b228fe1da4b9fa4ab39cfb791b5836b/audioop_lts-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b00be98ccd0fc123dcfad31d50030d25fcf31488cde9e61692029cd7394733b", size = 27332 }, + { url = "https://files.pythonhosted.org/packages/c7/bd/35dce665255434f54e5307de39e31912a6f902d4572da7c37582809de14f/audioop_lts-0.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d2e0f9f7a69403e388894d4ca5ada5c47230716a03f2847cfc7bd1ecb589d6", size = 92396 }, + { url = "https://files.pythonhosted.org/packages/2d/d2/deeb9f51def1437b3afa35aeb729d577c04bcd89394cb56f9239a9f50b6f/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9b0b8a03ef474f56d1a842af1a2e01398b8f7654009823c6d9e0ecff4d5cfbf", size = 91811 }, + { url = "https://files.pythonhosted.org/packages/76/3b/09f8b35b227cee28cc8231e296a82759ed80c1a08e349811d69773c48426/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b267b70747d82125f1a021506565bdc5609a2b24bcb4773c16d79d2bb260bbd", size = 100483 }, + { url = "https://files.pythonhosted.org/packages/0b/15/05b48a935cf3b130c248bfdbdea71ce6437f5394ee8533e0edd7cfd93d5e/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0337d658f9b81f4cd0fdb1f47635070cc084871a3d4646d9de74fdf4e7c3d24a", size = 103885 }, + { url = "https://files.pythonhosted.org/packages/83/80/186b7fce6d35b68d3d739f228dc31d60b3412105854edb975aa155a58339/audioop_lts-0.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:167d3b62586faef8b6b2275c3218796b12621a60e43f7e9d5845d627b9c9b80e", size = 84899 }, + { url = "https://files.pythonhosted.org/packages/49/89/c78cc5ac6cb5828f17514fb12966e299c850bc885e80f8ad94e38d450886/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0d9385e96f9f6da847f4d571ce3cb15b5091140edf3db97276872647ce37efd7", size = 89998 }, + { url = "https://files.pythonhosted.org/packages/4c/4b/6401888d0c010e586c2ca50fce4c903d70a6bb55928b16cfbdfd957a13da/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:48159d96962674eccdca9a3df280e864e8ac75e40a577cc97c5c42667ffabfc5", size = 99046 }, + { url = "https://files.pythonhosted.org/packages/de/f8/c874ca9bb447dae0e2ef2e231f6c4c2b0c39e31ae684d2420b0f9e97ee68/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fefe5868cd082db1186f2837d64cfbfa78b548ea0d0543e9b28935ccce81ce9", size = 84843 }, + { url = "https://files.pythonhosted.org/packages/3e/c0/0323e66f3daebc13fd46b36b30c3be47e3fc4257eae44f1e77eb828c703f/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:58cf54380c3884fb49fdd37dfb7a772632b6701d28edd3e2904743c5e1773602", size = 94490 }, + { url = "https://files.pythonhosted.org/packages/98/6b/acc7734ac02d95ab791c10c3f17ffa3584ccb9ac5c18fd771c638ed6d1f5/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:088327f00488cdeed296edd9215ca159f3a5a5034741465789cad403fcf4bec0", size = 92297 }, + { url = "https://files.pythonhosted.org/packages/13/c3/c3dc3f564ce6877ecd2a05f8d751b9b27a8c320c2533a98b0c86349778d0/audioop_lts-0.2.2-cp314-cp314t-win32.whl", hash = "sha256:068aa17a38b4e0e7de771c62c60bbca2455924b67a8814f3b0dee92b5820c0b3", size = 27331 }, + { url = "https://files.pythonhosted.org/packages/72/bb/b4608537e9ffcb86449091939d52d24a055216a36a8bf66b936af8c3e7ac/audioop_lts-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a5bf613e96f49712073de86f20dbdd4014ca18efd4d34ed18c75bd808337851b", size = 31697 }, + { url = "https://files.pythonhosted.org/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206 }, ] [[package]] @@ -434,33 +434,33 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/98/00d3dd826d46959ad8e32af2dbb2398868fd9fd0683c26e56d0789bd0e68/authlib-1.6.9.tar.gz", hash = "sha256:d8f2421e7e5980cc1ddb4e32d3f5fa659cfaf60d8eaf3281ebed192e4ab74f04", size = 165134, upload-time = "2026-03-02T07:44:01.998Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/98/00d3dd826d46959ad8e32af2dbb2398868fd9fd0683c26e56d0789bd0e68/authlib-1.6.9.tar.gz", hash = "sha256:d8f2421e7e5980cc1ddb4e32d3f5fa659cfaf60d8eaf3281ebed192e4ab74f04", size = 165134 } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/23/b65f568ed0c22f1efacb744d2db1a33c8068f384b8c9b482b52ebdbc3ef6/authlib-1.6.9-py2.py3-none-any.whl", hash = "sha256:f08b4c14e08f0861dc18a32357b33fbcfd2ea86cfe3fe149484b4d764c4a0ac3", size = 244197, upload-time = "2026-03-02T07:44:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/53/23/b65f568ed0c22f1efacb744d2db1a33c8068f384b8c9b482b52ebdbc3ef6/authlib-1.6.9-py2.py3-none-any.whl", hash = "sha256:f08b4c14e08f0861dc18a32357b33fbcfd2ea86cfe3fe149484b4d764c4a0ac3", size = 244197 }, ] [[package]] name = "av" version = "17.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/eb/abca886df3a091bc406feb5ff71b4c4f426beaae6b71b9697264ce8c7211/av-17.0.0.tar.gz", hash = "sha256:c53685df73775a8763c375c7b2d62a6cb149d992a26a4b098204da42ade8c3df", size = 4410769, upload-time = "2026-03-14T14:38:45.868Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/eb/abca886df3a091bc406feb5ff71b4c4f426beaae6b71b9697264ce8c7211/av-17.0.0.tar.gz", hash = "sha256:c53685df73775a8763c375c7b2d62a6cb149d992a26a4b098204da42ade8c3df", size = 4410769 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/fb/55e3b5b5d1fc61466292f26fbcbabafa2642f378dc48875f8f554591e1a4/av-17.0.0-cp311-abi3-macosx_11_0_x86_64.whl", hash = "sha256:ed4013fac77c309a4a68141dcf6148f1821bb1073a36d4289379762a6372f711", size = 23238424, upload-time = "2026-03-14T14:38:05.856Z" }, - { url = "https://files.pythonhosted.org/packages/52/03/9ace1acc08bc9ae38c14bf3a4b1360e995e4d999d1d33c2cbd7c9e77582a/av-17.0.0-cp311-abi3-macosx_14_0_arm64.whl", hash = "sha256:e44b6c83e9f3be9f79ee87d0b77a27cea9a9cd67bd630362c86b7e56a748dfbb", size = 18709043, upload-time = "2026-03-14T14:38:08.288Z" }, - { url = "https://files.pythonhosted.org/packages/00/c0/637721f3cd5bb8bd16105a1a08efd781fc12f449931bdb3a4d0cfd63fa55/av-17.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b440da6ac47da0629d509316f24bcd858f33158dbdd0f1b7293d71e99beb26de", size = 34018780, upload-time = "2026-03-14T14:38:10.45Z" }, - { url = "https://files.pythonhosted.org/packages/d2/59/d19bc3257dd985d55337d7f0414c019414b97e16cd3690ebf9941a847543/av-17.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1060cba85f97f4a337311169d92c0b5e143452cfa5ca0e65fa499d7955e8592e", size = 36358757, upload-time = "2026-03-14T14:38:13.092Z" }, - { url = "https://files.pythonhosted.org/packages/52/6c/a1f4f2677bae6f2ade7a8a18e90ebdcf70690c9b1c4e40e118aa30fa313f/av-17.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:deda202e6021cfc7ba3e816897760ec5431309d59a4da1f75df3c0e9413d71e7", size = 35195281, upload-time = "2026-03-14T14:38:15.789Z" }, - { url = "https://files.pythonhosted.org/packages/90/ea/52b0fc6f69432c7bf3f5fbe6f707113650aa40a1a05b9096ffc2bba4f77d/av-17.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ffaf266a1a9c2148072de0a4b5ae98061465178d2cfaa69ee089761149342974", size = 37444817, upload-time = "2026-03-14T14:38:18.563Z" }, - { url = "https://files.pythonhosted.org/packages/34/ad/d2172966282cb8f146c13b6be7416efefde74186460c5e1708ddfc13dba6/av-17.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:45a35a40b2875bf2f98de7c952d74d960f92f319734e6d28e03b4c62a49e6f49", size = 28888553, upload-time = "2026-03-14T14:38:21.223Z" }, - { url = "https://files.pythonhosted.org/packages/b0/bb/c5a4c4172c514d631fb506e6366b503576b8c7f29809cf42aca73e28ff01/av-17.0.0-cp311-abi3-win_arm64.whl", hash = "sha256:3d32e9b5c5bbcb872a0b6917b352a1db8a42142237826c9b49a36d5dbd9e9c26", size = 21916910, upload-time = "2026-03-14T14:38:23.706Z" }, - { url = "https://files.pythonhosted.org/packages/7f/8e/c40ac08e63f79387c59f6ecc38f47d4c942b549130eee579ec1a91f6a291/av-17.0.0-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:d13250fb4b4522e9a6bec32da082556d5f257110ea223758151375748d9bbe25", size = 23483029, upload-time = "2026-03-14T14:38:25.758Z" }, - { url = "https://files.pythonhosted.org/packages/a9/fb/b4419494bfc249163ec393c613966d66db7e95c76da3345711cd115a79df/av-17.0.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:dbb56aa3b7ae72451d1bf6e9d37c7d83d39b97af712f73583ff419fbf08fc237", size = 18920446, upload-time = "2026-03-14T14:38:27.905Z" }, - { url = "https://files.pythonhosted.org/packages/30/62/c2306d91602ddad2c56106f21dcb334fd51d5ea2e952f7fa025bb8aa39fc/av-17.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a213ac9e83b7ab12c2e9f277a09cac8e9d85cf0883efdab7a87a60e2e4e48879", size = 37477266, upload-time = "2026-03-14T14:38:30.404Z" }, - { url = "https://files.pythonhosted.org/packages/28/cd/c8510a9607886785c0b3ca019d503e888c3757529be42a7287fe2bfa92d5/av-17.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:e15c88bb0921f9435bcc5a27a0863dba571a80ad5e1389c4fcf2073833bb4a74", size = 39572988, upload-time = "2026-03-14T14:38:32.984Z" }, - { url = "https://files.pythonhosted.org/packages/7d/2d/207d9361e25b5abec9be335bbab4df6b6b838e2214be4b374f4cfb285427/av-17.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:096cfd1e9fc896506726c7c42aaf9b370e78c2f257cde4d6ddb6c889bfcc49ec", size = 38399591, upload-time = "2026-03-14T14:38:35.465Z" }, - { url = "https://files.pythonhosted.org/packages/73/ca/307740c6aa2980966bf11383ffcb04bacc5b13f3d268ab4cfb274ad6f793/av-17.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3649ab3d2c7f58049ded1a36e100c0d8fd529cf258f41dd88678ba824034d8c9", size = 40590681, upload-time = "2026-03-14T14:38:38.269Z" }, - { url = "https://files.pythonhosted.org/packages/35/f2/6fdb26d0651adf409864cb2a0d60da107e467d3d1aabc94b234ead54324a/av-17.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e5002271ab2135b551d980c2db8f3299d452e3b9d3633f24f6bb57fffe91cd10", size = 29216337, upload-time = "2026-03-14T14:38:40.83Z" }, - { url = "https://files.pythonhosted.org/packages/41/0a/0896b829a39b5669a2d811e1a79598de661693685cd62b31f11d0c18e65b/av-17.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dba98603fc4665b4f750de86fbaf6c0cfaece970671a9b529e0e3d1711e8367e", size = 22071058, upload-time = "2026-03-14T14:38:43.663Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fb/55e3b5b5d1fc61466292f26fbcbabafa2642f378dc48875f8f554591e1a4/av-17.0.0-cp311-abi3-macosx_11_0_x86_64.whl", hash = "sha256:ed4013fac77c309a4a68141dcf6148f1821bb1073a36d4289379762a6372f711", size = 23238424 }, + { url = "https://files.pythonhosted.org/packages/52/03/9ace1acc08bc9ae38c14bf3a4b1360e995e4d999d1d33c2cbd7c9e77582a/av-17.0.0-cp311-abi3-macosx_14_0_arm64.whl", hash = "sha256:e44b6c83e9f3be9f79ee87d0b77a27cea9a9cd67bd630362c86b7e56a748dfbb", size = 18709043 }, + { url = "https://files.pythonhosted.org/packages/00/c0/637721f3cd5bb8bd16105a1a08efd781fc12f449931bdb3a4d0cfd63fa55/av-17.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b440da6ac47da0629d509316f24bcd858f33158dbdd0f1b7293d71e99beb26de", size = 34018780 }, + { url = "https://files.pythonhosted.org/packages/d2/59/d19bc3257dd985d55337d7f0414c019414b97e16cd3690ebf9941a847543/av-17.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1060cba85f97f4a337311169d92c0b5e143452cfa5ca0e65fa499d7955e8592e", size = 36358757 }, + { url = "https://files.pythonhosted.org/packages/52/6c/a1f4f2677bae6f2ade7a8a18e90ebdcf70690c9b1c4e40e118aa30fa313f/av-17.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:deda202e6021cfc7ba3e816897760ec5431309d59a4da1f75df3c0e9413d71e7", size = 35195281 }, + { url = "https://files.pythonhosted.org/packages/90/ea/52b0fc6f69432c7bf3f5fbe6f707113650aa40a1a05b9096ffc2bba4f77d/av-17.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ffaf266a1a9c2148072de0a4b5ae98061465178d2cfaa69ee089761149342974", size = 37444817 }, + { url = "https://files.pythonhosted.org/packages/34/ad/d2172966282cb8f146c13b6be7416efefde74186460c5e1708ddfc13dba6/av-17.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:45a35a40b2875bf2f98de7c952d74d960f92f319734e6d28e03b4c62a49e6f49", size = 28888553 }, + { url = "https://files.pythonhosted.org/packages/b0/bb/c5a4c4172c514d631fb506e6366b503576b8c7f29809cf42aca73e28ff01/av-17.0.0-cp311-abi3-win_arm64.whl", hash = "sha256:3d32e9b5c5bbcb872a0b6917b352a1db8a42142237826c9b49a36d5dbd9e9c26", size = 21916910 }, + { url = "https://files.pythonhosted.org/packages/7f/8e/c40ac08e63f79387c59f6ecc38f47d4c942b549130eee579ec1a91f6a291/av-17.0.0-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:d13250fb4b4522e9a6bec32da082556d5f257110ea223758151375748d9bbe25", size = 23483029 }, + { url = "https://files.pythonhosted.org/packages/a9/fb/b4419494bfc249163ec393c613966d66db7e95c76da3345711cd115a79df/av-17.0.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:dbb56aa3b7ae72451d1bf6e9d37c7d83d39b97af712f73583ff419fbf08fc237", size = 18920446 }, + { url = "https://files.pythonhosted.org/packages/30/62/c2306d91602ddad2c56106f21dcb334fd51d5ea2e952f7fa025bb8aa39fc/av-17.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a213ac9e83b7ab12c2e9f277a09cac8e9d85cf0883efdab7a87a60e2e4e48879", size = 37477266 }, + { url = "https://files.pythonhosted.org/packages/28/cd/c8510a9607886785c0b3ca019d503e888c3757529be42a7287fe2bfa92d5/av-17.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:e15c88bb0921f9435bcc5a27a0863dba571a80ad5e1389c4fcf2073833bb4a74", size = 39572988 }, + { url = "https://files.pythonhosted.org/packages/7d/2d/207d9361e25b5abec9be335bbab4df6b6b838e2214be4b374f4cfb285427/av-17.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:096cfd1e9fc896506726c7c42aaf9b370e78c2f257cde4d6ddb6c889bfcc49ec", size = 38399591 }, + { url = "https://files.pythonhosted.org/packages/73/ca/307740c6aa2980966bf11383ffcb04bacc5b13f3d268ab4cfb274ad6f793/av-17.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3649ab3d2c7f58049ded1a36e100c0d8fd529cf258f41dd88678ba824034d8c9", size = 40590681 }, + { url = "https://files.pythonhosted.org/packages/35/f2/6fdb26d0651adf409864cb2a0d60da107e467d3d1aabc94b234ead54324a/av-17.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e5002271ab2135b551d980c2db8f3299d452e3b9d3633f24f6bb57fffe91cd10", size = 29216337 }, + { url = "https://files.pythonhosted.org/packages/41/0a/0896b829a39b5669a2d811e1a79598de661693685cd62b31f11d0c18e65b/av-17.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dba98603fc4665b4f750de86fbaf6c0cfaece970671a9b529e0e3d1711e8367e", size = 22071058 }, ] [[package]] @@ -472,9 +472,9 @@ dependencies = [ { name = "isodate" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/44/7b/8115cd713e2caa5e44def85f2b7ebd02a74ae74d7113ba20bdd41fd6dd80/azure_ai_documentintelligence-1.0.2.tar.gz", hash = "sha256:4d75a2513f2839365ebabc0e0e1772f5601b3a8c9a71e75da12440da13b63484", size = 170940, upload-time = "2025-03-27T02:46:20.606Z" } +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/8115cd713e2caa5e44def85f2b7ebd02a74ae74d7113ba20bdd41fd6dd80/azure_ai_documentintelligence-1.0.2.tar.gz", hash = "sha256:4d75a2513f2839365ebabc0e0e1772f5601b3a8c9a71e75da12440da13b63484", size = 170940 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/75/c9ec040f23082f54ffb1977ff8f364c2d21c79a640a13d1c1809e7fd6b1a/azure_ai_documentintelligence-1.0.2-py3-none-any.whl", hash = "sha256:e1fb446abbdeccc9759d897898a0fe13141ed29f9ad11fc705f951925822ed59", size = 106005, upload-time = "2025-03-27T02:46:22.356Z" }, + { url = "https://files.pythonhosted.org/packages/d9/75/c9ec040f23082f54ffb1977ff8f364c2d21c79a640a13d1c1809e7fd6b1a/azure_ai_documentintelligence-1.0.2-py3-none-any.whl", hash = "sha256:e1fb446abbdeccc9759d897898a0fe13141ed29f9ad11fc705f951925822ed59", size = 106005 }, ] [[package]] @@ -485,9 +485,9 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/83/bbde3faa84ddcb8eb0eca4b3ffb3221252281db4ce351300fe248c5c70b1/azure_core-1.39.0.tar.gz", hash = "sha256:8a90a562998dd44ce84597590fff6249701b98c0e8797c95fcdd695b54c35d74", size = 367531, upload-time = "2026-03-19T01:31:29.461Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/83/bbde3faa84ddcb8eb0eca4b3ffb3221252281db4ce351300fe248c5c70b1/azure_core-1.39.0.tar.gz", hash = "sha256:8a90a562998dd44ce84597590fff6249701b98c0e8797c95fcdd695b54c35d74", size = 367531 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d6/8ebcd05b01a580f086ac9a97fb9fac65c09a4b012161cc97c21a336e880b/azure_core-1.39.0-py3-none-any.whl", hash = "sha256:4ac7b70fab5438c3f68770649a78daf97833caa83827f91df9c14e0e0ea7d34f", size = 218318, upload-time = "2026-03-19T01:31:31.25Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d6/8ebcd05b01a580f086ac9a97fb9fac65c09a4b012161cc97c21a336e880b/azure_core-1.39.0-py3-none-any.whl", hash = "sha256:4ac7b70fab5438c3f68770649a78daf97833caa83827f91df9c14e0e0ea7d34f", size = 218318 }, ] [[package]] @@ -501,18 +501,18 @@ dependencies = [ { name = "msal-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/0e/3a63efb48aa4a5ae2cfca61ee152fbcb668092134d3eb8bfda472dd5c617/azure_identity-1.25.3.tar.gz", hash = "sha256:ab23c0d63015f50b630ef6c6cf395e7262f439ce06e5d07a64e874c724f8d9e6", size = 286304, upload-time = "2026-03-13T01:12:20.892Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/0e/3a63efb48aa4a5ae2cfca61ee152fbcb668092134d3eb8bfda472dd5c617/azure_identity-1.25.3.tar.gz", hash = "sha256:ab23c0d63015f50b630ef6c6cf395e7262f439ce06e5d07a64e874c724f8d9e6", size = 286304 } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/9a/417b3a533e01953a7c618884df2cb05a71e7b68bdbce4fbdb62349d2a2e8/azure_identity-1.25.3-py3-none-any.whl", hash = "sha256:f4d0b956a8146f30333e071374171f3cfa7bdb8073adb8c3814b65567aa7447c", size = 192138, upload-time = "2026-03-13T01:12:22.951Z" }, + { url = "https://files.pythonhosted.org/packages/49/9a/417b3a533e01953a7c618884df2cb05a71e7b68bdbce4fbdb62349d2a2e8/azure_identity-1.25.3-py3-none-any.whl", hash = "sha256:f4d0b956a8146f30333e071374171f3cfa7bdb8073adb8c3814b65567aa7447c", size = 192138 }, ] [[package]] name = "babel" version = "2.18.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554 } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845 }, ] [[package]] @@ -527,75 +527,75 @@ dependencies = [ { name = "platformdirs" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/47/5d/54c79aaaa9aa1278af24cae98d81d6ef635ad840f046bc2ccb5041ddeb1b/banks-2.4.1.tar.gz", hash = "sha256:8cbf1553f14c44d4f7e9c2064ad9212ce53ee4da000b2f8308d548b60db56655", size = 188033, upload-time = "2026-02-17T11:21:14.855Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/5d/54c79aaaa9aa1278af24cae98d81d6ef635ad840f046bc2ccb5041ddeb1b/banks-2.4.1.tar.gz", hash = "sha256:8cbf1553f14c44d4f7e9c2064ad9212ce53ee4da000b2f8308d548b60db56655", size = 188033 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/5a/f38b49e8b225b0c774e97c9495e52ab9ccdf6d82bde68c513bd736820eb2/banks-2.4.1-py3-none-any.whl", hash = "sha256:40e6d9b6e9b69fb403fa31f2853b3297e4919c1b6f2179b2119d2d4473c6ed13", size = 35032, upload-time = "2026-02-17T11:21:13.236Z" }, + { url = "https://files.pythonhosted.org/packages/b8/5a/f38b49e8b225b0c774e97c9495e52ab9ccdf6d82bde68c513bd736820eb2/banks-2.4.1-py3-none-any.whl", hash = "sha256:40e6d9b6e9b69fb403fa31f2853b3297e4919c1b6f2179b2119d2d4473c6ed13", size = 35032 }, ] [[package]] name = "bcrypt" version = "5.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386, upload-time = "2025-09-25T19:50:47.829Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386 } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/85/3e65e01985fddf25b64ca67275bb5bdb4040bd1a53b66d355c6c37c8a680/bcrypt-5.0.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be", size = 481806, upload-time = "2025-09-25T19:49:05.102Z" }, - { url = "https://files.pythonhosted.org/packages/44/dc/01eb79f12b177017a726cbf78330eb0eb442fae0e7b3dfd84ea2849552f3/bcrypt-5.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2", size = 268626, upload-time = "2025-09-25T19:49:06.723Z" }, - { url = "https://files.pythonhosted.org/packages/8c/cf/e82388ad5959c40d6afd94fb4743cc077129d45b952d46bdc3180310e2df/bcrypt-5.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f", size = 271853, upload-time = "2025-09-25T19:49:08.028Z" }, - { url = "https://files.pythonhosted.org/packages/ec/86/7134b9dae7cf0efa85671651341f6afa695857fae172615e960fb6a466fa/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86", size = 269793, upload-time = "2025-09-25T19:49:09.727Z" }, - { url = "https://files.pythonhosted.org/packages/cc/82/6296688ac1b9e503d034e7d0614d56e80c5d1a08402ff856a4549cb59207/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23", size = 289930, upload-time = "2025-09-25T19:49:11.204Z" }, - { url = "https://files.pythonhosted.org/packages/d1/18/884a44aa47f2a3b88dd09bc05a1e40b57878ecd111d17e5bba6f09f8bb77/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2", size = 272194, upload-time = "2025-09-25T19:49:12.524Z" }, - { url = "https://files.pythonhosted.org/packages/0e/8f/371a3ab33c6982070b674f1788e05b656cfbf5685894acbfef0c65483a59/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83", size = 269381, upload-time = "2025-09-25T19:49:14.308Z" }, - { url = "https://files.pythonhosted.org/packages/b1/34/7e4e6abb7a8778db6422e88b1f06eb07c47682313997ee8a8f9352e5a6f1/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746", size = 271750, upload-time = "2025-09-25T19:49:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/c0/1b/54f416be2499bd72123c70d98d36c6cd61a4e33d9b89562c22481c81bb30/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e", size = 303757, upload-time = "2025-09-25T19:49:17.244Z" }, - { url = "https://files.pythonhosted.org/packages/13/62/062c24c7bcf9d2826a1a843d0d605c65a755bc98002923d01fd61270705a/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d", size = 306740, upload-time = "2025-09-25T19:49:18.693Z" }, - { url = "https://files.pythonhosted.org/packages/d5/c8/1fdbfc8c0f20875b6b4020f3c7dc447b8de60aa0be5faaf009d24242aec9/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba", size = 334197, upload-time = "2025-09-25T19:49:20.523Z" }, - { url = "https://files.pythonhosted.org/packages/a6/c1/8b84545382d75bef226fbc6588af0f7b7d095f7cd6a670b42a86243183cd/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41", size = 352974, upload-time = "2025-09-25T19:49:22.254Z" }, - { url = "https://files.pythonhosted.org/packages/10/a6/ffb49d4254ed085e62e3e5dd05982b4393e32fe1e49bb1130186617c29cd/bcrypt-5.0.0-cp313-cp313t-win32.whl", hash = "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861", size = 148498, upload-time = "2025-09-25T19:49:24.134Z" }, - { url = "https://files.pythonhosted.org/packages/48/a9/259559edc85258b6d5fc5471a62a3299a6aa37a6611a169756bf4689323c/bcrypt-5.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e", size = 145853, upload-time = "2025-09-25T19:49:25.702Z" }, - { url = "https://files.pythonhosted.org/packages/2d/df/9714173403c7e8b245acf8e4be8876aac64a209d1b392af457c79e60492e/bcrypt-5.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5", size = 139626, upload-time = "2025-09-25T19:49:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/f8/14/c18006f91816606a4abe294ccc5d1e6f0e42304df5a33710e9e8e95416e1/bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", size = 481862, upload-time = "2025-09-25T19:49:28.365Z" }, - { url = "https://files.pythonhosted.org/packages/67/49/dd074d831f00e589537e07a0725cf0e220d1f0d5d8e85ad5bbff251c45aa/bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", size = 268544, upload-time = "2025-09-25T19:49:30.39Z" }, - { url = "https://files.pythonhosted.org/packages/f5/91/50ccba088b8c474545b034a1424d05195d9fcbaaf802ab8bfe2be5a4e0d7/bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", size = 271787, upload-time = "2025-09-25T19:49:32.144Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e7/d7dba133e02abcda3b52087a7eea8c0d4f64d3e593b4fffc10c31b7061f3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", size = 269753, upload-time = "2025-09-25T19:49:33.885Z" }, - { url = "https://files.pythonhosted.org/packages/33/fc/5b145673c4b8d01018307b5c2c1fc87a6f5a436f0ad56607aee389de8ee3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", size = 289587, upload-time = "2025-09-25T19:49:35.144Z" }, - { url = "https://files.pythonhosted.org/packages/27/d7/1ff22703ec6d4f90e62f1a5654b8867ef96bafb8e8102c2288333e1a6ca6/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", size = 272178, upload-time = "2025-09-25T19:49:36.793Z" }, - { url = "https://files.pythonhosted.org/packages/c8/88/815b6d558a1e4d40ece04a2f84865b0fef233513bd85fd0e40c294272d62/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", size = 269295, upload-time = "2025-09-25T19:49:38.164Z" }, - { url = "https://files.pythonhosted.org/packages/51/8c/e0db387c79ab4931fc89827d37608c31cc57b6edc08ccd2386139028dc0d/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", size = 271700, upload-time = "2025-09-25T19:49:39.917Z" }, - { url = "https://files.pythonhosted.org/packages/06/83/1570edddd150f572dbe9fc00f6203a89fc7d4226821f67328a85c330f239/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", size = 334034, upload-time = "2025-09-25T19:49:41.227Z" }, - { url = "https://files.pythonhosted.org/packages/c9/f2/ea64e51a65e56ae7a8a4ec236c2bfbdd4b23008abd50ac33fbb2d1d15424/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", size = 352766, upload-time = "2025-09-25T19:49:43.08Z" }, - { url = "https://files.pythonhosted.org/packages/d7/d4/1a388d21ee66876f27d1a1f41287897d0c0f1712ef97d395d708ba93004c/bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", size = 152449, upload-time = "2025-09-25T19:49:44.971Z" }, - { url = "https://files.pythonhosted.org/packages/3f/61/3291c2243ae0229e5bca5d19f4032cecad5dfb05a2557169d3a69dc0ba91/bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", size = 149310, upload-time = "2025-09-25T19:49:46.162Z" }, - { url = "https://files.pythonhosted.org/packages/3e/89/4b01c52ae0c1a681d4021e5dd3e45b111a8fb47254a274fa9a378d8d834b/bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", size = 143761, upload-time = "2025-09-25T19:49:47.345Z" }, - { url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553, upload-time = "2025-09-25T19:49:49.006Z" }, - { url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009, upload-time = "2025-09-25T19:49:50.581Z" }, - { url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029, upload-time = "2025-09-25T19:49:52.533Z" }, - { url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907, upload-time = "2025-09-25T19:49:54.709Z" }, - { url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500, upload-time = "2025-09-25T19:49:56.013Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412, upload-time = "2025-09-25T19:49:57.356Z" }, - { url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486, upload-time = "2025-09-25T19:49:59.116Z" }, - { url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940, upload-time = "2025-09-25T19:50:00.869Z" }, - { url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776, upload-time = "2025-09-25T19:50:02.393Z" }, - { url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922, upload-time = "2025-09-25T19:50:04.232Z" }, - { url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367, upload-time = "2025-09-25T19:50:05.559Z" }, - { url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187, upload-time = "2025-09-25T19:50:06.916Z" }, - { url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752, upload-time = "2025-09-25T19:50:08.515Z" }, - { url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881, upload-time = "2025-09-25T19:50:09.742Z" }, - { url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931, upload-time = "2025-09-25T19:50:11.016Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313, upload-time = "2025-09-25T19:50:12.309Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290, upload-time = "2025-09-25T19:50:13.673Z" }, - { url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253, upload-time = "2025-09-25T19:50:15.089Z" }, - { url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084, upload-time = "2025-09-25T19:50:16.699Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185, upload-time = "2025-09-25T19:50:18.525Z" }, - { url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656, upload-time = "2025-09-25T19:50:19.809Z" }, - { url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662, upload-time = "2025-09-25T19:50:21.567Z" }, - { url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240, upload-time = "2025-09-25T19:50:23.305Z" }, - { url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152, upload-time = "2025-09-25T19:50:24.597Z" }, - { url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284, upload-time = "2025-09-25T19:50:26.268Z" }, - { url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643, upload-time = "2025-09-25T19:50:28.02Z" }, - { url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698, upload-time = "2025-09-25T19:50:31.347Z" }, - { url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload-time = "2025-09-25T19:50:34.384Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload-time = "2025-09-25T19:50:35.69Z" }, - { url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" }, + { url = "https://files.pythonhosted.org/packages/13/85/3e65e01985fddf25b64ca67275bb5bdb4040bd1a53b66d355c6c37c8a680/bcrypt-5.0.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be", size = 481806 }, + { url = "https://files.pythonhosted.org/packages/44/dc/01eb79f12b177017a726cbf78330eb0eb442fae0e7b3dfd84ea2849552f3/bcrypt-5.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2", size = 268626 }, + { url = "https://files.pythonhosted.org/packages/8c/cf/e82388ad5959c40d6afd94fb4743cc077129d45b952d46bdc3180310e2df/bcrypt-5.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f", size = 271853 }, + { url = "https://files.pythonhosted.org/packages/ec/86/7134b9dae7cf0efa85671651341f6afa695857fae172615e960fb6a466fa/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86", size = 269793 }, + { url = "https://files.pythonhosted.org/packages/cc/82/6296688ac1b9e503d034e7d0614d56e80c5d1a08402ff856a4549cb59207/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23", size = 289930 }, + { url = "https://files.pythonhosted.org/packages/d1/18/884a44aa47f2a3b88dd09bc05a1e40b57878ecd111d17e5bba6f09f8bb77/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2", size = 272194 }, + { url = "https://files.pythonhosted.org/packages/0e/8f/371a3ab33c6982070b674f1788e05b656cfbf5685894acbfef0c65483a59/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83", size = 269381 }, + { url = "https://files.pythonhosted.org/packages/b1/34/7e4e6abb7a8778db6422e88b1f06eb07c47682313997ee8a8f9352e5a6f1/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746", size = 271750 }, + { url = "https://files.pythonhosted.org/packages/c0/1b/54f416be2499bd72123c70d98d36c6cd61a4e33d9b89562c22481c81bb30/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e", size = 303757 }, + { url = "https://files.pythonhosted.org/packages/13/62/062c24c7bcf9d2826a1a843d0d605c65a755bc98002923d01fd61270705a/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d", size = 306740 }, + { url = "https://files.pythonhosted.org/packages/d5/c8/1fdbfc8c0f20875b6b4020f3c7dc447b8de60aa0be5faaf009d24242aec9/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba", size = 334197 }, + { url = "https://files.pythonhosted.org/packages/a6/c1/8b84545382d75bef226fbc6588af0f7b7d095f7cd6a670b42a86243183cd/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41", size = 352974 }, + { url = "https://files.pythonhosted.org/packages/10/a6/ffb49d4254ed085e62e3e5dd05982b4393e32fe1e49bb1130186617c29cd/bcrypt-5.0.0-cp313-cp313t-win32.whl", hash = "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861", size = 148498 }, + { url = "https://files.pythonhosted.org/packages/48/a9/259559edc85258b6d5fc5471a62a3299a6aa37a6611a169756bf4689323c/bcrypt-5.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e", size = 145853 }, + { url = "https://files.pythonhosted.org/packages/2d/df/9714173403c7e8b245acf8e4be8876aac64a209d1b392af457c79e60492e/bcrypt-5.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5", size = 139626 }, + { url = "https://files.pythonhosted.org/packages/f8/14/c18006f91816606a4abe294ccc5d1e6f0e42304df5a33710e9e8e95416e1/bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", size = 481862 }, + { url = "https://files.pythonhosted.org/packages/67/49/dd074d831f00e589537e07a0725cf0e220d1f0d5d8e85ad5bbff251c45aa/bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", size = 268544 }, + { url = "https://files.pythonhosted.org/packages/f5/91/50ccba088b8c474545b034a1424d05195d9fcbaaf802ab8bfe2be5a4e0d7/bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", size = 271787 }, + { url = "https://files.pythonhosted.org/packages/aa/e7/d7dba133e02abcda3b52087a7eea8c0d4f64d3e593b4fffc10c31b7061f3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", size = 269753 }, + { url = "https://files.pythonhosted.org/packages/33/fc/5b145673c4b8d01018307b5c2c1fc87a6f5a436f0ad56607aee389de8ee3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", size = 289587 }, + { url = "https://files.pythonhosted.org/packages/27/d7/1ff22703ec6d4f90e62f1a5654b8867ef96bafb8e8102c2288333e1a6ca6/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", size = 272178 }, + { url = "https://files.pythonhosted.org/packages/c8/88/815b6d558a1e4d40ece04a2f84865b0fef233513bd85fd0e40c294272d62/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", size = 269295 }, + { url = "https://files.pythonhosted.org/packages/51/8c/e0db387c79ab4931fc89827d37608c31cc57b6edc08ccd2386139028dc0d/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", size = 271700 }, + { url = "https://files.pythonhosted.org/packages/06/83/1570edddd150f572dbe9fc00f6203a89fc7d4226821f67328a85c330f239/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", size = 334034 }, + { url = "https://files.pythonhosted.org/packages/c9/f2/ea64e51a65e56ae7a8a4ec236c2bfbdd4b23008abd50ac33fbb2d1d15424/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", size = 352766 }, + { url = "https://files.pythonhosted.org/packages/d7/d4/1a388d21ee66876f27d1a1f41287897d0c0f1712ef97d395d708ba93004c/bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", size = 152449 }, + { url = "https://files.pythonhosted.org/packages/3f/61/3291c2243ae0229e5bca5d19f4032cecad5dfb05a2557169d3a69dc0ba91/bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", size = 149310 }, + { url = "https://files.pythonhosted.org/packages/3e/89/4b01c52ae0c1a681d4021e5dd3e45b111a8fb47254a274fa9a378d8d834b/bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", size = 143761 }, + { url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553 }, + { url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009 }, + { url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029 }, + { url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907 }, + { url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500 }, + { url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412 }, + { url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486 }, + { url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940 }, + { url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776 }, + { url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922 }, + { url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367 }, + { url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187 }, + { url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752 }, + { url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881 }, + { url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931 }, + { url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313 }, + { url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290 }, + { url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253 }, + { url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084 }, + { url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185 }, + { url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656 }, + { url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662 }, + { url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240 }, + { url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152 }, + { url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284 }, + { url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643 }, + { url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698 }, + { url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725 }, + { url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912 }, + { url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953 }, ] [[package]] @@ -606,18 +606,18 @@ dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721 }, ] [[package]] name = "billiard" version = "4.2.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/23/b12ac0bcdfb7360d664f40a00b1bda139cbbbced012c34e375506dbd0143/billiard-4.2.4.tar.gz", hash = "sha256:55f542c371209e03cd5862299b74e52e4fbcba8250ba611ad94276b369b6a85f", size = 156537, upload-time = "2025-11-30T13:28:48.52Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/23/b12ac0bcdfb7360d664f40a00b1bda139cbbbced012c34e375506dbd0143/billiard-4.2.4.tar.gz", hash = "sha256:55f542c371209e03cd5862299b74e52e4fbcba8250ba611ad94276b369b6a85f", size = 156537 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/87/8bab77b323f16d67be364031220069f79159117dd5e43eeb4be2fef1ac9b/billiard-4.2.4-py3-none-any.whl", hash = "sha256:525b42bdec68d2b983347ac312f892db930858495db601b5836ac24e6477cde5", size = 87070, upload-time = "2025-11-30T13:28:47.016Z" }, + { url = "https://files.pythonhosted.org/packages/cb/87/8bab77b323f16d67be364031220069f79159117dd5e43eeb4be2fef1ac9b/billiard-4.2.4-py3-none-any.whl", hash = "sha256:525b42bdec68d2b983347ac312f892db930858495db601b5836ac24e6477cde5", size = 87070 }, ] [[package]] @@ -627,29 +627,29 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d0/d0/d8cc8c9a4488a787e7fa430f6055e5bd1ddb22c340a751d9e901b82e2efe/blis-1.3.3.tar.gz", hash = "sha256:034d4560ff3cc43e8aa37e188451b0440e3261d989bb8a42ceee865607715ecd", size = 2644873, upload-time = "2025-11-17T12:28:30.511Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/d0/d8cc8c9a4488a787e7fa430f6055e5bd1ddb22c340a751d9e901b82e2efe/blis-1.3.3.tar.gz", hash = "sha256:034d4560ff3cc43e8aa37e188451b0440e3261d989bb8a42ceee865607715ecd", size = 2644873 } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/d1/429cf0cf693d4c7dc2efed969bd474e315aab636e4a95f66c4ed7264912d/blis-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a1c74e100665f8e918ebdbae2794576adf1f691680b5cdb8b29578432f623ef", size = 6929663, upload-time = "2025-11-17T12:27:44.482Z" }, - { url = "https://files.pythonhosted.org/packages/11/69/363c8df8d98b3cc97be19aad6aabb2c9c53f372490d79316bdee92d476e7/blis-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f6c595185176ce021316263e1a1d636a3425b6c48366c1fd712d08d0b71849a", size = 1230939, upload-time = "2025-11-17T12:27:46.19Z" }, - { url = "https://files.pythonhosted.org/packages/96/2a/fbf65d906d823d839076c5150a6f8eb5ecbc5f9135e0b6510609bda1e6b7/blis-1.3.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d734b19fba0be7944f272dfa7b443b37c61f9476d9ab054a9ac53555ceadd2e0", size = 2818835, upload-time = "2025-11-17T12:27:48.167Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ad/58deaa3ad856dd3cc96493e40ffd2ed043d18d4d304f85a65cde1ccbf644/blis-1.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ef6d6e2b599a3a2788eb6d9b443533961265aa4ec49d574ed4bb846e548dcdb", size = 11366550, upload-time = "2025-11-17T12:27:49.958Z" }, - { url = "https://files.pythonhosted.org/packages/78/82/816a7adfe1f7acc8151f01ec86ef64467a3c833932d8f19f8e06613b8a4e/blis-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8c888438ae99c500422d50698e3028b65caa8ebb44e24204d87fda2df64058f7", size = 3023686, upload-time = "2025-11-17T12:27:52.062Z" }, - { url = "https://files.pythonhosted.org/packages/1e/e2/0e93b865f648b5519360846669a35f28ee8f4e1d93d054f6850d8afbabde/blis-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8177879fd3590b5eecdd377f9deafb5dc8af6d684f065bd01553302fb3fcf9a7", size = 14250939, upload-time = "2025-11-17T12:27:53.847Z" }, - { url = "https://files.pythonhosted.org/packages/20/07/fb43edc2ff0a6a367e4a94fc39eb3b85aa1e55e24cc857af2db145ce9f0d/blis-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:f20f7ad69aaffd1ce14fe77de557b6df9b61e0c9e582f75a843715d836b5c8af", size = 6192759, upload-time = "2025-11-17T12:27:56.176Z" }, - { url = "https://files.pythonhosted.org/packages/e6/f7/d26e62d9be3d70473a63e0a5d30bae49c2fe138bebac224adddcdef8a7ce/blis-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1e647341f958421a86b028a2efe16ce19c67dba2a05f79e8f7e80b1ff45328aa", size = 6928322, upload-time = "2025-11-17T12:27:57.965Z" }, - { url = "https://files.pythonhosted.org/packages/4a/78/750d12da388f714958eb2f2fd177652323bbe7ec528365c37129edd6eb84/blis-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d563160f874abb78a57e346f07312c5323f7ad67b6370052b6b17087ef234a8e", size = 1229635, upload-time = "2025-11-17T12:28:00.118Z" }, - { url = "https://files.pythonhosted.org/packages/e8/36/eac4199c5b200a5f3e93cad197da8d26d909f218eb444c4f552647c95240/blis-1.3.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:30b8a5b90cb6cb81d1ada9ae05aa55fb8e70d9a0ae9db40d2401bb9c1c8f14c4", size = 2815650, upload-time = "2025-11-17T12:28:02.544Z" }, - { url = "https://files.pythonhosted.org/packages/bf/51/472e7b36a6bedb5242a9757e7486f702c3619eff76e256735d0c8b1679c6/blis-1.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9f5c53b277f6ac5b3ca30bc12ebab7ea16c8f8c36b14428abb56924213dc127", size = 11359008, upload-time = "2025-11-17T12:28:04.589Z" }, - { url = "https://files.pythonhosted.org/packages/84/da/d0dfb6d6e6321ae44df0321384c32c322bd07b15740d7422727a1a49fc5d/blis-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6297e7616c158b305c9a8a4e47ca5fc9b0785194dd96c903b1a1591a7ca21ddf", size = 3011959, upload-time = "2025-11-17T12:28:06.862Z" }, - { url = "https://files.pythonhosted.org/packages/20/c5/2b0b5e556fa0364ed671051ea078a6d6d7b979b1cfef78d64ad3ca5f0c7f/blis-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3f966ca74f89f8a33e568b9a1d71992fc9a0d29a423e047f0a212643e21b5458", size = 14232456, upload-time = "2025-11-17T12:28:08.779Z" }, - { url = "https://files.pythonhosted.org/packages/31/07/4cdc81a47bf862c0b06d91f1bc6782064e8b69ac9b5d4ff51d97e4ff03da/blis-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:7a0fc4b237a3a453bdc3c7ab48d91439fcd2d013b665c46948d9eaf9c3e45a97", size = 6192624, upload-time = "2025-11-17T12:28:14.197Z" }, - { url = "https://files.pythonhosted.org/packages/5f/8a/80f7c68fbc24a76fc9c18522c46d6d69329c320abb18e26a707a5d874083/blis-1.3.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c3e33cfbf22a418373766816343fcfcd0556012aa3ffdf562c29cddec448a415", size = 6934081, upload-time = "2025-11-17T12:28:16.436Z" }, - { url = "https://files.pythonhosted.org/packages/e5/52/d1aa3a51a7fc299b0c89dcaa971922714f50b1202769eebbdaadd1b5cff7/blis-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6f165930e8d3a85c606d2003211497e28d528c7416fbfeafb6b15600963f7c9b", size = 1231486, upload-time = "2025-11-17T12:28:18.008Z" }, - { url = "https://files.pythonhosted.org/packages/99/4f/badc7bd7f74861b26c10123bba7b9d16f99cd9535ad0128780360713820f/blis-1.3.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:878d4d96d8f2c7a2459024f013f2e4e5f46d708b23437dae970d998e7bff14a0", size = 2814944, upload-time = "2025-11-17T12:28:19.654Z" }, - { url = "https://files.pythonhosted.org/packages/72/a6/f62a3bd814ca19ec7e29ac889fd354adea1217df3183e10217de51e2eb8b/blis-1.3.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f36c0ca84a05ee5d3dbaa38056c4423c1fc29948b17a7923dd2fed8967375d74", size = 11345825, upload-time = "2025-11-17T12:28:21.354Z" }, - { url = "https://files.pythonhosted.org/packages/d4/6c/671af79ee42bc4c968cae35c091ac89e8721c795bfa4639100670dc59139/blis-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e5a662c48cd4aad5dae1a950345df23957524f071315837a4c6feb7d3b288990", size = 3008771, upload-time = "2025-11-17T12:28:23.637Z" }, - { url = "https://files.pythonhosted.org/packages/be/92/7cd7f8490da7c98ee01557f2105885cc597217b0e7fd2eeb9e22cdd4ef23/blis-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9de26fbd72bac900c273b76d46f0b45b77a28eace2e01f6ac6c2239531a413bb", size = 14219213, upload-time = "2025-11-17T12:28:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/0a/de/acae8e9f9a1f4bb393d41c8265898b0f29772e38eac14e9f69d191e2c006/blis-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:9e5fdf4211b1972400f8ff6dafe87cb689c5d84f046b4a76b207c0bd2270faaf", size = 6324695, upload-time = "2025-11-17T12:28:28.401Z" }, + { url = "https://files.pythonhosted.org/packages/16/d1/429cf0cf693d4c7dc2efed969bd474e315aab636e4a95f66c4ed7264912d/blis-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a1c74e100665f8e918ebdbae2794576adf1f691680b5cdb8b29578432f623ef", size = 6929663 }, + { url = "https://files.pythonhosted.org/packages/11/69/363c8df8d98b3cc97be19aad6aabb2c9c53f372490d79316bdee92d476e7/blis-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f6c595185176ce021316263e1a1d636a3425b6c48366c1fd712d08d0b71849a", size = 1230939 }, + { url = "https://files.pythonhosted.org/packages/96/2a/fbf65d906d823d839076c5150a6f8eb5ecbc5f9135e0b6510609bda1e6b7/blis-1.3.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d734b19fba0be7944f272dfa7b443b37c61f9476d9ab054a9ac53555ceadd2e0", size = 2818835 }, + { url = "https://files.pythonhosted.org/packages/d5/ad/58deaa3ad856dd3cc96493e40ffd2ed043d18d4d304f85a65cde1ccbf644/blis-1.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ef6d6e2b599a3a2788eb6d9b443533961265aa4ec49d574ed4bb846e548dcdb", size = 11366550 }, + { url = "https://files.pythonhosted.org/packages/78/82/816a7adfe1f7acc8151f01ec86ef64467a3c833932d8f19f8e06613b8a4e/blis-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8c888438ae99c500422d50698e3028b65caa8ebb44e24204d87fda2df64058f7", size = 3023686 }, + { url = "https://files.pythonhosted.org/packages/1e/e2/0e93b865f648b5519360846669a35f28ee8f4e1d93d054f6850d8afbabde/blis-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8177879fd3590b5eecdd377f9deafb5dc8af6d684f065bd01553302fb3fcf9a7", size = 14250939 }, + { url = "https://files.pythonhosted.org/packages/20/07/fb43edc2ff0a6a367e4a94fc39eb3b85aa1e55e24cc857af2db145ce9f0d/blis-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:f20f7ad69aaffd1ce14fe77de557b6df9b61e0c9e582f75a843715d836b5c8af", size = 6192759 }, + { url = "https://files.pythonhosted.org/packages/e6/f7/d26e62d9be3d70473a63e0a5d30bae49c2fe138bebac224adddcdef8a7ce/blis-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1e647341f958421a86b028a2efe16ce19c67dba2a05f79e8f7e80b1ff45328aa", size = 6928322 }, + { url = "https://files.pythonhosted.org/packages/4a/78/750d12da388f714958eb2f2fd177652323bbe7ec528365c37129edd6eb84/blis-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d563160f874abb78a57e346f07312c5323f7ad67b6370052b6b17087ef234a8e", size = 1229635 }, + { url = "https://files.pythonhosted.org/packages/e8/36/eac4199c5b200a5f3e93cad197da8d26d909f218eb444c4f552647c95240/blis-1.3.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:30b8a5b90cb6cb81d1ada9ae05aa55fb8e70d9a0ae9db40d2401bb9c1c8f14c4", size = 2815650 }, + { url = "https://files.pythonhosted.org/packages/bf/51/472e7b36a6bedb5242a9757e7486f702c3619eff76e256735d0c8b1679c6/blis-1.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9f5c53b277f6ac5b3ca30bc12ebab7ea16c8f8c36b14428abb56924213dc127", size = 11359008 }, + { url = "https://files.pythonhosted.org/packages/84/da/d0dfb6d6e6321ae44df0321384c32c322bd07b15740d7422727a1a49fc5d/blis-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6297e7616c158b305c9a8a4e47ca5fc9b0785194dd96c903b1a1591a7ca21ddf", size = 3011959 }, + { url = "https://files.pythonhosted.org/packages/20/c5/2b0b5e556fa0364ed671051ea078a6d6d7b979b1cfef78d64ad3ca5f0c7f/blis-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3f966ca74f89f8a33e568b9a1d71992fc9a0d29a423e047f0a212643e21b5458", size = 14232456 }, + { url = "https://files.pythonhosted.org/packages/31/07/4cdc81a47bf862c0b06d91f1bc6782064e8b69ac9b5d4ff51d97e4ff03da/blis-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:7a0fc4b237a3a453bdc3c7ab48d91439fcd2d013b665c46948d9eaf9c3e45a97", size = 6192624 }, + { url = "https://files.pythonhosted.org/packages/5f/8a/80f7c68fbc24a76fc9c18522c46d6d69329c320abb18e26a707a5d874083/blis-1.3.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c3e33cfbf22a418373766816343fcfcd0556012aa3ffdf562c29cddec448a415", size = 6934081 }, + { url = "https://files.pythonhosted.org/packages/e5/52/d1aa3a51a7fc299b0c89dcaa971922714f50b1202769eebbdaadd1b5cff7/blis-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6f165930e8d3a85c606d2003211497e28d528c7416fbfeafb6b15600963f7c9b", size = 1231486 }, + { url = "https://files.pythonhosted.org/packages/99/4f/badc7bd7f74861b26c10123bba7b9d16f99cd9535ad0128780360713820f/blis-1.3.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:878d4d96d8f2c7a2459024f013f2e4e5f46d708b23437dae970d998e7bff14a0", size = 2814944 }, + { url = "https://files.pythonhosted.org/packages/72/a6/f62a3bd814ca19ec7e29ac889fd354adea1217df3183e10217de51e2eb8b/blis-1.3.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f36c0ca84a05ee5d3dbaa38056c4423c1fc29948b17a7923dd2fed8967375d74", size = 11345825 }, + { url = "https://files.pythonhosted.org/packages/d4/6c/671af79ee42bc4c968cae35c091ac89e8721c795bfa4639100670dc59139/blis-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e5a662c48cd4aad5dae1a950345df23957524f071315837a4c6feb7d3b288990", size = 3008771 }, + { url = "https://files.pythonhosted.org/packages/be/92/7cd7f8490da7c98ee01557f2105885cc597217b0e7fd2eeb9e22cdd4ef23/blis-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9de26fbd72bac900c273b76d46f0b45b77a28eace2e01f6ac6c2239531a413bb", size = 14219213 }, + { url = "https://files.pythonhosted.org/packages/0a/de/acae8e9f9a1f4bb393d41c8265898b0f29772e38eac14e9f69d191e2c006/blis-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:9e5fdf4211b1972400f8ff6dafe87cb689c5d84f046b4a76b207c0bd2270faaf", size = 6324695 }, ] [[package]] @@ -661,9 +661,9 @@ dependencies = [ { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/f6/b280afd91b2284744c0bb26afa1272bd60270726b4d3999fc27b68200854/boto3-1.42.77.tar.gz", hash = "sha256:c6d9b05e5b86767d4c6ef762f155c891366e5951162f71d030e109fe531f4fd9", size = 112773, upload-time = "2026-03-26T19:25:35.365Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/f6/b280afd91b2284744c0bb26afa1272bd60270726b4d3999fc27b68200854/boto3-1.42.77.tar.gz", hash = "sha256:c6d9b05e5b86767d4c6ef762f155c891366e5951162f71d030e109fe531f4fd9", size = 112773 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/e6/e27fb0956bd52e6ac222d47eaa04d267887ca1d52a6b3c25d2006c8dc9e6/boto3-1.42.77-py3-none-any.whl", hash = "sha256:95eb3ef693068586f70ca3f29c43701c34a9a73d0df413ea7eaff138efa4a6b9", size = 140555, upload-time = "2026-03-26T19:25:32.069Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e6/e27fb0956bd52e6ac222d47eaa04d267887ca1d52a6b3c25d2006c8dc9e6/boto3-1.42.77-py3-none-any.whl", hash = "sha256:95eb3ef693068586f70ca3f29c43701c34a9a73d0df413ea7eaff138efa4a6b9", size = 140555 }, ] [[package]] @@ -675,18 +675,18 @@ dependencies = [ { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/97/1800633988e890b4eea0706b2671342eddfeb33c1eb1d2fe28a8117f7907/botocore-1.42.77.tar.gz", hash = "sha256:cbb0ac410fab4aa0839a521329f970b271ec298d67465ed7fa7d095c0dad9f48", size = 15023911, upload-time = "2026-03-26T19:25:21.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/97/1800633988e890b4eea0706b2671342eddfeb33c1eb1d2fe28a8117f7907/botocore-1.42.77.tar.gz", hash = "sha256:cbb0ac410fab4aa0839a521329f970b271ec298d67465ed7fa7d095c0dad9f48", size = 15023911 } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/7c/6280e6b61f8c232eebd72cae950ed52ce2f38fecf2d178713de3cb1f6298/botocore-1.42.77-py3-none-any.whl", hash = "sha256:807bc2c3825bec6f025506ceeba5f7f111a00de8d58f35c679ee16c8ff6e7b10", size = 14699672, upload-time = "2026-03-26T19:25:15.996Z" }, + { url = "https://files.pythonhosted.org/packages/44/7c/6280e6b61f8c232eebd72cae950ed52ce2f38fecf2d178713de3cb1f6298/botocore-1.42.77-py3-none-any.whl", hash = "sha256:807bc2c3825bec6f025506ceeba5f7f111a00de8d58f35c679ee16c8ff6e7b10", size = 14699672 }, ] [[package]] name = "bracex" version = "2.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642, upload-time = "2025-06-22T19:12:31.254Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508, upload-time = "2025-06-22T19:12:29.781Z" }, + { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508 }, ] [[package]] @@ -698,43 +698,43 @@ dependencies = [ { name = "packaging" }, { name = "pyproject-hooks" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/1d/ab15c8ac57f4ee8778d7633bc6685f808ab414437b8644f555389cdc875e/build-1.4.2.tar.gz", hash = "sha256:35b14e1ee329c186d3f08466003521ed7685ec15ecffc07e68d706090bf161d1", size = 83433, upload-time = "2026-03-25T14:20:27.659Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/1d/ab15c8ac57f4ee8778d7633bc6685f808ab414437b8644f555389cdc875e/build-1.4.2.tar.gz", hash = "sha256:35b14e1ee329c186d3f08466003521ed7685ec15ecffc07e68d706090bf161d1", size = 83433 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl", hash = "sha256:7a4d8651ea877cb2a89458b1b198f2e69f536c95e89129dbf5d448045d60db88", size = 24643, upload-time = "2026-03-25T14:20:26.568Z" }, + { url = "https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl", hash = "sha256:7a4d8651ea877cb2a89458b1b198f2e69f536c95e89129dbf5d448045d60db88", size = 24643 }, ] [[package]] name = "cachetools" version = "7.0.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/dd/57fe3fdb6e65b25a5987fd2cdc7e22db0aef508b91634d2e57d22928d41b/cachetools-7.0.5.tar.gz", hash = "sha256:0cd042c24377200c1dcd225f8b7b12b0ca53cc2c961b43757e774ebe190fd990", size = 37367, upload-time = "2026-03-09T20:51:29.451Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/dd/57fe3fdb6e65b25a5987fd2cdc7e22db0aef508b91634d2e57d22928d41b/cachetools-7.0.5.tar.gz", hash = "sha256:0cd042c24377200c1dcd225f8b7b12b0ca53cc2c961b43757e774ebe190fd990", size = 37367 } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/f3/39cf3367b8107baa44f861dc802cbf16263c945b62d8265d36034fc07bea/cachetools-7.0.5-py3-none-any.whl", hash = "sha256:46bc8ebefbe485407621d0a4264b23c080cedd913921bad7ac3ed2f26c183114", size = 13918, upload-time = "2026-03-09T20:51:27.33Z" }, + { url = "https://files.pythonhosted.org/packages/06/f3/39cf3367b8107baa44f861dc802cbf16263c945b62d8265d36034fc07bea/cachetools-7.0.5-py3-none-any.whl", hash = "sha256:46bc8ebefbe485407621d0a4264b23c080cedd913921bad7ac3ed2f26c183114", size = 13918 }, ] [[package]] name = "catalogue" version = "2.0.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/b4/244d58127e1cdf04cf2dc7d9566f0d24ef01d5ce21811bab088ecc62b5ea/catalogue-2.0.10.tar.gz", hash = "sha256:4f56daa940913d3f09d589c191c74e5a6d51762b3a9e37dd53b7437afd6cda15", size = 19561, upload-time = "2023-09-25T06:29:24.962Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/b4/244d58127e1cdf04cf2dc7d9566f0d24ef01d5ce21811bab088ecc62b5ea/catalogue-2.0.10.tar.gz", hash = "sha256:4f56daa940913d3f09d589c191c74e5a6d51762b3a9e37dd53b7437afd6cda15", size = 19561 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/96/d32b941a501ab566a16358d68b6eb4e4acc373fab3c3c4d7d9e649f7b4bb/catalogue-2.0.10-py3-none-any.whl", hash = "sha256:58c2de0020aa90f4a2da7dfad161bf7b3b054c86a5f09fcedc0b2b740c109a9f", size = 17325, upload-time = "2023-09-25T06:29:23.337Z" }, + { url = "https://files.pythonhosted.org/packages/9e/96/d32b941a501ab566a16358d68b6eb4e4acc373fab3c3c4d7d9e649f7b4bb/catalogue-2.0.10-py3-none-any.whl", hash = "sha256:58c2de0020aa90f4a2da7dfad161bf7b3b054c86a5f09fcedc0b2b740c109a9f", size = 17325 }, ] [[package]] name = "catsu" version = "0.1.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/54/84b62d54afe2695a9170f6f434dc8912976a57bf6680aa99557eadb3676f/catsu-0.1.8.tar.gz", hash = "sha256:961cbc239e77b32ee08e5498a7ddcb83a3c08b025fb3fc504f97c14569e9aa74", size = 49998, upload-time = "2026-02-20T18:03:38.908Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/54/84b62d54afe2695a9170f6f434dc8912976a57bf6680aa99557eadb3676f/catsu-0.1.8.tar.gz", hash = "sha256:961cbc239e77b32ee08e5498a7ddcb83a3c08b025fb3fc504f97c14569e9aa74", size = 49998 } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/e8/e337d7d95f58bb950e0dd5a969b36b7188fa79e4c4341a3da9e36e3ea1ea/catsu-0.1.8-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4fa79883486405b81a1eb79081505a69c3c0f4a782b7199724fc2c15eb6b0274", size = 2214667, upload-time = "2026-02-20T18:03:27.553Z" }, - { url = "https://files.pythonhosted.org/packages/f5/dd/057b3a4591d65fc42605ed6a955dd68797dcc8aea0e02f4ca5990e5edc86/catsu-0.1.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7a27211d2b0a8bc449c7bf8f553c3add438bf9a2e48cc7a5176e39c103fe8369", size = 2149240, upload-time = "2026-02-20T18:03:29.273Z" }, - { url = "https://files.pythonhosted.org/packages/bb/bd/bfc3d49cb8ca68e08521eea0fd61c120e80f8d52b25beeed39d058e383cd/catsu-0.1.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2629b7a0827574426761575fe549754628d023db7be3b9450a6c05f91e6d249f", size = 2401198, upload-time = "2026-02-20T18:03:30.755Z" }, - { url = "https://files.pythonhosted.org/packages/8f/eb/3376890cf51b4bdd283db654d6d3b2fb98d6d26cc97c2a8af2097be1eb65/catsu-0.1.8-cp312-cp312-win_amd64.whl", hash = "sha256:87f179efa920f10b7d3f8ef4c54d18c53f827d9ee2fc3b4e919b4c1a005d2758", size = 1959392, upload-time = "2026-02-20T18:03:32.298Z" }, - { url = "https://files.pythonhosted.org/packages/5b/a4/3542483167e1a98998a2d5fb39a50978200c703d8f5fc9ac63acb529ee29/catsu-0.1.8-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6805074bbfb0c9df144834a02af14573ab423d822207d735b297fb5bed74dee4", size = 2214576, upload-time = "2026-02-20T18:03:33.691Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d5/5f8e90bfcd5b5c3b9e9e51fa644856fa89fb5f4e1f2cefd9e87be05d2e2d/catsu-0.1.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e1a81d2098e5dfb1e2394c7eeb594fe349451ebe4b51994e54dde5e4f06ecdb9", size = 2148945, upload-time = "2026-02-20T18:03:34.84Z" }, - { url = "https://files.pythonhosted.org/packages/76/72/edb51405083f3c9e87c0c30498b5ade0950eec8968d986c0baf1723ebb02/catsu-0.1.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97b84a6412562370ac42270c965ea065d4f8a8f66eb706ac8d4ab5544fbe38fd", size = 2400941, upload-time = "2026-02-20T18:03:36.145Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ce/254323b237e230e5534b3226551172357751c9633438327a939104b56551/catsu-0.1.8-cp313-cp313-win_amd64.whl", hash = "sha256:50ff392f5b9f0ad18fc11496f62079e36bb57270b5bca966feda220744139fa5", size = 1959463, upload-time = "2026-02-20T18:03:37.453Z" }, + { url = "https://files.pythonhosted.org/packages/78/e8/e337d7d95f58bb950e0dd5a969b36b7188fa79e4c4341a3da9e36e3ea1ea/catsu-0.1.8-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4fa79883486405b81a1eb79081505a69c3c0f4a782b7199724fc2c15eb6b0274", size = 2214667 }, + { url = "https://files.pythonhosted.org/packages/f5/dd/057b3a4591d65fc42605ed6a955dd68797dcc8aea0e02f4ca5990e5edc86/catsu-0.1.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7a27211d2b0a8bc449c7bf8f553c3add438bf9a2e48cc7a5176e39c103fe8369", size = 2149240 }, + { url = "https://files.pythonhosted.org/packages/bb/bd/bfc3d49cb8ca68e08521eea0fd61c120e80f8d52b25beeed39d058e383cd/catsu-0.1.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2629b7a0827574426761575fe549754628d023db7be3b9450a6c05f91e6d249f", size = 2401198 }, + { url = "https://files.pythonhosted.org/packages/8f/eb/3376890cf51b4bdd283db654d6d3b2fb98d6d26cc97c2a8af2097be1eb65/catsu-0.1.8-cp312-cp312-win_amd64.whl", hash = "sha256:87f179efa920f10b7d3f8ef4c54d18c53f827d9ee2fc3b4e919b4c1a005d2758", size = 1959392 }, + { url = "https://files.pythonhosted.org/packages/5b/a4/3542483167e1a98998a2d5fb39a50978200c703d8f5fc9ac63acb529ee29/catsu-0.1.8-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6805074bbfb0c9df144834a02af14573ab423d822207d735b297fb5bed74dee4", size = 2214576 }, + { url = "https://files.pythonhosted.org/packages/ba/d5/5f8e90bfcd5b5c3b9e9e51fa644856fa89fb5f4e1f2cefd9e87be05d2e2d/catsu-0.1.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e1a81d2098e5dfb1e2394c7eeb594fe349451ebe4b51994e54dde5e4f06ecdb9", size = 2148945 }, + { url = "https://files.pythonhosted.org/packages/76/72/edb51405083f3c9e87c0c30498b5ade0950eec8968d986c0baf1723ebb02/catsu-0.1.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97b84a6412562370ac42270c965ea065d4f8a8f66eb706ac8d4ab5544fbe38fd", size = 2400941 }, + { url = "https://files.pythonhosted.org/packages/4b/ce/254323b237e230e5534b3226551172357751c9633438327a939104b56551/catsu-0.1.8-cp313-cp313-win_amd64.whl", hash = "sha256:50ff392f5b9f0ad18fc11496f62079e36bb57270b5bca966feda220744139fa5", size = 1959463 }, ] [[package]] @@ -752,9 +752,9 @@ dependencies = [ { name = "tzlocal" }, { name = "vine" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e8/b4/a1233943ab5c8ea05fb877a88a0a0622bf47444b99e4991a8045ac37ea1d/celery-5.6.3.tar.gz", hash = "sha256:177006bd2054b882e9f01be59abd8529e88879ef50d7918a7050c5a9f4e12912", size = 1742243, upload-time = "2026-03-26T12:14:51.76Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/b4/a1233943ab5c8ea05fb877a88a0a0622bf47444b99e4991a8045ac37ea1d/celery-5.6.3.tar.gz", hash = "sha256:177006bd2054b882e9f01be59abd8529e88879ef50d7918a7050c5a9f4e12912", size = 1742243 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/c9/6eccdda96e098f7ae843162db2d3c149c6931a24fda69fe4ab84d0027eb5/celery-5.6.3-py3-none-any.whl", hash = "sha256:0808f42f80909c4d5833202360ffafb2a4f83f4d8e23e1285d926610e9a7afa6", size = 451235, upload-time = "2026-03-26T12:14:49.491Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c9/6eccdda96e098f7ae843162db2d3c149c6931a24fda69fe4ab84d0027eb5/celery-5.6.3-py3-none-any.whl", hash = "sha256:0808f42f80909c4d5833202360ffafb2a4f83f4d8e23e1285d926610e9a7afa6", size = 451235 }, ] [package.optional-dependencies] @@ -774,18 +774,18 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/12/c201f07582068141e88f9a523ab02fdc97de58f2f7c0df775c6c52b9d8dd/cerebras_cloud_sdk-1.67.0.tar.gz", hash = "sha256:3aed6f86c6c7a83ee9d4cfb08a2acea089cebf2af5b8aed116ef79995a4f4813", size = 131536, upload-time = "2026-01-29T23:31:27.306Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/12/c201f07582068141e88f9a523ab02fdc97de58f2f7c0df775c6c52b9d8dd/cerebras_cloud_sdk-1.67.0.tar.gz", hash = "sha256:3aed6f86c6c7a83ee9d4cfb08a2acea089cebf2af5b8aed116ef79995a4f4813", size = 131536 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/5e/36a364f3d1bab4073454b75e7c91dc7ec6879b960063d1a9c929f1c7ea71/cerebras_cloud_sdk-1.67.0-py3-none-any.whl", hash = "sha256:658b79ca2e9c16f75cc6b4e5d523ee014c9e54a88bd39f88905c28ecb33daae1", size = 97807, upload-time = "2026-01-29T23:31:25.77Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/36a364f3d1bab4073454b75e7c91dc7ec6879b960063d1a9c929f1c7ea71/cerebras_cloud_sdk-1.67.0-py3-none-any.whl", hash = "sha256:658b79ca2e9c16f75cc6b4e5d523ee014c9e54a88bd39f88905c28ecb33daae1", size = 97807 }, ] [[package]] name = "certifi" version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684 }, ] [[package]] @@ -795,127 +795,127 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271 }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048 }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529 }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097 }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983 }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519 }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572 }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963 }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361 }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932 }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557 }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762 }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230 }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043 }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446 }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101 }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948 }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422 }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499 }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928 }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302 }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909 }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402 }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780 }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320 }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487 }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049 }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793 }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300 }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244 }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828 }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926 }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328 }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650 }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687 }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773 }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013 }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593 }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354 }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480 }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584 }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443 }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437 }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487 }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726 }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195 }, ] [[package]] name = "charset-normalizer" version = "3.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, - { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, - { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, - { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, - { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, - { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, - { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, - { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, - { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, - { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, - { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, - { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, - { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" }, - { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" }, - { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" }, - { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" }, - { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" }, - { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" }, - { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" }, - { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" }, - { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" }, - { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" }, - { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" }, - { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" }, - { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" }, - { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" }, - { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458, upload-time = "2026-03-15T18:51:41.134Z" }, - { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277, upload-time = "2026-03-15T18:51:42.953Z" }, - { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758, upload-time = "2026-03-15T18:51:44.339Z" }, - { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299, upload-time = "2026-03-15T18:51:45.871Z" }, - { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811, upload-time = "2026-03-15T18:51:47.308Z" }, - { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706, upload-time = "2026-03-15T18:51:48.849Z" }, - { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706, upload-time = "2026-03-15T18:51:50.257Z" }, - { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497, upload-time = "2026-03-15T18:51:52.012Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511, upload-time = "2026-03-15T18:51:53.723Z" }, - { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133, upload-time = "2026-03-15T18:51:55.333Z" }, - { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035, upload-time = "2026-03-15T18:51:56.736Z" }, - { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321, upload-time = "2026-03-15T18:51:58.17Z" }, - { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973, upload-time = "2026-03-15T18:51:59.998Z" }, - { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610, upload-time = "2026-03-15T18:52:02.213Z" }, - { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962, upload-time = "2026-03-15T18:52:03.658Z" }, - { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595, upload-time = "2026-03-15T18:52:05.123Z" }, - { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828, upload-time = "2026-03-15T18:52:06.831Z" }, - { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138, upload-time = "2026-03-15T18:52:08.239Z" }, - { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679, upload-time = "2026-03-15T18:52:10.043Z" }, - { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475, upload-time = "2026-03-15T18:52:11.854Z" }, - { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230, upload-time = "2026-03-15T18:52:13.325Z" }, - { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045, upload-time = "2026-03-15T18:52:14.752Z" }, - { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658, upload-time = "2026-03-15T18:52:16.278Z" }, - { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769, upload-time = "2026-03-15T18:52:17.782Z" }, - { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328, upload-time = "2026-03-15T18:52:19.553Z" }, - { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302, upload-time = "2026-03-15T18:52:21.043Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127, upload-time = "2026-03-15T18:52:22.491Z" }, - { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840, upload-time = "2026-03-15T18:52:24.113Z" }, - { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890, upload-time = "2026-03-15T18:52:25.541Z" }, - { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379, upload-time = "2026-03-15T18:52:27.05Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043, upload-time = "2026-03-15T18:52:28.502Z" }, - { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523, upload-time = "2026-03-15T18:52:29.956Z" }, - { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, + { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154 }, + { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191 }, + { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674 }, + { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259 }, + { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276 }, + { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161 }, + { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452 }, + { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272 }, + { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622 }, + { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056 }, + { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751 }, + { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563 }, + { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265 }, + { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229 }, + { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277 }, + { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817 }, + { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823 }, + { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527 }, + { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388 }, + { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563 }, + { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587 }, + { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724 }, + { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956 }, + { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923 }, + { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366 }, + { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752 }, + { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296 }, + { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956 }, + { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652 }, + { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940 }, + { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101 }, + { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109 }, + { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458 }, + { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277 }, + { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758 }, + { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299 }, + { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811 }, + { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706 }, + { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706 }, + { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497 }, + { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511 }, + { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133 }, + { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035 }, + { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321 }, + { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973 }, + { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610 }, + { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962 }, + { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595 }, + { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828 }, + { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138 }, + { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679 }, + { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475 }, + { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230 }, + { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045 }, + { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658 }, + { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769 }, + { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328 }, + { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302 }, + { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127 }, + { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840 }, + { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890 }, + { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379 }, + { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043 }, + { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523 }, + { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455 }, ] [[package]] @@ -928,9 +928,9 @@ dependencies = [ { name = "tenacity" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/72/fdf8f89ff439f4ec357af0866c819512391936e4e61b6f15635a48434b8a/chonkie-1.6.0.tar.gz", hash = "sha256:14120d80610c1f549027fc7aa9a5ff604a729b545836f6cadd65d5ae83596279", size = 187056, upload-time = "2026-03-11T04:55:07.657Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/72/fdf8f89ff439f4ec357af0866c819512391936e4e61b6f15635a48434b8a/chonkie-1.6.0.tar.gz", hash = "sha256:14120d80610c1f549027fc7aa9a5ff604a729b545836f6cadd65d5ae83596279", size = 187056 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/c2/7ea7d3409df220dd0e048b1113b44f47eccab9d517b00b037ab0e34c3c7a/chonkie-1.6.0-py3-none-any.whl", hash = "sha256:aa357e02f5cdacac6f8280c5e8651207c866b4137bcf20904db8670ee0808877", size = 232997, upload-time = "2026-03-11T04:55:05.252Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c2/7ea7d3409df220dd0e048b1113b44f47eccab9d517b00b037ab0e34c3c7a/chonkie-1.6.0-py3-none-any.whl", hash = "sha256:aa357e02f5cdacac6f8280c5e8651207c866b4137bcf20904db8670ee0808877", size = 232997 }, ] [package.optional-dependencies] @@ -981,25 +981,25 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/f5/547c836f488dc74116ea42a2b2355365f4829fe6d925564f4db7775e6d34/chonkie_core-0.9.2.tar.gz", hash = "sha256:a34f457016fb4bedf9d0a62e55afc334670d88f8316d50ba9af8df83be78b56a", size = 49480, upload-time = "2026-01-21T09:09:46.265Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/f5/547c836f488dc74116ea42a2b2355365f4829fe6d925564f4db7775e6d34/chonkie_core-0.9.2.tar.gz", hash = "sha256:a34f457016fb4bedf9d0a62e55afc334670d88f8316d50ba9af8df83be78b56a", size = 49480 } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/55/8825b059e70a3c757c90efa319e35312a2650431aef1cec11b476ee8699b/chonkie_core-0.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d6d11337842ca90713d8b48d42ce823bcc82874437d4071a8aced9d47b66ec76", size = 347854, upload-time = "2026-01-21T09:09:17.49Z" }, - { url = "https://files.pythonhosted.org/packages/11/51/abac8676470c7e7a7967964eb9066e2efc346339c338da7190a41f412bba/chonkie_core-0.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:77fe2d6b9a709245408b53923dd4ebe1b79e09fdcdc5916df9c97e90c8e13eda", size = 333582, upload-time = "2026-01-21T09:09:18.863Z" }, - { url = "https://files.pythonhosted.org/packages/d4/8c/f62d4ff0efbc08d8c281051ce1752cd6bcb6a7f3e816f8b3c143741d1b86/chonkie_core-0.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0151a74791445985f30bc34cbe7d679e9a716d36e9acf67ed5dc3408be6a426", size = 365189, upload-time = "2026-01-21T09:09:19.884Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f2/cae3bf4174e7d2b8f0c9fe76a341bed8dc48e30069683854ca536fbed5bd/chonkie_core-0.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb5ba84745a7daa32749fe8cbdf691428c2bd4cef14e6555db4ce382b2edef05", size = 385232, upload-time = "2026-01-21T09:09:21.088Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1b/18323d5a7fa3638e9c0aaf00cb1fb1b678546466debb3ad57a6adea9d686/chonkie_core-0.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:ee5093fd6a3f78163445bab5907cc6fd883ccea0514f8866abead0f059683d45", size = 222786, upload-time = "2026-01-21T09:09:21.919Z" }, - { url = "https://files.pythonhosted.org/packages/28/ee/f45c8cb237e5a55eac366c9ac7a4a831329f6cf6f33401609063c1ed660d/chonkie_core-0.9.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6a658cd4fc5cb7c12bc6587246eb545f84d6aa25b86001a92fdcb191cea632c8", size = 347713, upload-time = "2026-01-21T09:09:22.809Z" }, - { url = "https://files.pythonhosted.org/packages/f9/31/0049eb4366cef2171404166e8ff1f39ffe350d7d8921247d262dbb3d4d6c/chonkie_core-0.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ebf47e86be6603cbb940529aed6880655ac7f0bac232952565160fdbea5283d0", size = 333290, upload-time = "2026-01-21T09:09:23.786Z" }, - { url = "https://files.pythonhosted.org/packages/03/d9/3a082faa359e3b24826547bdc725dc9af92b4180b262d3ca6872724cbfbb/chonkie_core-0.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64645ff2c299b953b4a1ff951d1492b4a2b461c624b20604ced5612a8622b030", size = 364600, upload-time = "2026-01-21T09:09:24.64Z" }, - { url = "https://files.pythonhosted.org/packages/04/0b/b89aa90c4f44ce4d82effc064031016bb791979cfd6147c155548e706ef7/chonkie_core-0.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:418748eeea1e09c539fd1d7f7f91c1611765c9d82a523e4c4ae0ed9e30a16b2d", size = 384806, upload-time = "2026-01-21T09:09:25.566Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d9/a7f8577b5550a4323aa9eda16336669b8ad6e8a5ea0c176c9baa25738436/chonkie_core-0.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f3718af3037480023423125e3b4a490c8f4cbf6de38d652169a97dd8ba391953", size = 222393, upload-time = "2026-01-21T09:09:26.897Z" }, - { url = "https://files.pythonhosted.org/packages/72/73/cf6a32cfa9238f19a1d539a1d8371b7d90e21e42458a43fbc949c6476871/chonkie_core-0.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66dc990ac58471fbd12845402726ca69b510602abab7c1c3e52cf8e21f9552e3", size = 363201, upload-time = "2026-01-21T09:09:27.764Z" }, - { url = "https://files.pythonhosted.org/packages/1f/1f/56029d9a557e983cf71d22365b4229c4cfaf09401faa6cbc7e912cef2213/chonkie_core-0.9.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:325c0c853268fbdc37f4a65c3cde68fa56e3a25d164eee9512ceb41edc819902", size = 347292, upload-time = "2026-01-21T09:09:28.676Z" }, - { url = "https://files.pythonhosted.org/packages/21/5b/08b8230d9264007cc7920cf1b1576f2ee1a1ef20d3cd5f8adb5e043e0908/chonkie_core-0.9.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:be9ea3bee05564a721f5c6c8699e1ad5996cf353b2faea2217a08ddee29e2de7", size = 332693, upload-time = "2026-01-21T09:09:29.541Z" }, - { url = "https://files.pythonhosted.org/packages/eb/72/255c918da43a96c90b2bb96f1951a6ea1c513c18b36caecb6e9192275b83/chonkie_core-0.9.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:926db6c26e78b2d84dfb8422073a3f0f20478160ab48204f306fa35f3e1e95d7", size = 364578, upload-time = "2026-01-21T09:09:30.673Z" }, - { url = "https://files.pythonhosted.org/packages/60/80/1710844b9706cd44324446eb368e813ebb4a085e96f469f54b61ddff67dc/chonkie_core-0.9.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c861f147e5932e659dcbe770ca0779a470acc62e9242bff87f36d03eae29644d", size = 384173, upload-time = "2026-01-21T09:09:32.015Z" }, - { url = "https://files.pythonhosted.org/packages/5c/5c/a31b259ea94620ea4e4100ed4cc952ad770f2c7af36293bbc9154efb5c9e/chonkie_core-0.9.2-cp314-cp314-win_amd64.whl", hash = "sha256:83473c708a23652d6dc70142b2e586f965af3031b1d2a5c6336f1fc78614b452", size = 222275, upload-time = "2026-01-21T09:09:34.148Z" }, - { url = "https://files.pythonhosted.org/packages/48/8a/c15c88f59bc9cf6f7ac994689d048fd60fcb72247f6b67ca31dc4eadf2f8/chonkie_core-0.9.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d6c0b89ee5e21d255bcf7d11f17b07ef811a71292717f50889244474cfab8bc", size = 363813, upload-time = "2026-01-21T09:09:35.175Z" }, + { url = "https://files.pythonhosted.org/packages/52/55/8825b059e70a3c757c90efa319e35312a2650431aef1cec11b476ee8699b/chonkie_core-0.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d6d11337842ca90713d8b48d42ce823bcc82874437d4071a8aced9d47b66ec76", size = 347854 }, + { url = "https://files.pythonhosted.org/packages/11/51/abac8676470c7e7a7967964eb9066e2efc346339c338da7190a41f412bba/chonkie_core-0.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:77fe2d6b9a709245408b53923dd4ebe1b79e09fdcdc5916df9c97e90c8e13eda", size = 333582 }, + { url = "https://files.pythonhosted.org/packages/d4/8c/f62d4ff0efbc08d8c281051ce1752cd6bcb6a7f3e816f8b3c143741d1b86/chonkie_core-0.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0151a74791445985f30bc34cbe7d679e9a716d36e9acf67ed5dc3408be6a426", size = 365189 }, + { url = "https://files.pythonhosted.org/packages/7a/f2/cae3bf4174e7d2b8f0c9fe76a341bed8dc48e30069683854ca536fbed5bd/chonkie_core-0.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb5ba84745a7daa32749fe8cbdf691428c2bd4cef14e6555db4ce382b2edef05", size = 385232 }, + { url = "https://files.pythonhosted.org/packages/fe/1b/18323d5a7fa3638e9c0aaf00cb1fb1b678546466debb3ad57a6adea9d686/chonkie_core-0.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:ee5093fd6a3f78163445bab5907cc6fd883ccea0514f8866abead0f059683d45", size = 222786 }, + { url = "https://files.pythonhosted.org/packages/28/ee/f45c8cb237e5a55eac366c9ac7a4a831329f6cf6f33401609063c1ed660d/chonkie_core-0.9.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6a658cd4fc5cb7c12bc6587246eb545f84d6aa25b86001a92fdcb191cea632c8", size = 347713 }, + { url = "https://files.pythonhosted.org/packages/f9/31/0049eb4366cef2171404166e8ff1f39ffe350d7d8921247d262dbb3d4d6c/chonkie_core-0.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ebf47e86be6603cbb940529aed6880655ac7f0bac232952565160fdbea5283d0", size = 333290 }, + { url = "https://files.pythonhosted.org/packages/03/d9/3a082faa359e3b24826547bdc725dc9af92b4180b262d3ca6872724cbfbb/chonkie_core-0.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64645ff2c299b953b4a1ff951d1492b4a2b461c624b20604ced5612a8622b030", size = 364600 }, + { url = "https://files.pythonhosted.org/packages/04/0b/b89aa90c4f44ce4d82effc064031016bb791979cfd6147c155548e706ef7/chonkie_core-0.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:418748eeea1e09c539fd1d7f7f91c1611765c9d82a523e4c4ae0ed9e30a16b2d", size = 384806 }, + { url = "https://files.pythonhosted.org/packages/3d/d9/a7f8577b5550a4323aa9eda16336669b8ad6e8a5ea0c176c9baa25738436/chonkie_core-0.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f3718af3037480023423125e3b4a490c8f4cbf6de38d652169a97dd8ba391953", size = 222393 }, + { url = "https://files.pythonhosted.org/packages/72/73/cf6a32cfa9238f19a1d539a1d8371b7d90e21e42458a43fbc949c6476871/chonkie_core-0.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66dc990ac58471fbd12845402726ca69b510602abab7c1c3e52cf8e21f9552e3", size = 363201 }, + { url = "https://files.pythonhosted.org/packages/1f/1f/56029d9a557e983cf71d22365b4229c4cfaf09401faa6cbc7e912cef2213/chonkie_core-0.9.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:325c0c853268fbdc37f4a65c3cde68fa56e3a25d164eee9512ceb41edc819902", size = 347292 }, + { url = "https://files.pythonhosted.org/packages/21/5b/08b8230d9264007cc7920cf1b1576f2ee1a1ef20d3cd5f8adb5e043e0908/chonkie_core-0.9.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:be9ea3bee05564a721f5c6c8699e1ad5996cf353b2faea2217a08ddee29e2de7", size = 332693 }, + { url = "https://files.pythonhosted.org/packages/eb/72/255c918da43a96c90b2bb96f1951a6ea1c513c18b36caecb6e9192275b83/chonkie_core-0.9.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:926db6c26e78b2d84dfb8422073a3f0f20478160ab48204f306fa35f3e1e95d7", size = 364578 }, + { url = "https://files.pythonhosted.org/packages/60/80/1710844b9706cd44324446eb368e813ebb4a085e96f469f54b61ddff67dc/chonkie_core-0.9.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c861f147e5932e659dcbe770ca0779a470acc62e9242bff87f36d03eae29644d", size = 384173 }, + { url = "https://files.pythonhosted.org/packages/5c/5c/a31b259ea94620ea4e4100ed4cc952ad770f2c7af36293bbc9154efb5c9e/chonkie_core-0.9.2-cp314-cp314-win_amd64.whl", hash = "sha256:83473c708a23652d6dc70142b2e586f965af3031b1d2a5c6336f1fc78614b452", size = 222275 }, + { url = "https://files.pythonhosted.org/packages/48/8a/c15c88f59bc9cf6f7ac994689d048fd60fcb72247f6b67ca31dc4eadf2f8/chonkie_core-0.9.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d6c0b89ee5e21d255bcf7d11f17b07ef811a71292717f50889244474cfab8bc", size = 363813 }, ] [[package]] @@ -1035,13 +1035,13 @@ dependencies = [ { name = "typing-extensions" }, { name = "uvicorn", extra = ["standard"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/6d/ab03e16be3ec663e353166f38be082efb51c0988687f8c8eee1416a7e732/chromadb-1.5.5.tar.gz", hash = "sha256:8d669285b77cc288db27583a57b2f85ba451a9b8e3bef85a260cd78e6b57be35", size = 2411397, upload-time = "2026-03-10T09:30:01.987Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/6d/ab03e16be3ec663e353166f38be082efb51c0988687f8c8eee1416a7e732/chromadb-1.5.5.tar.gz", hash = "sha256:8d669285b77cc288db27583a57b2f85ba451a9b8e3bef85a260cd78e6b57be35", size = 2411397 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/62/ee578f8ccd62928257558b13a3e7c236e402cfb319c9b201b6a75897d644/chromadb-1.5.5-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d590998ed81164afbfb1734bb534b25ec2c9810fc1c5ce53bf8f7ac644a79887", size = 20800888, upload-time = "2026-03-10T09:29:59.546Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ce/430a87d906f79cdc7e23efcd89dd237e3dbedaf6704b40ce1da127993bf8/chromadb-1.5.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5ff2912d20a82fdbf4e27ff3e1c91dab25e2ba2c629f9739bc12c11a3151aac7", size = 20091810, upload-time = "2026-03-10T09:29:56.044Z" }, - { url = "https://files.pythonhosted.org/packages/a8/5a/11543a76ab25c55bec6133bb98ce0dc0f4850acb36600344d8286734a051/chromadb-1.5.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f54e7736ae0eeec436a1c1fb04b77b2c6c4108996790ef16f88327e38ad13cd", size = 20740649, upload-time = "2026-03-10T09:29:49.346Z" }, - { url = "https://files.pythonhosted.org/packages/d3/66/e0b35c41be7c02d6fa37f6c8f61a16b7b20607ddc847574e9a5503fe853b/chromadb-1.5.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb238ae508a6ce68fdd7875e040d7e5aa29d6e40fb651b51f5537b7cda789762", size = 21589423, upload-time = "2026-03-10T09:29:52.724Z" }, - { url = "https://files.pythonhosted.org/packages/a2/df/ce1ffcc0ad3eef8bd35b920809b990e6925ba94b2580dc5bd7ccde0fc06a/chromadb-1.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:3953403b63bb1c05405d10db36d183c4d19a027938c15898510d11943499046f", size = 21915873, upload-time = "2026-03-10T09:30:21.349Z" }, + { url = "https://files.pythonhosted.org/packages/f0/62/ee578f8ccd62928257558b13a3e7c236e402cfb319c9b201b6a75897d644/chromadb-1.5.5-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:d590998ed81164afbfb1734bb534b25ec2c9810fc1c5ce53bf8f7ac644a79887", size = 20800888 }, + { url = "https://files.pythonhosted.org/packages/f8/ce/430a87d906f79cdc7e23efcd89dd237e3dbedaf6704b40ce1da127993bf8/chromadb-1.5.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5ff2912d20a82fdbf4e27ff3e1c91dab25e2ba2c629f9739bc12c11a3151aac7", size = 20091810 }, + { url = "https://files.pythonhosted.org/packages/a8/5a/11543a76ab25c55bec6133bb98ce0dc0f4850acb36600344d8286734a051/chromadb-1.5.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f54e7736ae0eeec436a1c1fb04b77b2c6c4108996790ef16f88327e38ad13cd", size = 20740649 }, + { url = "https://files.pythonhosted.org/packages/d3/66/e0b35c41be7c02d6fa37f6c8f61a16b7b20607ddc847574e9a5503fe853b/chromadb-1.5.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb238ae508a6ce68fdd7875e040d7e5aa29d6e40fb651b51f5537b7cda789762", size = 21589423 }, + { url = "https://files.pythonhosted.org/packages/a2/df/ce1ffcc0ad3eef8bd35b920809b990e6925ba94b2580dc5bd7ccde0fc06a/chromadb-1.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:3953403b63bb1c05405d10db36d183c4d19a027938c15898510d11943499046f", size = 21915873 }, ] [[package]] @@ -1051,9 +1051,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, ] [[package]] @@ -1063,9 +1063,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089, upload-time = "2024-03-24T08:22:07.499Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631, upload-time = "2024-03-24T08:22:06.356Z" }, + { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631 }, ] [[package]] @@ -1075,9 +1075,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343, upload-time = "2025-06-25T00:47:37.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051, upload-time = "2025-06-25T00:47:36.731Z" }, + { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051 }, ] [[package]] @@ -1088,18 +1088,18 @@ dependencies = [ { name = "click" }, { name = "prompt-toolkit" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449, upload-time = "2023-06-15T12:43:51.141Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449 } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289, upload-time = "2023-06-15T12:43:48.626Z" }, + { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289 }, ] [[package]] name = "cloudpathlib" version = "0.23.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f4/18/2ac35d6b3015a0c74e923d94fc69baf8307f7c3233de015d69f99e17afa8/cloudpathlib-0.23.0.tar.gz", hash = "sha256:eb38a34c6b8a048ecfd2b2f60917f7cbad4a105b7c979196450c2f541f4d6b4b", size = 53126, upload-time = "2025-10-07T22:47:56.278Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/18/2ac35d6b3015a0c74e923d94fc69baf8307f7c3233de015d69f99e17afa8/cloudpathlib-0.23.0.tar.gz", hash = "sha256:eb38a34c6b8a048ecfd2b2f60917f7cbad4a105b7c979196450c2f541f4d6b4b", size = 53126 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/8a/c4bb04426d608be4a3171efa2e233d2c59a5c8937850c10d098e126df18e/cloudpathlib-0.23.0-py3-none-any.whl", hash = "sha256:8520b3b01468fee77de37ab5d50b1b524ea6b4a8731c35d1b7407ac0cd716002", size = 62755, upload-time = "2025-10-07T22:47:54.905Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8a/c4bb04426d608be4a3171efa2e233d2c59a5c8937850c10d098e126df18e/cloudpathlib-0.23.0-py3-none-any.whl", hash = "sha256:8520b3b01468fee77de37ab5d50b1b524ea6b4a8731c35d1b7407ac0cd716002", size = 62755 }, ] [[package]] @@ -1116,18 +1116,18 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/75/4c346f6e2322e545f8452692304bd4eca15a2a0209ab9af6a0d1a7810b67/cohere-5.21.1.tar.gz", hash = "sha256:e5ade4423b928b01ff2038980e1b62b2a5bb412c8ab83e30882753b810a5509f", size = 191272, upload-time = "2026-03-26T15:09:27.857Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/75/4c346f6e2322e545f8452692304bd4eca15a2a0209ab9af6a0d1a7810b67/cohere-5.21.1.tar.gz", hash = "sha256:e5ade4423b928b01ff2038980e1b62b2a5bb412c8ab83e30882753b810a5509f", size = 191272 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/50/5538f02ec6d10fbb84f29c1b18c68ff2a03d7877926a80275efdf8755a9f/cohere-5.21.1-py3-none-any.whl", hash = "sha256:f15592ec60d8cf12f01563db94ec28c388c61269d9617f23c2d6d910e505344e", size = 334262, upload-time = "2026-03-26T15:09:26.284Z" }, + { url = "https://files.pythonhosted.org/packages/0a/50/5538f02ec6d10fbb84f29c1b18c68ff2a03d7877926a80275efdf8755a9f/cohere-5.21.1-py3-none-any.whl", hash = "sha256:f15592ec60d8cf12f01563db94ec28c388c61269d9617f23c2d6d910e505344e", size = 334262 }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] [[package]] @@ -1137,9 +1137,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743 }, ] [[package]] @@ -1154,9 +1154,9 @@ dependencies = [ { name = "pysher" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/77/5e8557041d09b29a960208c560e82c5a79d606396192c9a99b02f79b61dd/composio-0.11.4.tar.gz", hash = "sha256:cb0622fa31926d9ce4f09e4aa7605a7873b4e3e61c7d7d094682025f34a19ebb", size = 170542, upload-time = "2026-03-25T21:47:16.924Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/77/5e8557041d09b29a960208c560e82c5a79d606396192c9a99b02f79b61dd/composio-0.11.4.tar.gz", hash = "sha256:cb0622fa31926d9ce4f09e4aa7605a7873b4e3e61c7d7d094682025f34a19ebb", size = 170542 } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/46/ccc66eb27e753db878dcaea5f001543863f8563b7a0fe754da0964de9cff/composio-0.11.4-py3-none-any.whl", hash = "sha256:7362f3a3ef4c71a37bc5e3983daa12531bbbe36d961ee00c3428a572a15b7d00", size = 116357, upload-time = "2026-03-25T21:47:02.682Z" }, + { url = "https://files.pythonhosted.org/packages/41/46/ccc66eb27e753db878dcaea5f001543863f8563b7a0fe754da0964de9cff/composio-0.11.4-py3-none-any.whl", hash = "sha256:7362f3a3ef4c71a37bc5e3983daa12531bbbe36d961ee00c3428a572a15b7d00", size = 116357 }, ] [[package]] @@ -1171,18 +1171,18 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/21/f11039315ce859b723f22b5a16bbb9ba53c83568b56621de7136031a5d3c/composio_client-1.29.0.tar.gz", hash = "sha256:5bbc23a47538e9314bb88cdb9144fd270d5a128ce264936a51ab7db51d75801b", size = 220489, upload-time = "2026-03-20T17:39:24.288Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/21/f11039315ce859b723f22b5a16bbb9ba53c83568b56621de7136031a5d3c/composio_client-1.29.0.tar.gz", hash = "sha256:5bbc23a47538e9314bb88cdb9144fd270d5a128ce264936a51ab7db51d75801b", size = 220489 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/85/66e103d68a20fc602e144fab77746521c9a98f9206dee8984ce69feea61e/composio_client-1.29.0-py3-none-any.whl", hash = "sha256:9910268e77eead235e2b08fc504f2cfd11ad4987d756e260a8ba95f45cbf1b0a", size = 248522, upload-time = "2026-03-20T17:39:23.154Z" }, + { url = "https://files.pythonhosted.org/packages/a4/85/66e103d68a20fc602e144fab77746521c9a98f9206dee8984ce69feea61e/composio_client-1.29.0-py3-none-any.whl", hash = "sha256:9910268e77eead235e2b08fc504f2cfd11ad4987d756e260a8ba95f45cbf1b0a", size = 248522 }, ] [[package]] name = "confection" version = "1.3.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/65/efd0fe8a936fc8ca2978cb7b82581fb20d901c6039e746a808f746b7647b/confection-1.3.3.tar.gz", hash = "sha256:f0f6810d567ff73993fe74d218ca5e1ffb6a44fb03f391257fc5d033546cbfaa", size = 54895, upload-time = "2026-03-24T18:45:24.331Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/65/efd0fe8a936fc8ca2978cb7b82581fb20d901c6039e746a808f746b7647b/confection-1.3.3.tar.gz", hash = "sha256:f0f6810d567ff73993fe74d218ca5e1ffb6a44fb03f391257fc5d033546cbfaa", size = 54895 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/e4/d66708bdf0d92fb4d49b22cdff4b10cec38aca5dcd7e81d909bb55c65cd7/confection-1.3.3-py3-none-any.whl", hash = "sha256:b9fef9ee84b237ef4611ec3eb5797b70e13063e6310ad9f15536373f5e313c82", size = 35902, upload-time = "2026-03-24T18:45:22.664Z" }, + { url = "https://files.pythonhosted.org/packages/8d/e4/d66708bdf0d92fb4d49b22cdff4b10cec38aca5dcd7e81d909bb55c65cd7/confection-1.3.3-py3-none-any.whl", hash = "sha256:b9fef9ee84b237ef4611ec3eb5797b70e13063e6310ad9f15536373f5e313c82", size = 35902 }, ] [[package]] @@ -1192,63 +1192,63 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174 } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, - { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, - { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, - { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, - { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, - { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, - { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, - { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, - { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, - { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, - { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, - { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, - { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, - { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, - { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, - { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, - { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, - { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, - { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, - { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, - { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, - { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, - { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, - { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, - { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, - { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, - { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, - { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, - { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, - { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, - { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, - { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, - { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, - { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, - { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, - { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, - { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, - { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, - { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, - { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, - { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, - { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, - { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, - { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419 }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979 }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653 }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536 }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397 }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601 }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288 }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386 }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018 }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567 }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655 }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257 }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034 }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672 }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234 }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169 }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859 }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062 }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932 }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024 }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578 }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524 }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730 }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897 }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751 }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486 }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106 }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548 }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297 }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023 }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157 }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570 }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713 }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189 }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251 }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810 }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871 }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264 }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819 }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650 }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833 }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692 }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424 }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300 }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769 }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892 }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748 }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554 }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118 }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555 }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295 }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027 }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428 }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331 }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831 }, ] [[package]] @@ -1260,9 +1260,9 @@ dependencies = [ { name = "tld" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/54/6d6ceeff4bed42e7a10d6064d35ee43a810e7b3e8beb4abeae8cff4713ae/courlan-1.3.2.tar.gz", hash = "sha256:0b66f4db3a9c39a6e22dd247c72cfaa57d68ea660e94bb2c84ec7db8712af190", size = 206382, upload-time = "2024-10-29T16:40:20.994Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/54/6d6ceeff4bed42e7a10d6064d35ee43a810e7b3e8beb4abeae8cff4713ae/courlan-1.3.2.tar.gz", hash = "sha256:0b66f4db3a9c39a6e22dd247c72cfaa57d68ea660e94bb2c84ec7db8712af190", size = 206382 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/ca/6a667ccbe649856dcd3458bab80b016681b274399d6211187c6ab969fc50/courlan-1.3.2-py3-none-any.whl", hash = "sha256:d0dab52cf5b5b1000ee2839fbc2837e93b2514d3cb5bb61ae158a55b7a04c6be", size = 33848, upload-time = "2024-10-29T16:40:18.325Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ca/6a667ccbe649856dcd3458bab80b016681b274399d6211187c6ab969fc50/courlan-1.3.2-py3-none-any.whl", hash = "sha256:d0dab52cf5b5b1000ee2839fbc2837e93b2514d3cb5bb61ae158a55b7a04c6be", size = 33848 }, ] [[package]] @@ -1272,9 +1272,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/de/5832661ed55107b8a09af3f0a2e71e0957226a59eb1dcf0a445cce6daf20/croniter-6.2.2.tar.gz", hash = "sha256:ba60832a5ec8e12e51b8691c3309a113d1cf6526bdf1a48150ce8ec7a532d0ab", size = 113762, upload-time = "2026-03-15T08:43:48.112Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/de/5832661ed55107b8a09af3f0a2e71e0957226a59eb1dcf0a445cce6daf20/croniter-6.2.2.tar.gz", hash = "sha256:ba60832a5ec8e12e51b8691c3309a113d1cf6526bdf1a48150ce8ec7a532d0ab", size = 113762 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/39/783980e78cb92c2d7bdb1fc7dbc86e94ccc6d58224d76a7f1f51b6c51e30/croniter-6.2.2-py3-none-any.whl", hash = "sha256:a5d17b1060974d36251ea4faf388233eca8acf0d09cbd92d35f4c4ac8f279960", size = 45422, upload-time = "2026-03-15T08:43:46.626Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/783980e78cb92c2d7bdb1fc7dbc86e94ccc6d58224d76a7f1f51b6c51e30/croniter-6.2.2-py3-none-any.whl", hash = "sha256:a5d17b1060974d36251ea4faf388233eca8acf0d09cbd92d35f4c4ac8f279960", size = 45422 }, ] [[package]] @@ -1284,50 +1284,50 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542, upload-time = "2026-03-25T23:34:53.396Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542 } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401, upload-time = "2026-03-25T23:33:22.096Z" }, - { url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275, upload-time = "2026-03-25T23:33:23.844Z" }, - { url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320, upload-time = "2026-03-25T23:33:25.703Z" }, - { url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082, upload-time = "2026-03-25T23:33:27.423Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514, upload-time = "2026-03-25T23:33:29.206Z" }, - { url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766, upload-time = "2026-03-25T23:33:30.834Z" }, - { url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535, upload-time = "2026-03-25T23:33:33.02Z" }, - { url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618, upload-time = "2026-03-25T23:33:34.567Z" }, - { url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802, upload-time = "2026-03-25T23:33:37.034Z" }, - { url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425, upload-time = "2026-03-25T23:33:38.904Z" }, - { url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530, upload-time = "2026-03-25T23:33:40.842Z" }, - { url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896, upload-time = "2026-03-25T23:33:42.781Z" }, - { url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348, upload-time = "2026-03-25T23:33:45.021Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896, upload-time = "2026-03-25T23:33:46.649Z" }, - { url = "https://files.pythonhosted.org/packages/01/41/3a578f7fd5c70611c0aacba52cd13cb364a5dee895a5c1d467208a9380b0/cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275", size = 7117147, upload-time = "2026-03-25T23:33:48.249Z" }, - { url = "https://files.pythonhosted.org/packages/fa/87/887f35a6fca9dde90cad08e0de0c89263a8e59b2d2ff904fd9fcd8025b6f/cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4", size = 4266221, upload-time = "2026-03-25T23:33:49.874Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a8/0a90c4f0b0871e0e3d1ed126aed101328a8a57fd9fd17f00fb67e82a51ca/cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b", size = 4408952, upload-time = "2026-03-25T23:33:52.128Z" }, - { url = "https://files.pythonhosted.org/packages/16/0b/b239701eb946523e4e9f329336e4ff32b1247e109cbab32d1a7b61da8ed7/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707", size = 4270141, upload-time = "2026-03-25T23:33:54.11Z" }, - { url = "https://files.pythonhosted.org/packages/0f/a8/976acdd4f0f30df7b25605f4b9d3d89295351665c2091d18224f7ad5cdbf/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361", size = 4904178, upload-time = "2026-03-25T23:33:55.725Z" }, - { url = "https://files.pythonhosted.org/packages/b1/1b/bf0e01a88efd0e59679b69f42d4afd5bced8700bb5e80617b2d63a3741af/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b", size = 4441812, upload-time = "2026-03-25T23:33:57.364Z" }, - { url = "https://files.pythonhosted.org/packages/bb/8b/11df86de2ea389c65aa1806f331cae145f2ed18011f30234cc10ca253de8/cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca", size = 3963923, upload-time = "2026-03-25T23:33:59.361Z" }, - { url = "https://files.pythonhosted.org/packages/91/e0/207fb177c3a9ef6a8108f234208c3e9e76a6aa8cf20d51932916bd43bda0/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013", size = 4269695, upload-time = "2026-03-25T23:34:00.909Z" }, - { url = "https://files.pythonhosted.org/packages/21/5e/19f3260ed1e95bced52ace7501fabcd266df67077eeb382b79c81729d2d3/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4", size = 4869785, upload-time = "2026-03-25T23:34:02.796Z" }, - { url = "https://files.pythonhosted.org/packages/10/38/cd7864d79aa1d92ef6f1a584281433419b955ad5a5ba8d1eb6c872165bcb/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a", size = 4441404, upload-time = "2026-03-25T23:34:04.35Z" }, - { url = "https://files.pythonhosted.org/packages/09/0a/4fe7a8d25fed74419f91835cf5829ade6408fd1963c9eae9c4bce390ecbb/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d", size = 4397549, upload-time = "2026-03-25T23:34:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/5f/a0/7d738944eac6513cd60a8da98b65951f4a3b279b93479a7e8926d9cd730b/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736", size = 4651874, upload-time = "2026-03-25T23:34:07.916Z" }, - { url = "https://files.pythonhosted.org/packages/cb/f1/c2326781ca05208845efca38bf714f76939ae446cd492d7613808badedf1/cryptography-46.0.6-cp314-cp314t-win32.whl", hash = "sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed", size = 3001511, upload-time = "2026-03-25T23:34:09.892Z" }, - { url = "https://files.pythonhosted.org/packages/c9/57/fe4a23eb549ac9d903bd4698ffda13383808ef0876cc912bcb2838799ece/cryptography-46.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4", size = 3471692, upload-time = "2026-03-25T23:34:11.613Z" }, - { url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776, upload-time = "2026-03-25T23:34:13.308Z" }, - { url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529, upload-time = "2026-03-25T23:34:15.019Z" }, - { url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827, upload-time = "2026-03-25T23:34:16.604Z" }, - { url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265, upload-time = "2026-03-25T23:34:18.586Z" }, - { url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800, upload-time = "2026-03-25T23:34:20.561Z" }, - { url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771, upload-time = "2026-03-25T23:34:22.406Z" }, - { url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333, upload-time = "2026-03-25T23:34:24.281Z" }, - { url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069, upload-time = "2026-03-25T23:34:25.895Z" }, - { url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358, upload-time = "2026-03-25T23:34:27.67Z" }, - { url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061, upload-time = "2026-03-25T23:34:29.375Z" }, - { url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103, upload-time = "2026-03-25T23:34:32.036Z" }, - { url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255, upload-time = "2026-03-25T23:34:33.679Z" }, - { url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660, upload-time = "2026-03-25T23:34:35.418Z" }, - { url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160, upload-time = "2026-03-25T23:34:37.191Z" }, + { url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401 }, + { url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275 }, + { url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320 }, + { url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082 }, + { url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514 }, + { url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766 }, + { url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535 }, + { url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618 }, + { url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802 }, + { url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425 }, + { url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530 }, + { url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896 }, + { url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348 }, + { url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896 }, + { url = "https://files.pythonhosted.org/packages/01/41/3a578f7fd5c70611c0aacba52cd13cb364a5dee895a5c1d467208a9380b0/cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275", size = 7117147 }, + { url = "https://files.pythonhosted.org/packages/fa/87/887f35a6fca9dde90cad08e0de0c89263a8e59b2d2ff904fd9fcd8025b6f/cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4", size = 4266221 }, + { url = "https://files.pythonhosted.org/packages/aa/a8/0a90c4f0b0871e0e3d1ed126aed101328a8a57fd9fd17f00fb67e82a51ca/cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b", size = 4408952 }, + { url = "https://files.pythonhosted.org/packages/16/0b/b239701eb946523e4e9f329336e4ff32b1247e109cbab32d1a7b61da8ed7/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707", size = 4270141 }, + { url = "https://files.pythonhosted.org/packages/0f/a8/976acdd4f0f30df7b25605f4b9d3d89295351665c2091d18224f7ad5cdbf/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361", size = 4904178 }, + { url = "https://files.pythonhosted.org/packages/b1/1b/bf0e01a88efd0e59679b69f42d4afd5bced8700bb5e80617b2d63a3741af/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b", size = 4441812 }, + { url = "https://files.pythonhosted.org/packages/bb/8b/11df86de2ea389c65aa1806f331cae145f2ed18011f30234cc10ca253de8/cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca", size = 3963923 }, + { url = "https://files.pythonhosted.org/packages/91/e0/207fb177c3a9ef6a8108f234208c3e9e76a6aa8cf20d51932916bd43bda0/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013", size = 4269695 }, + { url = "https://files.pythonhosted.org/packages/21/5e/19f3260ed1e95bced52ace7501fabcd266df67077eeb382b79c81729d2d3/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4", size = 4869785 }, + { url = "https://files.pythonhosted.org/packages/10/38/cd7864d79aa1d92ef6f1a584281433419b955ad5a5ba8d1eb6c872165bcb/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a", size = 4441404 }, + { url = "https://files.pythonhosted.org/packages/09/0a/4fe7a8d25fed74419f91835cf5829ade6408fd1963c9eae9c4bce390ecbb/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d", size = 4397549 }, + { url = "https://files.pythonhosted.org/packages/5f/a0/7d738944eac6513cd60a8da98b65951f4a3b279b93479a7e8926d9cd730b/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736", size = 4651874 }, + { url = "https://files.pythonhosted.org/packages/cb/f1/c2326781ca05208845efca38bf714f76939ae446cd492d7613808badedf1/cryptography-46.0.6-cp314-cp314t-win32.whl", hash = "sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed", size = 3001511 }, + { url = "https://files.pythonhosted.org/packages/c9/57/fe4a23eb549ac9d903bd4698ffda13383808ef0876cc912bcb2838799ece/cryptography-46.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4", size = 3471692 }, + { url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776 }, + { url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529 }, + { url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827 }, + { url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265 }, + { url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800 }, + { url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771 }, + { url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333 }, + { url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069 }, + { url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358 }, + { url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061 }, + { url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103 }, + { url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255 }, + { url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660 }, + { url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160 }, ] [[package]] @@ -1341,9 +1341,9 @@ dependencies = [ { name = "rfc3986" }, { name = "uritemplate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/53/a6/a8436f8f7b5578461a4e5c0dbc8341fe2596b703704cf0f5acd35953cc85/csvw-1.11.0.tar.gz", hash = "sha256:c156466fab3331861e0cf3cbe0c4538705800bfac98819149cd70ecbe6f152eb", size = 34812, upload-time = "2021-05-06T08:15:15.351Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a6/a8436f8f7b5578461a4e5c0dbc8341fe2596b703704cf0f5acd35953cc85/csvw-1.11.0.tar.gz", hash = "sha256:c156466fab3331861e0cf3cbe0c4538705800bfac98819149cd70ecbe6f152eb", size = 34812 } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/ae/afb43a6b88c4202d29e4ec7aca76633d8c530140f4f5a32ee762d07c4607/csvw-1.11.0-py2.py3-none-any.whl", hash = "sha256:243825391308f2568593415364868dda5e50f608fc2bb307fbd79d534af52fd5", size = 35198, upload-time = "2021-05-06T08:15:19.729Z" }, + { url = "https://files.pythonhosted.org/packages/55/ae/afb43a6b88c4202d29e4ec7aca76633d8c530140f4f5a32ee762d07c4607/csvw-1.11.0-py2.py3-none-any.whl", hash = "sha256:243825391308f2568593415364868dda5e50f608fc2bb307fbd79d534af52fd5", size = 35198 }, ] [[package]] @@ -1356,26 +1356,26 @@ dependencies = [ { name = "setuptools" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/0f/581de94b64c5f2327a736270bc7e7a5f8fe5cf1ed56a2203b52de4d8986a/ctranslate2-4.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4c0cbd46a23b8dc37ccdbd9b447cb5f7fadc361c90e9df17d82ca84b1f019986", size = 1257089, upload-time = "2026-02-04T06:11:32.442Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e9/d55b0e436362f9fe26bd98fefd2dd5d81926121f1d7f799c805e6035bb26/ctranslate2-4.7.1-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:5b141ddad1da5f84cf3c2a569a56227a37de649a555d376cbd9b80e8f0373dd8", size = 11918502, upload-time = "2026-02-04T06:11:33.986Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ce/9f29f0b0bb4280c2ebafb3ddb6cdff8ef1c2e185ee020c0ec0ecba7dc934/ctranslate2-4.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d00a62544db4a3caaa58a3c50d39b25613c042b430053ae32384d94eb1d40990", size = 16859601, upload-time = "2026-02-04T06:11:36.227Z" }, - { url = "https://files.pythonhosted.org/packages/b3/86/428d270fd72117d19fb48ed3211aa8a3c8bd7577373252962cb634e0fd01/ctranslate2-4.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:722b93a89647974cbd182b4c7f87fefc7794fff7fc9cbd0303b6447905cc157e", size = 38995338, upload-time = "2026-02-04T06:11:42.789Z" }, - { url = "https://files.pythonhosted.org/packages/4a/f4/d23dbfb9c62cb642c114a30f05d753ba61d6ffbfd8a3a4012fe85a073bcb/ctranslate2-4.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:d0f734dc3757118094663bdaaf713f5090c55c1927fb330a76bb8b84173940e8", size = 18844949, upload-time = "2026-02-04T06:11:45.436Z" }, - { url = "https://files.pythonhosted.org/packages/34/6d/eb49ba05db286b4ea9d5d3fcf5f5cd0a9a5e218d46349618d5041001e303/ctranslate2-4.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b2abf2929756e3ec6246057b56df379995661560a2d776af05f9d97f63afcf5", size = 1256960, upload-time = "2026-02-04T06:11:47.487Z" }, - { url = "https://files.pythonhosted.org/packages/45/5a/b9cce7b00d89fc6fdeaf27587aa52d0597b465058563e93ff50910553bdd/ctranslate2-4.7.1-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:857ef3959d6b1c40dc227c715a36db33db2d097164996d6c75b6db8e30828f52", size = 11918645, upload-time = "2026-02-04T06:11:49.599Z" }, - { url = "https://files.pythonhosted.org/packages/ea/03/c0db0a5276599fb44ceafa2f2cb1afd5628808ec406fe036060a39693680/ctranslate2-4.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:393a9e7e989034660526a2c0e8bb65d1924f43d9a5c77d336494a353d16ba2a4", size = 16860452, upload-time = "2026-02-04T06:11:52.276Z" }, - { url = "https://files.pythonhosted.org/packages/0b/03/4e3728ce29d192ee75ed9a2d8589bf4f19edafe5bed3845187de51b179a3/ctranslate2-4.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a3d0682f2b9082e31c73d75b45f16cde77355ab76d7e8356a24c3cb2480a6d3", size = 38995174, upload-time = "2026-02-04T06:11:55.477Z" }, - { url = "https://files.pythonhosted.org/packages/9b/15/6e8e87c6a201d69803a79ac2e29623ce7c2cc9cd1df9db99810cca714373/ctranslate2-4.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:baa6d2b10f57933d8c11791e8522659217918722d07bbef2389a443801125fe7", size = 18844953, upload-time = "2026-02-04T06:11:58.519Z" }, - { url = "https://files.pythonhosted.org/packages/fd/73/8a6b7ba18cad0c8667ee221ddab8c361cb70926440e5b8dd0e81924c28ac/ctranslate2-4.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d5dfb076566551f4959dfd0706f94c923c1931def9b7bb249a2caa6ab23353a0", size = 1257560, upload-time = "2026-02-04T06:12:00.926Z" }, - { url = "https://files.pythonhosted.org/packages/70/c2/8817ca5d6c1b175b23a12f7c8b91484652f8718a76353317e5919b038733/ctranslate2-4.7.1-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:eecdb4ed934b384f16e8c01b185b082d6b5ffc7dcbb0b6a6eb48cd465282d957", size = 11918995, upload-time = "2026-02-04T06:12:02.875Z" }, - { url = "https://files.pythonhosted.org/packages/ac/33/b8eb3acc67bbca4d9872fc9ff94db78e6167a7ba5cd932f585d1560effc7/ctranslate2-4.7.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1aa6796edcc3c8d163c9e39c429d50076d266d68980fed9d1b2443f617c67e9e", size = 16844162, upload-time = "2026-02-04T06:12:05.099Z" }, - { url = "https://files.pythonhosted.org/packages/80/11/6474893b07121057035069a0a483fe1cd8c47878213f282afb4c0c6fc275/ctranslate2-4.7.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24c0482c51726430fb83724451921c0e539d769c8618dcfd46b1645e7f75960d", size = 38966728, upload-time = "2026-02-04T06:12:07.923Z" }, - { url = "https://files.pythonhosted.org/packages/94/88/8fc7ff435c5e783e5fad9586d839d463e023988dbbbad949d442092d01f1/ctranslate2-4.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:76db234c0446a23d20dd8eeaa7a789cc87d1d05283f48bf3152bae9fa0a69844", size = 19100788, upload-time = "2026-02-04T06:12:10.592Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b3/f100013a76a98d64e67c721bd4559ea4eeb54be3e4ac45f4d801769899af/ctranslate2-4.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:058c9db2277dc8b19ecc86c7937628f69022f341844b9081d2ab642965d88fc6", size = 1280179, upload-time = "2026-02-04T06:12:12.596Z" }, - { url = "https://files.pythonhosted.org/packages/39/22/b77f748015667a5e2ca54a5ee080d7016fce34314f0e8cf904784549305a/ctranslate2-4.7.1-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:5abcf885062c7f28a3f9a46be8d185795e8706ac6230ad086cae0bc82917df31", size = 11940166, upload-time = "2026-02-04T06:12:14.054Z" }, - { url = "https://files.pythonhosted.org/packages/7d/78/6d7fd52f646c6ba3343f71277a9bbef33734632949d1651231948b0f0359/ctranslate2-4.7.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9950acb04a002d5c60ae90a1ddceead1a803af1f00cadd9b1a1dc76e1f017481", size = 16849483, upload-time = "2026-02-04T06:12:17.082Z" }, - { url = "https://files.pythonhosted.org/packages/40/27/58769ff15ac31b44205bd7a8aeca80cf7357c657ea5df1b94ce0f5c83771/ctranslate2-4.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1dcc734e92e3f1ceeaa0c42bbfd009352857be179ecd4a7ed6cccc086a202f58", size = 38949393, upload-time = "2026-02-04T06:12:21.302Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5c/9fa0ad6462b62efd0fb5ac1100eee47bc96ecc198ff4e237c731e5473616/ctranslate2-4.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:dfb7657bdb7b8211c8f9ecb6f3b70bc0db0e0384d01a8b1808cb66fe7199df59", size = 19123451, upload-time = "2026-02-04T06:12:24.115Z" }, + { url = "https://files.pythonhosted.org/packages/fc/0f/581de94b64c5f2327a736270bc7e7a5f8fe5cf1ed56a2203b52de4d8986a/ctranslate2-4.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4c0cbd46a23b8dc37ccdbd9b447cb5f7fadc361c90e9df17d82ca84b1f019986", size = 1257089 }, + { url = "https://files.pythonhosted.org/packages/3d/e9/d55b0e436362f9fe26bd98fefd2dd5d81926121f1d7f799c805e6035bb26/ctranslate2-4.7.1-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:5b141ddad1da5f84cf3c2a569a56227a37de649a555d376cbd9b80e8f0373dd8", size = 11918502 }, + { url = "https://files.pythonhosted.org/packages/ec/ce/9f29f0b0bb4280c2ebafb3ddb6cdff8ef1c2e185ee020c0ec0ecba7dc934/ctranslate2-4.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d00a62544db4a3caaa58a3c50d39b25613c042b430053ae32384d94eb1d40990", size = 16859601 }, + { url = "https://files.pythonhosted.org/packages/b3/86/428d270fd72117d19fb48ed3211aa8a3c8bd7577373252962cb634e0fd01/ctranslate2-4.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:722b93a89647974cbd182b4c7f87fefc7794fff7fc9cbd0303b6447905cc157e", size = 38995338 }, + { url = "https://files.pythonhosted.org/packages/4a/f4/d23dbfb9c62cb642c114a30f05d753ba61d6ffbfd8a3a4012fe85a073bcb/ctranslate2-4.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:d0f734dc3757118094663bdaaf713f5090c55c1927fb330a76bb8b84173940e8", size = 18844949 }, + { url = "https://files.pythonhosted.org/packages/34/6d/eb49ba05db286b4ea9d5d3fcf5f5cd0a9a5e218d46349618d5041001e303/ctranslate2-4.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b2abf2929756e3ec6246057b56df379995661560a2d776af05f9d97f63afcf5", size = 1256960 }, + { url = "https://files.pythonhosted.org/packages/45/5a/b9cce7b00d89fc6fdeaf27587aa52d0597b465058563e93ff50910553bdd/ctranslate2-4.7.1-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:857ef3959d6b1c40dc227c715a36db33db2d097164996d6c75b6db8e30828f52", size = 11918645 }, + { url = "https://files.pythonhosted.org/packages/ea/03/c0db0a5276599fb44ceafa2f2cb1afd5628808ec406fe036060a39693680/ctranslate2-4.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:393a9e7e989034660526a2c0e8bb65d1924f43d9a5c77d336494a353d16ba2a4", size = 16860452 }, + { url = "https://files.pythonhosted.org/packages/0b/03/4e3728ce29d192ee75ed9a2d8589bf4f19edafe5bed3845187de51b179a3/ctranslate2-4.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a3d0682f2b9082e31c73d75b45f16cde77355ab76d7e8356a24c3cb2480a6d3", size = 38995174 }, + { url = "https://files.pythonhosted.org/packages/9b/15/6e8e87c6a201d69803a79ac2e29623ce7c2cc9cd1df9db99810cca714373/ctranslate2-4.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:baa6d2b10f57933d8c11791e8522659217918722d07bbef2389a443801125fe7", size = 18844953 }, + { url = "https://files.pythonhosted.org/packages/fd/73/8a6b7ba18cad0c8667ee221ddab8c361cb70926440e5b8dd0e81924c28ac/ctranslate2-4.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d5dfb076566551f4959dfd0706f94c923c1931def9b7bb249a2caa6ab23353a0", size = 1257560 }, + { url = "https://files.pythonhosted.org/packages/70/c2/8817ca5d6c1b175b23a12f7c8b91484652f8718a76353317e5919b038733/ctranslate2-4.7.1-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:eecdb4ed934b384f16e8c01b185b082d6b5ffc7dcbb0b6a6eb48cd465282d957", size = 11918995 }, + { url = "https://files.pythonhosted.org/packages/ac/33/b8eb3acc67bbca4d9872fc9ff94db78e6167a7ba5cd932f585d1560effc7/ctranslate2-4.7.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1aa6796edcc3c8d163c9e39c429d50076d266d68980fed9d1b2443f617c67e9e", size = 16844162 }, + { url = "https://files.pythonhosted.org/packages/80/11/6474893b07121057035069a0a483fe1cd8c47878213f282afb4c0c6fc275/ctranslate2-4.7.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24c0482c51726430fb83724451921c0e539d769c8618dcfd46b1645e7f75960d", size = 38966728 }, + { url = "https://files.pythonhosted.org/packages/94/88/8fc7ff435c5e783e5fad9586d839d463e023988dbbbad949d442092d01f1/ctranslate2-4.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:76db234c0446a23d20dd8eeaa7a789cc87d1d05283f48bf3152bae9fa0a69844", size = 19100788 }, + { url = "https://files.pythonhosted.org/packages/d9/b3/f100013a76a98d64e67c721bd4559ea4eeb54be3e4ac45f4d801769899af/ctranslate2-4.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:058c9db2277dc8b19ecc86c7937628f69022f341844b9081d2ab642965d88fc6", size = 1280179 }, + { url = "https://files.pythonhosted.org/packages/39/22/b77f748015667a5e2ca54a5ee080d7016fce34314f0e8cf904784549305a/ctranslate2-4.7.1-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:5abcf885062c7f28a3f9a46be8d185795e8706ac6230ad086cae0bc82917df31", size = 11940166 }, + { url = "https://files.pythonhosted.org/packages/7d/78/6d7fd52f646c6ba3343f71277a9bbef33734632949d1651231948b0f0359/ctranslate2-4.7.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9950acb04a002d5c60ae90a1ddceead1a803af1f00cadd9b1a1dc76e1f017481", size = 16849483 }, + { url = "https://files.pythonhosted.org/packages/40/27/58769ff15ac31b44205bd7a8aeca80cf7357c657ea5df1b94ce0f5c83771/ctranslate2-4.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1dcc734e92e3f1ceeaa0c42bbfd009352857be179ecd4a7ed6cccc086a202f58", size = 38949393 }, + { url = "https://files.pythonhosted.org/packages/0e/5c/9fa0ad6462b62efd0fb5ac1100eee47bc96ecc198ff4e237c731e5473616/ctranslate2-4.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:dfb7657bdb7b8211c8f9ecb6f3b70bc0db0e0384d01a8b1808cb66fe7199df59", size = 19123451 }, ] [[package]] @@ -1386,14 +1386,14 @@ dependencies = [ { name = "cuda-pathfinder", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404, upload-time = "2026-03-11T00:12:44.041Z" }, - { url = "https://files.pythonhosted.org/packages/1f/92/f899f7bbb5617bb65ec52a6eac1e9a1447a86b916c4194f8a5001b8cde0c/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955", size = 6320619, upload-time = "2026-03-11T00:12:45.939Z" }, - { url = "https://files.pythonhosted.org/packages/df/93/eef988860a3ca985f82c4f3174fc0cdd94e07331ba9a92e8e064c260337f/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0", size = 5614610, upload-time = "2026-03-11T00:12:50.337Z" }, - { url = "https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d", size = 6149914, upload-time = "2026-03-11T00:12:52.374Z" }, - { url = "https://files.pythonhosted.org/packages/c0/87/87a014f045b77c6de5c8527b0757fe644417b184e5367db977236a141602/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e", size = 5685673, upload-time = "2026-03-11T00:12:56.371Z" }, - { url = "https://files.pythonhosted.org/packages/ee/5e/c0fe77a73aaefd3fff25ffaccaac69c5a63eafdf8b9a4c476626ef0ac703/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626", size = 6191386, upload-time = "2026-03-11T00:12:58.965Z" }, - { url = "https://files.pythonhosted.org/packages/5f/58/ed2c3b39c8dd5f96aa7a4abef0d47a73932c7a988e30f5fa428f00ed0da1/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771", size = 5507469, upload-time = "2026-03-11T00:13:04.063Z" }, - { url = "https://files.pythonhosted.org/packages/1f/01/0c941b112ceeb21439b05895eace78ca1aa2eaaf695c8521a068fd9b4c00/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b", size = 6059693, upload-time = "2026-03-11T00:13:06.003Z" }, + { url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404 }, + { url = "https://files.pythonhosted.org/packages/1f/92/f899f7bbb5617bb65ec52a6eac1e9a1447a86b916c4194f8a5001b8cde0c/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955", size = 6320619 }, + { url = "https://files.pythonhosted.org/packages/df/93/eef988860a3ca985f82c4f3174fc0cdd94e07331ba9a92e8e064c260337f/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0", size = 5614610 }, + { url = "https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d", size = 6149914 }, + { url = "https://files.pythonhosted.org/packages/c0/87/87a014f045b77c6de5c8527b0757fe644417b184e5367db977236a141602/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e", size = 5685673 }, + { url = "https://files.pythonhosted.org/packages/ee/5e/c0fe77a73aaefd3fff25ffaccaac69c5a63eafdf8b9a4c476626ef0ac703/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626", size = 6191386 }, + { url = "https://files.pythonhosted.org/packages/5f/58/ed2c3b39c8dd5f96aa7a4abef0d47a73932c7a988e30f5fa428f00ed0da1/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771", size = 5507469 }, + { url = "https://files.pythonhosted.org/packages/1f/01/0c941b112ceeb21439b05895eace78ca1aa2eaaf695c8521a068fd9b4c00/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b", size = 6059693 }, ] [[package]] @@ -1401,7 +1401,7 @@ name = "cuda-pathfinder" version = "1.5.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/66/0c02bd330e7d976f83fa68583d6198d76f23581bcbb5c0e98a6148f326e5/cuda_pathfinder-1.5.0-py3-none-any.whl", hash = "sha256:498f90a9e9de36044a7924742aecce11c50c49f735f1bc53e05aa46de9ea4110", size = 49739, upload-time = "2026-03-24T21:14:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/93/66/0c02bd330e7d976f83fa68583d6198d76f23581bcbb5c0e98a6148f326e5/cuda_pathfinder-1.5.0-py3-none-any.whl", hash = "sha256:498f90a9e9de36044a7924742aecce11c50c49f735f1bc53e05aa46de9ea4110", size = 49739 }, ] [[package]] @@ -1409,7 +1409,7 @@ name = "cuda-toolkit" version = "13.0.2" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364, upload-time = "2025-12-19T23:24:07.328Z" }, + { url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364 }, ] [package.optional-dependencies] @@ -1454,13 +1454,13 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "regex" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/fa/b2d55f0d53c7c7f5dc0b6dbb48cc4344ee84fb572f23de28040bf2cde89d/curated-tokenizers-0.0.9.tar.gz", hash = "sha256:c93d47e54ab3528a6db2796eeb4bdce5d44e8226c671e42c2f23522ab1d0ce25", size = 2237055, upload-time = "2024-01-18T13:45:52.36Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/fa/b2d55f0d53c7c7f5dc0b6dbb48cc4344ee84fb572f23de28040bf2cde89d/curated-tokenizers-0.0.9.tar.gz", hash = "sha256:c93d47e54ab3528a6db2796eeb4bdce5d44e8226c671e42c2f23522ab1d0ce25", size = 2237055 } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/3e/c10474a21ed0166f94cebb46fe96cf07fdf7f399d84e6157ec4dfbd97b53/curated_tokenizers-0.0.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e66aedfeae0c91f3f3e2980b17933b3d08f3fba6c8ba7057b9b05d596e8a0b27", size = 734544, upload-time = "2024-01-18T13:45:18.864Z" }, - { url = "https://files.pythonhosted.org/packages/34/fb/d6e57b1155bee398f43de58ecdcdda44957e9635183312ac0820a19fc94d/curated_tokenizers-0.0.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2abbb571666a9c9b3a15f9df022e25ed1137e9fa8346788aaa747c00f940a3c6", size = 703466, upload-time = "2024-01-18T13:45:20.051Z" }, - { url = "https://files.pythonhosted.org/packages/1b/7c/2d24633275f2854c144652ee6ef97ae85d444855b6da5aa1203678541fa5/curated_tokenizers-0.0.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64b9991a9720a0ce8cc72d29791fd73f2cc2bef0241b002fd2a756ec8a629143", size = 706194, upload-time = "2024-01-18T13:45:21.844Z" }, - { url = "https://files.pythonhosted.org/packages/21/24/12ae8f92d0e319ed07dd9c3ee5d24e71dd6ff3dd8d4dbe2126a6e5cbf7a1/curated_tokenizers-0.0.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35fb208a01f2b3f22172596915d229859549a2d76e484be976dd728b1ca3bdec", size = 734029, upload-time = "2024-01-18T13:45:23.628Z" }, - { url = "https://files.pythonhosted.org/packages/0f/fc/776b7464029ea126bf55df2a5edd437178ad8e5c0126f953891dfa603f9c/curated_tokenizers-0.0.9-cp312-cp312-win_amd64.whl", hash = "sha256:209d756694c7fb000a0b642016eb6e71c740cfce293adcbf3384aa2a1e701eb2", size = 731507, upload-time = "2024-01-18T13:45:25.416Z" }, + { url = "https://files.pythonhosted.org/packages/10/3e/c10474a21ed0166f94cebb46fe96cf07fdf7f399d84e6157ec4dfbd97b53/curated_tokenizers-0.0.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e66aedfeae0c91f3f3e2980b17933b3d08f3fba6c8ba7057b9b05d596e8a0b27", size = 734544 }, + { url = "https://files.pythonhosted.org/packages/34/fb/d6e57b1155bee398f43de58ecdcdda44957e9635183312ac0820a19fc94d/curated_tokenizers-0.0.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2abbb571666a9c9b3a15f9df022e25ed1137e9fa8346788aaa747c00f940a3c6", size = 703466 }, + { url = "https://files.pythonhosted.org/packages/1b/7c/2d24633275f2854c144652ee6ef97ae85d444855b6da5aa1203678541fa5/curated_tokenizers-0.0.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64b9991a9720a0ce8cc72d29791fd73f2cc2bef0241b002fd2a756ec8a629143", size = 706194 }, + { url = "https://files.pythonhosted.org/packages/21/24/12ae8f92d0e319ed07dd9c3ee5d24e71dd6ff3dd8d4dbe2126a6e5cbf7a1/curated_tokenizers-0.0.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35fb208a01f2b3f22172596915d229859549a2d76e484be976dd728b1ca3bdec", size = 734029 }, + { url = "https://files.pythonhosted.org/packages/0f/fc/776b7464029ea126bf55df2a5edd437178ad8e5c0126f953891dfa603f9c/curated_tokenizers-0.0.9-cp312-cp312-win_amd64.whl", hash = "sha256:209d756694c7fb000a0b642016eb6e71c740cfce293adcbf3384aa2a1e701eb2", size = 731507 }, ] [[package]] @@ -1470,66 +1470,66 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "torch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/06/6c12c149a7f737dacc76b4c3949dbc7ff87d622567b86996896ae4d104aa/curated-transformers-0.1.1.tar.gz", hash = "sha256:4671f03314df30efda2ec2b59bc7692ea34fcea44cb65382342c16684e8a2119", size = 16313, upload-time = "2023-05-24T07:29:22.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/70/06/6c12c149a7f737dacc76b4c3949dbc7ff87d622567b86996896ae4d104aa/curated-transformers-0.1.1.tar.gz", hash = "sha256:4671f03314df30efda2ec2b59bc7692ea34fcea44cb65382342c16684e8a2119", size = 16313 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/67/3b72b3fdfcadab61bc8f59c17e63770e526ffabd583ed32f174a7c01af85/curated_transformers-0.1.1-py2.py3-none-any.whl", hash = "sha256:d716063d73d803c6925d2dab56fde9b9ab8e89e663c2c0587804944ba488ff01", size = 25972, upload-time = "2023-05-24T07:29:21.119Z" }, + { url = "https://files.pythonhosted.org/packages/d6/67/3b72b3fdfcadab61bc8f59c17e63770e526ffabd583ed32f174a7c01af85/curated_transformers-0.1.1-py2.py3-none-any.whl", hash = "sha256:d716063d73d803c6925d2dab56fde9b9ab8e89e663c2c0587804944ba488ff01", size = 25972 }, ] [[package]] name = "cycler" version = "0.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 }, ] [[package]] name = "cymem" version = "2.0.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/2f0fbb32535c3731b7c2974c569fb9325e0a38ed5565a08e1139a3b71e82/cymem-2.0.13.tar.gz", hash = "sha256:1c91a92ae8c7104275ac26bd4d29b08ccd3e7faff5893d3858cb6fadf1bc1588", size = 12320, upload-time = "2025-11-14T14:58:36.902Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/2f0fbb32535c3731b7c2974c569fb9325e0a38ed5565a08e1139a3b71e82/cymem-2.0.13.tar.gz", hash = "sha256:1c91a92ae8c7104275ac26bd4d29b08ccd3e7faff5893d3858cb6fadf1bc1588", size = 12320 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/52/478a2911ab5028cb710b4900d64aceba6f4f882fcb13fd8d40a456a1b6dc/cymem-2.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8afbc5162a0fe14b6463e1c4e45248a1b2fe2cbcecc8a5b9e511117080da0eb", size = 43745, upload-time = "2025-11-14T14:57:32.52Z" }, - { url = "https://files.pythonhosted.org/packages/f9/71/f0f8adee945524774b16af326bd314a14a478ed369a728a22834e6785a18/cymem-2.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9251d889348fe79a75e9b3e4d1b5fa651fca8a64500820685d73a3acc21b6a8", size = 42927, upload-time = "2025-11-14T14:57:33.827Z" }, - { url = "https://files.pythonhosted.org/packages/62/6d/159780fe162ff715d62b809246e5fc20901cef87ca28b67d255a8d741861/cymem-2.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:742fc19764467a49ed22e56a4d2134c262d73a6c635409584ae3bf9afa092c33", size = 258346, upload-time = "2025-11-14T14:57:34.917Z" }, - { url = "https://files.pythonhosted.org/packages/eb/12/678d16f7aa1996f947bf17b8cfb917ea9c9674ef5e2bd3690c04123d5680/cymem-2.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f190a92fe46197ee64d32560eb121c2809bb843341733227f51538ce77b3410d", size = 260843, upload-time = "2025-11-14T14:57:36.503Z" }, - { url = "https://files.pythonhosted.org/packages/31/5d/0dd8c167c08cd85e70d274b7235cfe1e31b3cebc99221178eaf4bbb95c6f/cymem-2.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d670329ee8dbbbf241b7c08069fe3f1d3a1a3e2d69c7d05ea008a7010d826298", size = 254607, upload-time = "2025-11-14T14:57:38.036Z" }, - { url = "https://files.pythonhosted.org/packages/b7/c9/d6514a412a1160aa65db539836b3d47f9b59f6675f294ec34ae32f867c82/cymem-2.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a84ba3178d9128b9ffb52ce81ebab456e9fe959125b51109f5b73ebdfc6b60d6", size = 262421, upload-time = "2025-11-14T14:57:39.265Z" }, - { url = "https://files.pythonhosted.org/packages/dd/fe/3ee37d02ca4040f2fb22d34eb415198f955862b5dd47eee01df4c8f5454c/cymem-2.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:2ff1c41fd59b789579fdace78aa587c5fc091991fa59458c382b116fc36e30dc", size = 40176, upload-time = "2025-11-14T14:57:40.706Z" }, - { url = "https://files.pythonhosted.org/packages/94/fb/1b681635bfd5f2274d0caa8f934b58435db6c091b97f5593738065ddb786/cymem-2.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:6bbd701338df7bf408648191dff52472a9b334f71bcd31a21a41d83821050f67", size = 35959, upload-time = "2025-11-14T14:57:41.682Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0f/95a4d1e3bebfdfa7829252369357cf9a764f67569328cd9221f21e2c952e/cymem-2.0.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:891fd9030293a8b652dc7fb9fdc79a910a6c76fc679cd775e6741b819ffea476", size = 43478, upload-time = "2025-11-14T14:57:42.682Z" }, - { url = "https://files.pythonhosted.org/packages/bf/a0/8fc929cc29ae466b7b4efc23ece99cbd3ea34992ccff319089c624d667fd/cymem-2.0.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:89c4889bd16513ce1644ccfe1e7c473ba7ca150f0621e66feac3a571bde09e7e", size = 42695, upload-time = "2025-11-14T14:57:43.741Z" }, - { url = "https://files.pythonhosted.org/packages/4a/b3/deeb01354ebaf384438083ffe0310209ef903db3e7ba5a8f584b06d28387/cymem-2.0.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:45dcaba0f48bef9cc3d8b0b92058640244a95a9f12542210b51318da97c2cf28", size = 250573, upload-time = "2025-11-14T14:57:44.81Z" }, - { url = "https://files.pythonhosted.org/packages/36/36/bc980b9a14409f3356309c45a8d88d58797d02002a9d794dd6c84e809d3a/cymem-2.0.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e96848faaafccc0abd631f1c5fb194eac0caee4f5a8777fdbb3e349d3a21741c", size = 254572, upload-time = "2025-11-14T14:57:46.023Z" }, - { url = "https://files.pythonhosted.org/packages/fd/dd/a12522952624685bd0f8968e26d2ed6d059c967413ce6eb52292f538f1b0/cymem-2.0.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e02d3e2c3bfeb21185d5a4a70790d9df40629a87d8d7617dc22b4e864f665fa3", size = 248060, upload-time = "2025-11-14T14:57:47.605Z" }, - { url = "https://files.pythonhosted.org/packages/08/11/5dc933ddfeb2dfea747a0b935cb965b9a7580b324d96fc5f5a1b5ff8df29/cymem-2.0.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fece5229fd5ecdcd7a0738affb8c59890e13073ae5626544e13825f26c019d3c", size = 254601, upload-time = "2025-11-14T14:57:48.861Z" }, - { url = "https://files.pythonhosted.org/packages/70/66/d23b06166864fa94e13a98e5922986ce774832936473578febce64448d75/cymem-2.0.13-cp313-cp313-win_amd64.whl", hash = "sha256:38aefeb269597c1a0c2ddf1567dd8605489b661fa0369c6406c1acd433b4c7ba", size = 40103, upload-time = "2025-11-14T14:57:50.396Z" }, - { url = "https://files.pythonhosted.org/packages/2f/9e/c7b21271ab88a21760f3afdec84d2bc09ffa9e6c8d774ad9d4f1afab0416/cymem-2.0.13-cp313-cp313-win_arm64.whl", hash = "sha256:717270dcfd8c8096b479c42708b151002ff98e434a7b6f1f916387a6c791e2ad", size = 36016, upload-time = "2025-11-14T14:57:51.611Z" }, - { url = "https://files.pythonhosted.org/packages/7f/28/d3b03427edc04ae04910edf1c24b993881c3ba93a9729a42bcbb816a1808/cymem-2.0.13-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7e1a863a7f144ffb345397813701509cfc74fc9ed360a4d92799805b4b865dd1", size = 46429, upload-time = "2025-11-14T14:57:52.582Z" }, - { url = "https://files.pythonhosted.org/packages/35/a9/7ed53e481f47ebfb922b0b42e980cec83e98ccb2137dc597ea156642440c/cymem-2.0.13-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c16cb80efc017b054f78998c6b4b013cef509c7b3d802707ce1f85a1d68361bf", size = 46205, upload-time = "2025-11-14T14:57:53.64Z" }, - { url = "https://files.pythonhosted.org/packages/61/39/a3d6ad073cf7f0fbbb8bbf09698c3c8fac11be3f791d710239a4e8dd3438/cymem-2.0.13-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d78a27c88b26c89bd1ece247d1d5939dba05a1dae6305aad8fd8056b17ddb51", size = 296083, upload-time = "2025-11-14T14:57:55.922Z" }, - { url = "https://files.pythonhosted.org/packages/36/0c/20697c8bc19f624a595833e566f37d7bcb9167b0ce69de896eba7cfc9c2d/cymem-2.0.13-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6d36710760f817194dacb09d9fc45cb6a5062ed75e85f0ef7ad7aeeb13d80cc3", size = 286159, upload-time = "2025-11-14T14:57:57.106Z" }, - { url = "https://files.pythonhosted.org/packages/82/d4/9326e3422d1c2d2b4a8fb859bdcce80138f6ab721ddafa4cba328a505c71/cymem-2.0.13-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c8f30971cadd5dcf73bcfbbc5849b1f1e1f40db8cd846c4aa7d3b5e035c7b583", size = 288186, upload-time = "2025-11-14T14:57:58.334Z" }, - { url = "https://files.pythonhosted.org/packages/ed/bc/68da7dd749b72884dc22e898562f335002d70306069d496376e5ff3b6153/cymem-2.0.13-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9d441d0e45798ec1fd330373bf7ffa6b795f229275f64016b6a193e6e2a51522", size = 290353, upload-time = "2025-11-14T14:58:00.562Z" }, - { url = "https://files.pythonhosted.org/packages/50/23/dbf2ad6ecd19b99b3aab6203b1a06608bbd04a09c522d836b854f2f30f73/cymem-2.0.13-cp313-cp313t-win_amd64.whl", hash = "sha256:d1c950eebb9f0f15e3ef3591313482a5a611d16fc12d545e2018cd607f40f472", size = 44764, upload-time = "2025-11-14T14:58:01.793Z" }, - { url = "https://files.pythonhosted.org/packages/54/3f/35701c13e1fc7b0895198c8b20068c569a841e0daf8e0b14d1dc0816b28f/cymem-2.0.13-cp313-cp313t-win_arm64.whl", hash = "sha256:042e8611ef862c34a97b13241f5d0da86d58aca3cecc45c533496678e75c5a1f", size = 38964, upload-time = "2025-11-14T14:58:02.87Z" }, - { url = "https://files.pythonhosted.org/packages/a7/2e/f0e1596010a9a57fa9ebd124a678c07c5b2092283781ae51e79edcf5cb98/cymem-2.0.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d2a4bf67db76c7b6afc33de44fb1c318207c3224a30da02c70901936b5aafdf1", size = 43812, upload-time = "2025-11-14T14:58:04.227Z" }, - { url = "https://files.pythonhosted.org/packages/bc/45/8ccc21df08fcbfa6aa3efeb7efc11a1c81c90e7476e255768bb9c29ba02a/cymem-2.0.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:92a2ce50afa5625fb5ce7c9302cee61e23a57ccac52cd0410b4858e572f8614b", size = 42951, upload-time = "2025-11-14T14:58:05.424Z" }, - { url = "https://files.pythonhosted.org/packages/01/8c/fe16531631f051d3d1226fa42e2d76fd2c8d5cfa893ec93baee90c7a9d90/cymem-2.0.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bc116a70cc3a5dc3d1684db5268eff9399a0be8603980005e5b889564f1ea42f", size = 249878, upload-time = "2025-11-14T14:58:06.95Z" }, - { url = "https://files.pythonhosted.org/packages/47/4b/39d67b80ffb260457c05fcc545de37d82e9e2dbafc93dd6b64f17e09b933/cymem-2.0.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68489bf0035c4c280614067ab6a82815b01dc9fcd486742a5306fe9f68deb7ef", size = 252571, upload-time = "2025-11-14T14:58:08.232Z" }, - { url = "https://files.pythonhosted.org/packages/53/0e/76f6531f74dfdfe7107899cce93ab063bb7ee086ccd3910522b31f623c08/cymem-2.0.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:03cb7bdb55718d5eb6ef0340b1d2430ba1386db30d33e9134d01ba9d6d34d705", size = 248555, upload-time = "2025-11-14T14:58:09.429Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7c/eee56757db81f0aefc2615267677ae145aff74228f529838425057003c0d/cymem-2.0.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1710390e7fb2510a8091a1991024d8ae838fd06b02cdfdcd35f006192e3c6b0e", size = 254177, upload-time = "2025-11-14T14:58:10.594Z" }, - { url = "https://files.pythonhosted.org/packages/77/e0/a4b58ec9e53c836dce07ef39837a64a599f4a21a134fc7ca57a3a8f9a4b5/cymem-2.0.13-cp314-cp314-win_amd64.whl", hash = "sha256:ac699c8ec72a3a9de8109bd78821ab22f60b14cf2abccd970b5ff310e14158ed", size = 40853, upload-time = "2025-11-14T14:58:12.116Z" }, - { url = "https://files.pythonhosted.org/packages/61/81/9931d1f83e5aeba175440af0b28f0c2e6f71274a5a7b688bc3e907669388/cymem-2.0.13-cp314-cp314-win_arm64.whl", hash = "sha256:90c2d0c04bcda12cd5cebe9be93ce3af6742ad8da96e1b1907e3f8e00291def1", size = 36970, upload-time = "2025-11-14T14:58:13.114Z" }, - { url = "https://files.pythonhosted.org/packages/b7/ef/af447c2184dec6dec973be14614df8ccb4d16d1c74e0784ab4f02538433c/cymem-2.0.13-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff036bbc1464993552fd1251b0a83fe102af334b301e3896d7aa05a4999ad042", size = 46804, upload-time = "2025-11-14T14:58:14.113Z" }, - { url = "https://files.pythonhosted.org/packages/8c/95/e10f33a8d4fc17f9b933d451038218437f9326c2abb15a3e7f58ce2a06ec/cymem-2.0.13-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb8291691ba7ff4e6e000224cc97a744a8d9588418535c9454fd8436911df612", size = 46254, upload-time = "2025-11-14T14:58:15.156Z" }, - { url = "https://files.pythonhosted.org/packages/e7/7a/5efeb2d2ea6ebad2745301ad33a4fa9a8f9a33b66623ee4d9185683007a6/cymem-2.0.13-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d8d06ea59006b1251ad5794bcc00121e148434826090ead0073c7b7fedebe431", size = 296061, upload-time = "2025-11-14T14:58:16.254Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/2a3f65842cc8443c2c0650cf23d525be06c8761ab212e0a095a88627be1b/cymem-2.0.13-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c0046a619ecc845ccb4528b37b63426a0cbcb4f14d7940add3391f59f13701e6", size = 285784, upload-time = "2025-11-14T14:58:17.412Z" }, - { url = "https://files.pythonhosted.org/packages/98/73/dd5f9729398f0108c2e71d942253d0d484d299d08b02e474d7cfc43ed0b0/cymem-2.0.13-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:18ad5b116a82fa3674bc8838bd3792891b428971e2123ae8c0fd3ca472157c5e", size = 288062, upload-time = "2025-11-14T14:58:20.225Z" }, - { url = "https://files.pythonhosted.org/packages/5a/01/ffe51729a8f961a437920560659073e47f575d4627445216c1177ecd4a41/cymem-2.0.13-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:666ce6146bc61b9318aa70d91ce33f126b6344a25cf0b925621baed0c161e9cc", size = 290465, upload-time = "2025-11-14T14:58:21.815Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ac/c9e7d68607f71ef978c81e334ab2898b426944c71950212b1467186f69f9/cymem-2.0.13-cp314-cp314t-win_amd64.whl", hash = "sha256:84c1168c563d9d1e04546cb65e3e54fde2bf814f7c7faf11fc06436598e386d1", size = 46665, upload-time = "2025-11-14T14:58:23.512Z" }, - { url = "https://files.pythonhosted.org/packages/66/66/150e406a2db5535533aa3c946de58f0371f2e412e23f050c704588023e6e/cymem-2.0.13-cp314-cp314t-win_arm64.whl", hash = "sha256:e9027764dc5f1999fb4b4cabee1d0322c59e330c0a6485b436a68275f614277f", size = 39715, upload-time = "2025-11-14T14:58:24.773Z" }, + { url = "https://files.pythonhosted.org/packages/c9/52/478a2911ab5028cb710b4900d64aceba6f4f882fcb13fd8d40a456a1b6dc/cymem-2.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8afbc5162a0fe14b6463e1c4e45248a1b2fe2cbcecc8a5b9e511117080da0eb", size = 43745 }, + { url = "https://files.pythonhosted.org/packages/f9/71/f0f8adee945524774b16af326bd314a14a478ed369a728a22834e6785a18/cymem-2.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9251d889348fe79a75e9b3e4d1b5fa651fca8a64500820685d73a3acc21b6a8", size = 42927 }, + { url = "https://files.pythonhosted.org/packages/62/6d/159780fe162ff715d62b809246e5fc20901cef87ca28b67d255a8d741861/cymem-2.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:742fc19764467a49ed22e56a4d2134c262d73a6c635409584ae3bf9afa092c33", size = 258346 }, + { url = "https://files.pythonhosted.org/packages/eb/12/678d16f7aa1996f947bf17b8cfb917ea9c9674ef5e2bd3690c04123d5680/cymem-2.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f190a92fe46197ee64d32560eb121c2809bb843341733227f51538ce77b3410d", size = 260843 }, + { url = "https://files.pythonhosted.org/packages/31/5d/0dd8c167c08cd85e70d274b7235cfe1e31b3cebc99221178eaf4bbb95c6f/cymem-2.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d670329ee8dbbbf241b7c08069fe3f1d3a1a3e2d69c7d05ea008a7010d826298", size = 254607 }, + { url = "https://files.pythonhosted.org/packages/b7/c9/d6514a412a1160aa65db539836b3d47f9b59f6675f294ec34ae32f867c82/cymem-2.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a84ba3178d9128b9ffb52ce81ebab456e9fe959125b51109f5b73ebdfc6b60d6", size = 262421 }, + { url = "https://files.pythonhosted.org/packages/dd/fe/3ee37d02ca4040f2fb22d34eb415198f955862b5dd47eee01df4c8f5454c/cymem-2.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:2ff1c41fd59b789579fdace78aa587c5fc091991fa59458c382b116fc36e30dc", size = 40176 }, + { url = "https://files.pythonhosted.org/packages/94/fb/1b681635bfd5f2274d0caa8f934b58435db6c091b97f5593738065ddb786/cymem-2.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:6bbd701338df7bf408648191dff52472a9b334f71bcd31a21a41d83821050f67", size = 35959 }, + { url = "https://files.pythonhosted.org/packages/ce/0f/95a4d1e3bebfdfa7829252369357cf9a764f67569328cd9221f21e2c952e/cymem-2.0.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:891fd9030293a8b652dc7fb9fdc79a910a6c76fc679cd775e6741b819ffea476", size = 43478 }, + { url = "https://files.pythonhosted.org/packages/bf/a0/8fc929cc29ae466b7b4efc23ece99cbd3ea34992ccff319089c624d667fd/cymem-2.0.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:89c4889bd16513ce1644ccfe1e7c473ba7ca150f0621e66feac3a571bde09e7e", size = 42695 }, + { url = "https://files.pythonhosted.org/packages/4a/b3/deeb01354ebaf384438083ffe0310209ef903db3e7ba5a8f584b06d28387/cymem-2.0.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:45dcaba0f48bef9cc3d8b0b92058640244a95a9f12542210b51318da97c2cf28", size = 250573 }, + { url = "https://files.pythonhosted.org/packages/36/36/bc980b9a14409f3356309c45a8d88d58797d02002a9d794dd6c84e809d3a/cymem-2.0.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e96848faaafccc0abd631f1c5fb194eac0caee4f5a8777fdbb3e349d3a21741c", size = 254572 }, + { url = "https://files.pythonhosted.org/packages/fd/dd/a12522952624685bd0f8968e26d2ed6d059c967413ce6eb52292f538f1b0/cymem-2.0.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e02d3e2c3bfeb21185d5a4a70790d9df40629a87d8d7617dc22b4e864f665fa3", size = 248060 }, + { url = "https://files.pythonhosted.org/packages/08/11/5dc933ddfeb2dfea747a0b935cb965b9a7580b324d96fc5f5a1b5ff8df29/cymem-2.0.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fece5229fd5ecdcd7a0738affb8c59890e13073ae5626544e13825f26c019d3c", size = 254601 }, + { url = "https://files.pythonhosted.org/packages/70/66/d23b06166864fa94e13a98e5922986ce774832936473578febce64448d75/cymem-2.0.13-cp313-cp313-win_amd64.whl", hash = "sha256:38aefeb269597c1a0c2ddf1567dd8605489b661fa0369c6406c1acd433b4c7ba", size = 40103 }, + { url = "https://files.pythonhosted.org/packages/2f/9e/c7b21271ab88a21760f3afdec84d2bc09ffa9e6c8d774ad9d4f1afab0416/cymem-2.0.13-cp313-cp313-win_arm64.whl", hash = "sha256:717270dcfd8c8096b479c42708b151002ff98e434a7b6f1f916387a6c791e2ad", size = 36016 }, + { url = "https://files.pythonhosted.org/packages/7f/28/d3b03427edc04ae04910edf1c24b993881c3ba93a9729a42bcbb816a1808/cymem-2.0.13-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7e1a863a7f144ffb345397813701509cfc74fc9ed360a4d92799805b4b865dd1", size = 46429 }, + { url = "https://files.pythonhosted.org/packages/35/a9/7ed53e481f47ebfb922b0b42e980cec83e98ccb2137dc597ea156642440c/cymem-2.0.13-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c16cb80efc017b054f78998c6b4b013cef509c7b3d802707ce1f85a1d68361bf", size = 46205 }, + { url = "https://files.pythonhosted.org/packages/61/39/a3d6ad073cf7f0fbbb8bbf09698c3c8fac11be3f791d710239a4e8dd3438/cymem-2.0.13-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d78a27c88b26c89bd1ece247d1d5939dba05a1dae6305aad8fd8056b17ddb51", size = 296083 }, + { url = "https://files.pythonhosted.org/packages/36/0c/20697c8bc19f624a595833e566f37d7bcb9167b0ce69de896eba7cfc9c2d/cymem-2.0.13-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6d36710760f817194dacb09d9fc45cb6a5062ed75e85f0ef7ad7aeeb13d80cc3", size = 286159 }, + { url = "https://files.pythonhosted.org/packages/82/d4/9326e3422d1c2d2b4a8fb859bdcce80138f6ab721ddafa4cba328a505c71/cymem-2.0.13-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c8f30971cadd5dcf73bcfbbc5849b1f1e1f40db8cd846c4aa7d3b5e035c7b583", size = 288186 }, + { url = "https://files.pythonhosted.org/packages/ed/bc/68da7dd749b72884dc22e898562f335002d70306069d496376e5ff3b6153/cymem-2.0.13-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9d441d0e45798ec1fd330373bf7ffa6b795f229275f64016b6a193e6e2a51522", size = 290353 }, + { url = "https://files.pythonhosted.org/packages/50/23/dbf2ad6ecd19b99b3aab6203b1a06608bbd04a09c522d836b854f2f30f73/cymem-2.0.13-cp313-cp313t-win_amd64.whl", hash = "sha256:d1c950eebb9f0f15e3ef3591313482a5a611d16fc12d545e2018cd607f40f472", size = 44764 }, + { url = "https://files.pythonhosted.org/packages/54/3f/35701c13e1fc7b0895198c8b20068c569a841e0daf8e0b14d1dc0816b28f/cymem-2.0.13-cp313-cp313t-win_arm64.whl", hash = "sha256:042e8611ef862c34a97b13241f5d0da86d58aca3cecc45c533496678e75c5a1f", size = 38964 }, + { url = "https://files.pythonhosted.org/packages/a7/2e/f0e1596010a9a57fa9ebd124a678c07c5b2092283781ae51e79edcf5cb98/cymem-2.0.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d2a4bf67db76c7b6afc33de44fb1c318207c3224a30da02c70901936b5aafdf1", size = 43812 }, + { url = "https://files.pythonhosted.org/packages/bc/45/8ccc21df08fcbfa6aa3efeb7efc11a1c81c90e7476e255768bb9c29ba02a/cymem-2.0.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:92a2ce50afa5625fb5ce7c9302cee61e23a57ccac52cd0410b4858e572f8614b", size = 42951 }, + { url = "https://files.pythonhosted.org/packages/01/8c/fe16531631f051d3d1226fa42e2d76fd2c8d5cfa893ec93baee90c7a9d90/cymem-2.0.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bc116a70cc3a5dc3d1684db5268eff9399a0be8603980005e5b889564f1ea42f", size = 249878 }, + { url = "https://files.pythonhosted.org/packages/47/4b/39d67b80ffb260457c05fcc545de37d82e9e2dbafc93dd6b64f17e09b933/cymem-2.0.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68489bf0035c4c280614067ab6a82815b01dc9fcd486742a5306fe9f68deb7ef", size = 252571 }, + { url = "https://files.pythonhosted.org/packages/53/0e/76f6531f74dfdfe7107899cce93ab063bb7ee086ccd3910522b31f623c08/cymem-2.0.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:03cb7bdb55718d5eb6ef0340b1d2430ba1386db30d33e9134d01ba9d6d34d705", size = 248555 }, + { url = "https://files.pythonhosted.org/packages/c7/7c/eee56757db81f0aefc2615267677ae145aff74228f529838425057003c0d/cymem-2.0.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1710390e7fb2510a8091a1991024d8ae838fd06b02cdfdcd35f006192e3c6b0e", size = 254177 }, + { url = "https://files.pythonhosted.org/packages/77/e0/a4b58ec9e53c836dce07ef39837a64a599f4a21a134fc7ca57a3a8f9a4b5/cymem-2.0.13-cp314-cp314-win_amd64.whl", hash = "sha256:ac699c8ec72a3a9de8109bd78821ab22f60b14cf2abccd970b5ff310e14158ed", size = 40853 }, + { url = "https://files.pythonhosted.org/packages/61/81/9931d1f83e5aeba175440af0b28f0c2e6f71274a5a7b688bc3e907669388/cymem-2.0.13-cp314-cp314-win_arm64.whl", hash = "sha256:90c2d0c04bcda12cd5cebe9be93ce3af6742ad8da96e1b1907e3f8e00291def1", size = 36970 }, + { url = "https://files.pythonhosted.org/packages/b7/ef/af447c2184dec6dec973be14614df8ccb4d16d1c74e0784ab4f02538433c/cymem-2.0.13-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff036bbc1464993552fd1251b0a83fe102af334b301e3896d7aa05a4999ad042", size = 46804 }, + { url = "https://files.pythonhosted.org/packages/8c/95/e10f33a8d4fc17f9b933d451038218437f9326c2abb15a3e7f58ce2a06ec/cymem-2.0.13-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb8291691ba7ff4e6e000224cc97a744a8d9588418535c9454fd8436911df612", size = 46254 }, + { url = "https://files.pythonhosted.org/packages/e7/7a/5efeb2d2ea6ebad2745301ad33a4fa9a8f9a33b66623ee4d9185683007a6/cymem-2.0.13-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d8d06ea59006b1251ad5794bcc00121e148434826090ead0073c7b7fedebe431", size = 296061 }, + { url = "https://files.pythonhosted.org/packages/0b/28/2a3f65842cc8443c2c0650cf23d525be06c8761ab212e0a095a88627be1b/cymem-2.0.13-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c0046a619ecc845ccb4528b37b63426a0cbcb4f14d7940add3391f59f13701e6", size = 285784 }, + { url = "https://files.pythonhosted.org/packages/98/73/dd5f9729398f0108c2e71d942253d0d484d299d08b02e474d7cfc43ed0b0/cymem-2.0.13-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:18ad5b116a82fa3674bc8838bd3792891b428971e2123ae8c0fd3ca472157c5e", size = 288062 }, + { url = "https://files.pythonhosted.org/packages/5a/01/ffe51729a8f961a437920560659073e47f575d4627445216c1177ecd4a41/cymem-2.0.13-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:666ce6146bc61b9318aa70d91ce33f126b6344a25cf0b925621baed0c161e9cc", size = 290465 }, + { url = "https://files.pythonhosted.org/packages/fd/ac/c9e7d68607f71ef978c81e334ab2898b426944c71950212b1467186f69f9/cymem-2.0.13-cp314-cp314t-win_amd64.whl", hash = "sha256:84c1168c563d9d1e04546cb65e3e54fde2bf814f7c7faf11fc06436598e386d1", size = 46665 }, + { url = "https://files.pythonhosted.org/packages/66/66/150e406a2db5535533aa3c946de58f0371f2e412e23f050c704588023e6e/cymem-2.0.13-cp314-cp314t-win_arm64.whl", hash = "sha256:e9027764dc5f1999fb4b4cabee1d0322c59e330c0a6485b436a68275f614277f", size = 39715 }, ] [[package]] @@ -1540,9 +1540,9 @@ dependencies = [ { name = "marshmallow" }, { name = "typing-inspect" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227, upload-time = "2024-06-09T16:20:19.103Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" }, + { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686 }, ] [[package]] @@ -1564,9 +1564,9 @@ dependencies = [ { name = "tqdm" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e3/9d/348ed92110ba5f9b70b51ca1078d4809767a835aa2b7ce7e74ad2b98323d/datasets-4.0.0.tar.gz", hash = "sha256:9657e7140a9050db13443ba21cb5de185af8af944479b00e7ff1e00a61c8dbf1", size = 569566, upload-time = "2025-07-09T14:35:52.431Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/9d/348ed92110ba5f9b70b51ca1078d4809767a835aa2b7ce7e74ad2b98323d/datasets-4.0.0.tar.gz", hash = "sha256:9657e7140a9050db13443ba21cb5de185af8af944479b00e7ff1e00a61c8dbf1", size = 569566 } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/62/eb8157afb21bd229c864521c1ab4fa8e9b4f1b06bafdd8c4668a7a31b5dd/datasets-4.0.0-py3-none-any.whl", hash = "sha256:7ef95e62025fd122882dbce6cb904c8cd3fbc829de6669a5eb939c77d50e203d", size = 494825, upload-time = "2025-07-09T14:35:50.658Z" }, + { url = "https://files.pythonhosted.org/packages/eb/62/eb8157afb21bd229c864521c1ab4fa8e9b4f1b06bafdd8c4668a7a31b5dd/datasets-4.0.0-py3-none-any.whl", hash = "sha256:7ef95e62025fd122882dbce6cb904c8cd3fbc829de6669a5eb939c77d50e203d", size = 494825 }, ] [[package]] @@ -1579,9 +1579,9 @@ dependencies = [ { name = "regex" }, { name = "tzlocal" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/2d/a0ccdb78788064fa0dc901b8524e50615c42be1d78b78d646d0b28d09180/dateparser-1.4.0.tar.gz", hash = "sha256:97a21840d5ecdf7630c584f673338a5afac5dfe84f647baf4d7e8df98f9354a4", size = 321512, upload-time = "2026-03-26T09:56:10.292Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/2d/a0ccdb78788064fa0dc901b8524e50615c42be1d78b78d646d0b28d09180/dateparser-1.4.0.tar.gz", hash = "sha256:97a21840d5ecdf7630c584f673338a5afac5dfe84f647baf4d7e8df98f9354a4", size = 321512 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/0b/3c3bb7cbe757279e693a0be6049048012f794d01f81099609ecd53b899f0/dateparser-1.4.0-py3-none-any.whl", hash = "sha256:7902b8e85d603494bf70a5a0b1decdddb2270b9c6e6b2bc8a57b93476c0df378", size = 300379, upload-time = "2026-03-26T09:56:08.409Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0b/3c3bb7cbe757279e693a0be6049048012f794d01f81099609ecd53b899f0/dateparser-1.4.0-py3-none-any.whl", hash = "sha256:7902b8e85d603494bf70a5a0b1decdddb2270b9c6e6b2bc8a57b93476c0df378", size = 300379 }, ] [[package]] @@ -1608,9 +1608,9 @@ dependencies = [ { name = "urllib3" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/03/b07a337cfba22d8cea1384c71e806361180c91bac3ce10320caf709153af/daytona-0.158.0.tar.gz", hash = "sha256:cddc54228727221a1f892f6a09df79eb5545f44f442544f0c97d48664c3c72b0", size = 127196, upload-time = "2026-03-27T14:39:08.104Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/03/b07a337cfba22d8cea1384c71e806361180c91bac3ce10320caf709153af/daytona-0.158.0.tar.gz", hash = "sha256:cddc54228727221a1f892f6a09df79eb5545f44f442544f0c97d48664c3c72b0", size = 127196 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/c2/ca914fc4574f96182d6367293734119bc4114535780666af22c7e56357db/daytona-0.158.0-py3-none-any.whl", hash = "sha256:c9dbb02143e9d1d43bffc13bc22907f352d7df9d704cf8e018cbab4832bf5d0f", size = 157792, upload-time = "2026-03-27T14:39:06.405Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c2/ca914fc4574f96182d6367293734119bc4114535780666af22c7e56357db/daytona-0.158.0-py3-none-any.whl", hash = "sha256:c9dbb02143e9d1d43bffc13bc22907f352d7df9d704cf8e018cbab4832bf5d0f", size = 157792 }, ] [[package]] @@ -1623,9 +1623,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/bf/a0b60190134a0a85fc113c32592447a6e51044dd7cc817345418e860493c/daytona_api_client-0.158.0.tar.gz", hash = "sha256:4751383cf62e5bd9746037b3190f62c7fa2690231f04ce69fd2892e6e70a05db", size = 145997, upload-time = "2026-03-27T14:38:13.024Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/bf/a0b60190134a0a85fc113c32592447a6e51044dd7cc817345418e860493c/daytona_api_client-0.158.0.tar.gz", hash = "sha256:4751383cf62e5bd9746037b3190f62c7fa2690231f04ce69fd2892e6e70a05db", size = 145997 } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/aa/5b10ed8349c370cf9c42d208302a8e5d7d813a3f3b0195510c412400c0f3/daytona_api_client-0.158.0-py3-none-any.whl", hash = "sha256:2ddec78fae84a8afe5a894dab7c35f31f8448f48282a5c93c03bf974f1119390", size = 400550, upload-time = "2026-03-27T14:38:11.275Z" }, + { url = "https://files.pythonhosted.org/packages/50/aa/5b10ed8349c370cf9c42d208302a8e5d7d813a3f3b0195510c412400c0f3/daytona_api_client-0.158.0-py3-none-any.whl", hash = "sha256:2ddec78fae84a8afe5a894dab7c35f31f8448f48282a5c93c03bf974f1119390", size = 400550 }, ] [[package]] @@ -1640,9 +1640,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/b2/053a262c2fcc1b216dc6518a1a15dff7e0b6686af2533fb41d1aa36a40cc/daytona_api_client_async-0.158.0.tar.gz", hash = "sha256:8d06230e211204b6e4b994a41ce94890007fbe11fa55c99386ba547e40624431", size = 146115, upload-time = "2026-03-27T14:38:15.64Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/b2/053a262c2fcc1b216dc6518a1a15dff7e0b6686af2533fb41d1aa36a40cc/daytona_api_client_async-0.158.0.tar.gz", hash = "sha256:8d06230e211204b6e4b994a41ce94890007fbe11fa55c99386ba547e40624431", size = 146115 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/87/ed12a2caefd8a87a1266b82e195b5c354b43ac81252c4e0098a9cd25e975/daytona_api_client_async-0.158.0-py3-none-any.whl", hash = "sha256:9247809870d02a355e8136e4b6ee041fec371e01ed03deff1b9f20027f69c3fe", size = 403611, upload-time = "2026-03-27T14:38:13.925Z" }, + { url = "https://files.pythonhosted.org/packages/0c/87/ed12a2caefd8a87a1266b82e195b5c354b43ac81252c4e0098a9cd25e975/daytona_api_client_async-0.158.0-py3-none-any.whl", hash = "sha256:9247809870d02a355e8136e4b6ee041fec371e01ed03deff1b9f20027f69c3fe", size = 403611 }, ] [[package]] @@ -1655,9 +1655,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/9a/1fa332a4ffa5bbbf95ef946a98ed590fe2c7ab58727065e381eebacfb7aa/daytona_toolbox_api_client-0.158.0.tar.gz", hash = "sha256:2abd2d7521772af3089fd2d36142c5f2d0ffb0f2aa12cf5134ae5274161342f0", size = 67495, upload-time = "2026-03-27T14:38:20.829Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/9a/1fa332a4ffa5bbbf95ef946a98ed590fe2c7ab58727065e381eebacfb7aa/daytona_toolbox_api_client-0.158.0.tar.gz", hash = "sha256:2abd2d7521772af3089fd2d36142c5f2d0ffb0f2aa12cf5134ae5274161342f0", size = 67495 } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/dc/ed944ce6511e092d46831485580180f8caecfbca32bfe39fc92f1a3936a1/daytona_toolbox_api_client-0.158.0-py3-none-any.whl", hash = "sha256:2852d161aae238aa08e5a0c91b6dd2861521b7a780467216ce97fdf5e4f85f22", size = 177288, upload-time = "2026-03-27T14:38:19.294Z" }, + { url = "https://files.pythonhosted.org/packages/36/dc/ed944ce6511e092d46831485580180f8caecfbca32bfe39fc92f1a3936a1/daytona_toolbox_api_client-0.158.0-py3-none-any.whl", hash = "sha256:2852d161aae238aa08e5a0c91b6dd2861521b7a780467216ce97fdf5e4f85f22", size = 177288 }, ] [[package]] @@ -1672,9 +1672,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/b2/e72bbbb0e70855befad02863e42b4fc2c4944ce75acbbad1e1ff8780b3ed/daytona_toolbox_api_client_async-0.158.0.tar.gz", hash = "sha256:d49c7f4372881a0c521ab6e63a57c97163795f717dbaeb072afc5e377ef50f33", size = 64560, upload-time = "2026-03-27T14:38:36.663Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/b2/e72bbbb0e70855befad02863e42b4fc2c4944ce75acbbad1e1ff8780b3ed/daytona_toolbox_api_client_async-0.158.0.tar.gz", hash = "sha256:d49c7f4372881a0c521ab6e63a57c97163795f717dbaeb072afc5e377ef50f33", size = 64560 } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/db/95e931deb1b131e522e116fc3462b3f61169cbf6acba011fffcda94ca38a/daytona_toolbox_api_client_async-0.158.0-py3-none-any.whl", hash = "sha256:c3c1ab2a26e819e76d2c66d6bad49360200d9aa9aa945eed6b74e8a2d210e43d", size = 178684, upload-time = "2026-03-27T14:38:35.138Z" }, + { url = "https://files.pythonhosted.org/packages/db/db/95e931deb1b131e522e116fc3462b3f61169cbf6acba011fffcda94ca38a/daytona_toolbox_api_client_async-0.158.0-py3-none-any.whl", hash = "sha256:c3c1ab2a26e819e76d2c66d6bad49360200d9aa9aa945eed6b74e8a2d210e43d", size = 178684 }, ] [[package]] @@ -1688,18 +1688,18 @@ dependencies = [ { name = "langchain-google-genai" }, { name = "wcmatch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/ef/0b2ccd5e4f40c1554145de17a7f3ee41994de1dda3ea36abe28600f1a3cf/deepagents-0.4.12.tar.gz", hash = "sha256:fc24a691e5cba00920ac4fa1d94f8147d6081fe513ed22bdba7da469288681c3", size = 91870, upload-time = "2026-03-20T14:54:29.904Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/ef/0b2ccd5e4f40c1554145de17a7f3ee41994de1dda3ea36abe28600f1a3cf/deepagents-0.4.12.tar.gz", hash = "sha256:fc24a691e5cba00920ac4fa1d94f8147d6081fe513ed22bdba7da469288681c3", size = 91870 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/77/63a5cb4e3a8871c4a52d600661a232c26b35f52931ee551c3adc38eeacf6/deepagents-0.4.12-py3-none-any.whl", hash = "sha256:76a272bac25607c5ef8c5adc876e391da945f1107b504686964dfdb6afdc1ebb", size = 104455, upload-time = "2026-03-20T14:54:28.786Z" }, + { url = "https://files.pythonhosted.org/packages/5e/77/63a5cb4e3a8871c4a52d600661a232c26b35f52931ee551c3adc38eeacf6/deepagents-0.4.12-py3-none-any.whl", hash = "sha256:76a272bac25607c5ef8c5adc876e391da945f1107b504686964dfdb6afdc1ebb", size = 104455 }, ] [[package]] name = "defusedxml" version = "0.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, ] [[package]] @@ -1709,9 +1709,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744, upload-time = "2025-01-27T10:46:25.7Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998, upload-time = "2025-01-27T10:46:09.186Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998 }, ] [[package]] @@ -1721,27 +1721,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788, upload-time = "2020-04-20T14:23:38.738Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178 }, ] [[package]] name = "dill" version = "0.3.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/17/4d/ac7ffa80c69ea1df30a8aa11b3578692a5118e7cd1aa157e3ef73b092d15/dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", size = 184847, upload-time = "2024-01-27T23:42:16.145Z" } +sdist = { url = "https://files.pythonhosted.org/packages/17/4d/ac7ffa80c69ea1df30a8aa11b3578692a5118e7cd1aa157e3ef73b092d15/dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca", size = 184847 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252, upload-time = "2024-01-27T23:42:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7a/cef76fd8438a42f96db64ddaa85280485a9c395e7df3db8158cfec1eee34/dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7", size = 116252 }, ] [[package]] name = "dirtyjson" version = "1.0.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/04/d24f6e645ad82ba0ef092fa17d9ef7a21953781663648a01c9371d9e8e98/dirtyjson-1.0.8.tar.gz", hash = "sha256:90ca4a18f3ff30ce849d100dcf4a003953c79d3a2348ef056f1d9c22231a25fd", size = 30782, upload-time = "2022-11-28T23:32:33.319Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/04/d24f6e645ad82ba0ef092fa17d9ef7a21953781663648a01c9371d9e8e98/dirtyjson-1.0.8.tar.gz", hash = "sha256:90ca4a18f3ff30ce849d100dcf4a003953c79d3a2348ef056f1d9c22231a25fd", size = 30782 } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/69/1bcf70f81de1b4a9f21b3a62ec0c83bdff991c88d6cc2267d02408457e88/dirtyjson-1.0.8-py3-none-any.whl", hash = "sha256:125e27248435a58acace26d5c2c4c11a1c0de0a9c5124c5a94ba78e517d74f53", size = 25197, upload-time = "2022-11-28T23:32:31.219Z" }, + { url = "https://files.pythonhosted.org/packages/68/69/1bcf70f81de1b4a9f21b3a62ec0c83bdff991c88d6cc2267d02408457e88/dirtyjson-1.0.8-py3-none-any.whl", hash = "sha256:125e27248435a58acace26d5c2c4c11a1c0de0a9c5124c5a94ba78e517d74f53", size = 25197 }, ] [[package]] @@ -1752,36 +1752,36 @@ dependencies = [ { name = "aiohttp" }, { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/57/9a2d9abdabdc9db8ef28ce0cf4129669e1c8717ba28d607b5ba357c4de3b/discord_py-2.7.1.tar.gz", hash = "sha256:24d5e6a45535152e4b98148a9dd6b550d25dc2c9fb41b6d670319411641249da", size = 1106326, upload-time = "2026-03-03T18:40:46.24Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/57/9a2d9abdabdc9db8ef28ce0cf4129669e1c8717ba28d607b5ba357c4de3b/discord_py-2.7.1.tar.gz", hash = "sha256:24d5e6a45535152e4b98148a9dd6b550d25dc2c9fb41b6d670319411641249da", size = 1106326 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/a7/17208c3b3f92319e7fad259f1c6d5a5baf8fd0654c54846ced329f83c3eb/discord_py-2.7.1-py3-none-any.whl", hash = "sha256:849dca2c63b171146f3a7f3f8acc04248098e9e6203412ce3cf2745f284f7439", size = 1227550, upload-time = "2026-03-03T18:40:44.492Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a7/17208c3b3f92319e7fad259f1c6d5a5baf8fd0654c54846ced329f83c3eb/discord_py-2.7.1-py3-none-any.whl", hash = "sha256:849dca2c63b171146f3a7f3f8acc04248098e9e6203412ce3cf2745f284f7439", size = 1227550 }, ] [[package]] name = "distro" version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, ] [[package]] name = "dlinfo" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/8e/8f2f94cd40af1b51e8e371a83b385d622170d42f98776441a6118f4dd682/dlinfo-2.0.0.tar.gz", hash = "sha256:88a2bc04f51d01bc604cdc9eb1c3cc0bde89057532ca6a3e71a41f6235433e17", size = 12727, upload-time = "2025-01-16T15:43:10.756Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/8e/8f2f94cd40af1b51e8e371a83b385d622170d42f98776441a6118f4dd682/dlinfo-2.0.0.tar.gz", hash = "sha256:88a2bc04f51d01bc604cdc9eb1c3cc0bde89057532ca6a3e71a41f6235433e17", size = 12727 } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/90/022c79d6e5e6f843268c10b84d4a021ee3afba0621d3c176d3ff2024bfc8/dlinfo-2.0.0-py3-none-any.whl", hash = "sha256:b32cc18e3ea67c0ca9ca409e5b41eed863bd1363dbc9dd3de90fedf11b61e7bc", size = 3654, upload-time = "2025-01-16T15:43:09.474Z" }, + { url = "https://files.pythonhosted.org/packages/da/90/022c79d6e5e6f843268c10b84d4a021ee3afba0621d3c176d3ff2024bfc8/dlinfo-2.0.0-py3-none-any.whl", hash = "sha256:b32cc18e3ea67c0ca9ca409e5b41eed863bd1363dbc9dd3de90fedf11b61e7bc", size = 3654 }, ] [[package]] name = "dnspython" version = "2.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094 }, ] [[package]] @@ -1821,9 +1821,9 @@ dependencies = [ { name = "tqdm" }, { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/de/d38c5b258517a142262080b481121c4fdd1b356525327c7c225bdda5fb42/docling-2.82.0.tar.gz", hash = "sha256:a5fbc7520ece4705a7416fd7d00583855a640bea823bef6d326bda8621791b35", size = 417279, upload-time = "2026-03-25T09:41:08.136Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/de/d38c5b258517a142262080b481121c4fdd1b356525327c7c225bdda5fb42/docling-2.82.0.tar.gz", hash = "sha256:a5fbc7520ece4705a7416fd7d00583855a640bea823bef6d326bda8621791b35", size = 417279 } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/5d/b1fc590ad0d1290a309c589f795a768db78a2b09d4bb4609829fb7fc71a1/docling-2.82.0-py3-none-any.whl", hash = "sha256:19c6fcff1832a7b21604a680360a4600f5ac83fcbcb391b095c018faac8a6805", size = 446283, upload-time = "2026-03-25T09:41:06.164Z" }, + { url = "https://files.pythonhosted.org/packages/26/5d/b1fc590ad0d1290a309c589f795a768db78a2b09d4bb4609829fb7fc71a1/docling-2.82.0-py3-none-any.whl", hash = "sha256:19c6fcff1832a7b21604a680360a4600f5ac83fcbcb391b095c018faac8a6805", size = 446283 }, ] [[package]] @@ -1843,9 +1843,9 @@ dependencies = [ { name = "typer" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/fb/2d88db67502418bb3e04e7ed2ac1893510d0c93c33defef08a6047d705e8/docling_core-2.70.2.tar.gz", hash = "sha256:f4cf53c86afc0f14bd526f9ee94125f68f77a284a079a99d1b83c9368ab5508c", size = 300733, upload-time = "2026-03-20T15:38:12.816Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/fb/2d88db67502418bb3e04e7ed2ac1893510d0c93c33defef08a6047d705e8/docling_core-2.70.2.tar.gz", hash = "sha256:f4cf53c86afc0f14bd526f9ee94125f68f77a284a079a99d1b83c9368ab5508c", size = 300733 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/d1/cc3ff3e9afd17447510270622429eab6e0edc94f0c842f463075f20c3cab/docling_core-2.70.2-py3-none-any.whl", hash = "sha256:80d456b3500654f5ded421bd08d10bf255d76167c2d7f5ca7b0db1c8eed53a0a", size = 266702, upload-time = "2026-03-20T15:38:11.35Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d1/cc3ff3e9afd17447510270622429eab6e0edc94f0c842f463075f20c3cab/docling_core-2.70.2-py3-none-any.whl", hash = "sha256:80d456b3500654f5ded421bd08d10bf255d76167c2d7f5ca7b0db1c8eed53a0a", size = 266702 }, ] [package.optional-dependencies] @@ -1878,9 +1878,9 @@ dependencies = [ { name = "tqdm" }, { name = "transformers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/87/01bf0c710af37328aa3517b34e64c2a2f3a6283a1cfc8859ae05881dd769/docling_ibm_models-3.13.0.tar.gz", hash = "sha256:f402effae8a63b0e5c3b5ce13120601baa2cd8098beef1d53ab5a056443758d3", size = 98538, upload-time = "2026-03-27T15:49:57.569Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/87/01bf0c710af37328aa3517b34e64c2a2f3a6283a1cfc8859ae05881dd769/docling_ibm_models-3.13.0.tar.gz", hash = "sha256:f402effae8a63b0e5c3b5ce13120601baa2cd8098beef1d53ab5a056443758d3", size = 98538 } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/52/11a8c8fff80e1fa581173edcc91cc92ed24184519e746fe39456f617653d/docling_ibm_models-3.13.0-py3-none-any.whl", hash = "sha256:a11acc6034b06e0bed8dc0ca1fa700615b8246eacce411619168e1f6562b0d0d", size = 93855, upload-time = "2026-03-27T15:49:56.353Z" }, + { url = "https://files.pythonhosted.org/packages/25/52/11a8c8fff80e1fa581173edcc91cc92ed24184519e746fe39456f617653d/docling_ibm_models-3.13.0-py3-none-any.whl", hash = "sha256:a11acc6034b06e0bed8dc0ca1fa700615b8246eacce411619168e1f6562b0d0d", size = 93855 }, ] [[package]] @@ -1894,53 +1894,53 @@ dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "tabulate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/34/f951e261d20cc71bc55703a3f4f51b13d8dc98ed634995905ecc41e5650a/docling_parse-5.6.1.tar.gz", hash = "sha256:e47d40a7c268a775c41a8cc7773555a5856a1bcfd5a05ee97ee0a162270ad7f9", size = 61127846, upload-time = "2026-03-24T11:15:01.304Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/34/f951e261d20cc71bc55703a3f4f51b13d8dc98ed634995905ecc41e5650a/docling_parse-5.6.1.tar.gz", hash = "sha256:e47d40a7c268a775c41a8cc7773555a5856a1bcfd5a05ee97ee0a162270ad7f9", size = 61127846 } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/70/b91d5630b736d56c2fb4b0480ae364fedf0ce0735ef52408c2a915e4657d/docling_parse-5.6.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:7b978cfb4f8c08c81202a611aefb45ff2cf5a0ff02c27f390851f749110087ec", size = 7807265, upload-time = "2026-03-24T11:14:34.827Z" }, - { url = "https://files.pythonhosted.org/packages/9d/0f/3b9ecfab5d881ffda30bfc950854175660cb852428ddecc49cac1bc56bc7/docling_parse-5.6.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe78cc80489f79ed4ef5ba7e56f6e280d64c1be86b9b188b7e669748e09098bc", size = 8202963, upload-time = "2026-03-24T11:14:36.508Z" }, - { url = "https://files.pythonhosted.org/packages/3c/6b/d434c3328ce45e8847883cf409c117677003c883d08a7de8a09fd71e959d/docling_parse-5.6.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5317adacca100a1a2ed85040e4b38552b451de56304c0c63c5be2ddf448d752c", size = 8302625, upload-time = "2026-03-24T11:14:38.306Z" }, - { url = "https://files.pythonhosted.org/packages/ad/04/df0e86122456345c81f81cec93aec49a0c1283e14c3787f95666b29a3a83/docling_parse-5.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:8f51cc4ebbe7aa34511dc5ed34186a9253e95c3ee91fa462ce5775f3d56764fe", size = 9208290, upload-time = "2026-03-24T11:14:40.381Z" }, - { url = "https://files.pythonhosted.org/packages/05/1b/e3238fb0f8eef299121d58a889564b97b0b5486301c0ffb776c0e4e7bd51/docling_parse-5.6.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6eb7c1caab1f78952b6daa9b09448a052408eb44f528d17f38a27fe3c048616a", size = 7807330, upload-time = "2026-03-24T11:14:42.621Z" }, - { url = "https://files.pythonhosted.org/packages/b7/1e/840a855a0f0e85c339a7e5b7dc7aa2eee191550dca64c59da45cad37b2cd/docling_parse-5.6.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d0f0a8fe0552430aee6581f4f021def72e0842652696c6b295aca8f5e2712c8", size = 8203763, upload-time = "2026-03-24T11:14:44.649Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ea/d010a8c679656e1274f7c08836eb765a3366076889890e80a1690daa686f/docling_parse-5.6.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:630da916fcd8cb7dae92143bd5f2a571827f75a4baaf8818d04521334379c32b", size = 8302890, upload-time = "2026-03-24T11:14:46.876Z" }, - { url = "https://files.pythonhosted.org/packages/d8/ec/231bd25fc104e9c6cd5fbb48f5a9df199c6f3ced950968f67887c85e131b/docling_parse-5.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:d5cba3c5102936b3ebbe983cd15fa250bf3b57c9ced65a3eb6b53f15d2d68378", size = 9208256, upload-time = "2026-03-24T11:14:48.692Z" }, - { url = "https://files.pythonhosted.org/packages/53/d3/95acf75c9cafae1fe3ca85fe303c852c98f235c18eb4ce631709644da34b/docling_parse-5.6.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:fd46e42d9f6cc30d3ed2f2c0046c529926852b772cebe9802e61c07019c1b5ce", size = 7807909, upload-time = "2026-03-24T11:14:50.87Z" }, - { url = "https://files.pythonhosted.org/packages/6a/d1/26aeadf8b666646445d401183ef16ffe6c71044817f2f7cb3110c0367b63/docling_parse-5.6.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6f0c43d30bc2188a9b700587cd9ab4e3e088aa4a8f033fd50281e205603d28a", size = 8204294, upload-time = "2026-03-24T11:14:53.049Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e2/1c93f3fe0574ff909b6a5edbd2f34f972c9f7102c30e97a72ce3d7c448e2/docling_parse-5.6.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b0cadcad4433718d569219e2e19547dfea4ba6b803f35658e9cd02db52480ec", size = 8303207, upload-time = "2026-03-24T11:14:54.785Z" }, - { url = "https://files.pythonhosted.org/packages/70/36/cc7a4a2697b8c9e3660811637f67e4880a97c16551376e630fdfee3eac3a/docling_parse-5.6.1-cp314-cp314-win_amd64.whl", hash = "sha256:d7ac7fbd6012d83e3a7845e618ec67854bccad09ab097219418c167a1fb9eef5", size = 9569000, upload-time = "2026-03-24T11:14:56.601Z" }, + { url = "https://files.pythonhosted.org/packages/21/70/b91d5630b736d56c2fb4b0480ae364fedf0ce0735ef52408c2a915e4657d/docling_parse-5.6.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:7b978cfb4f8c08c81202a611aefb45ff2cf5a0ff02c27f390851f749110087ec", size = 7807265 }, + { url = "https://files.pythonhosted.org/packages/9d/0f/3b9ecfab5d881ffda30bfc950854175660cb852428ddecc49cac1bc56bc7/docling_parse-5.6.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe78cc80489f79ed4ef5ba7e56f6e280d64c1be86b9b188b7e669748e09098bc", size = 8202963 }, + { url = "https://files.pythonhosted.org/packages/3c/6b/d434c3328ce45e8847883cf409c117677003c883d08a7de8a09fd71e959d/docling_parse-5.6.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5317adacca100a1a2ed85040e4b38552b451de56304c0c63c5be2ddf448d752c", size = 8302625 }, + { url = "https://files.pythonhosted.org/packages/ad/04/df0e86122456345c81f81cec93aec49a0c1283e14c3787f95666b29a3a83/docling_parse-5.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:8f51cc4ebbe7aa34511dc5ed34186a9253e95c3ee91fa462ce5775f3d56764fe", size = 9208290 }, + { url = "https://files.pythonhosted.org/packages/05/1b/e3238fb0f8eef299121d58a889564b97b0b5486301c0ffb776c0e4e7bd51/docling_parse-5.6.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6eb7c1caab1f78952b6daa9b09448a052408eb44f528d17f38a27fe3c048616a", size = 7807330 }, + { url = "https://files.pythonhosted.org/packages/b7/1e/840a855a0f0e85c339a7e5b7dc7aa2eee191550dca64c59da45cad37b2cd/docling_parse-5.6.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d0f0a8fe0552430aee6581f4f021def72e0842652696c6b295aca8f5e2712c8", size = 8203763 }, + { url = "https://files.pythonhosted.org/packages/fc/ea/d010a8c679656e1274f7c08836eb765a3366076889890e80a1690daa686f/docling_parse-5.6.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:630da916fcd8cb7dae92143bd5f2a571827f75a4baaf8818d04521334379c32b", size = 8302890 }, + { url = "https://files.pythonhosted.org/packages/d8/ec/231bd25fc104e9c6cd5fbb48f5a9df199c6f3ced950968f67887c85e131b/docling_parse-5.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:d5cba3c5102936b3ebbe983cd15fa250bf3b57c9ced65a3eb6b53f15d2d68378", size = 9208256 }, + { url = "https://files.pythonhosted.org/packages/53/d3/95acf75c9cafae1fe3ca85fe303c852c98f235c18eb4ce631709644da34b/docling_parse-5.6.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:fd46e42d9f6cc30d3ed2f2c0046c529926852b772cebe9802e61c07019c1b5ce", size = 7807909 }, + { url = "https://files.pythonhosted.org/packages/6a/d1/26aeadf8b666646445d401183ef16ffe6c71044817f2f7cb3110c0367b63/docling_parse-5.6.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6f0c43d30bc2188a9b700587cd9ab4e3e088aa4a8f033fd50281e205603d28a", size = 8204294 }, + { url = "https://files.pythonhosted.org/packages/e2/e2/1c93f3fe0574ff909b6a5edbd2f34f972c9f7102c30e97a72ce3d7c448e2/docling_parse-5.6.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b0cadcad4433718d569219e2e19547dfea4ba6b803f35658e9cd02db52480ec", size = 8303207 }, + { url = "https://files.pythonhosted.org/packages/70/36/cc7a4a2697b8c9e3660811637f67e4880a97c16551376e630fdfee3eac3a/docling_parse-5.6.1-cp314-cp314-win_amd64.whl", hash = "sha256:d7ac7fbd6012d83e3a7845e618ec67854bccad09ab097219418c167a1fb9eef5", size = 9569000 }, ] [[package]] name = "docopt" version = "0.6.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901, upload-time = "2014-06-16T11:18:57.406Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901 } [[package]] name = "docstring-parser" version = "0.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442 } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, + { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896 }, ] [[package]] name = "docutils" version = "0.22.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196 }, ] [[package]] name = "durationpy" version = "0.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/a4/e44218c2b394e31a6dd0d6b095c4e1f32d0be54c2a4b250032d717647bab/durationpy-0.10.tar.gz", hash = "sha256:1fa6893409a6e739c9c72334fc65cca1f355dbdd93405d30f726deb5bde42fba", size = 3335, upload-time = "2025-05-17T13:52:37.26Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/a4/e44218c2b394e31a6dd0d6b095c4e1f32d0be54c2a4b250032d717647bab/durationpy-0.10.tar.gz", hash = "sha256:1fa6893409a6e739c9c72334fc65cca1f355dbdd93405d30f726deb5bde42fba", size = 3335 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922, upload-time = "2025-05-17T13:52:36.463Z" }, + { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922 }, ] [[package]] @@ -1952,9 +1952,9 @@ dependencies = [ { name = "sniffio" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/0a/a92140b666afdcb9862a16e4d80873b3c887c1b7e3f17e945fc3460edf1b/elastic_transport-9.2.1.tar.gz", hash = "sha256:97d9abd638ba8aa90faa4ca1bf1a18bde0fe2088fbc8757f2eb7b299f205773d", size = 77403, upload-time = "2025-12-23T11:54:12.849Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/0a/a92140b666afdcb9862a16e4d80873b3c887c1b7e3f17e945fc3460edf1b/elastic_transport-9.2.1.tar.gz", hash = "sha256:97d9abd638ba8aa90faa4ca1bf1a18bde0fe2088fbc8757f2eb7b299f205773d", size = 77403 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e6/a42b600ae8b808371f740381f6c32050cad93f870d36cc697b8b7006bf7c/elastic_transport-9.2.1-py3-none-any.whl", hash = "sha256:39e1a25e486af34ce7aa1bc9005d1c736f1b6fb04c9b64ea0604ded5a61fc1d4", size = 65327, upload-time = "2025-12-23T11:54:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e6/a42b600ae8b808371f740381f6c32050cad93f870d36cc697b8b7006bf7c/elastic_transport-9.2.1-py3-none-any.whl", hash = "sha256:39e1a25e486af34ce7aa1bc9005d1c736f1b6fb04c9b64ea0604ded5a61fc1d4", size = 65327 }, ] [[package]] @@ -1968,9 +1968,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/15/283459c9299d412ffa2aaab69b082857631c519233f5491d6c567e3320ca/elasticsearch-9.3.0.tar.gz", hash = "sha256:f76e149c0a22d5ccbba58bdc30c9f51cf894231b359ef4fd7e839b558b59f856", size = 893538, upload-time = "2026-02-03T20:26:38.914Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/15/283459c9299d412ffa2aaab69b082857631c519233f5491d6c567e3320ca/elasticsearch-9.3.0.tar.gz", hash = "sha256:f76e149c0a22d5ccbba58bdc30c9f51cf894231b359ef4fd7e839b558b59f856", size = 893538 } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/37/3a196f8918743f2104cb66b1f56218079ecac6e128c061de7df7f4faef02/elasticsearch-9.3.0-py3-none-any.whl", hash = "sha256:67bd2bb4f0800f58c2847d29cd57d6e7bf5bc273483b4f17421f93e75ba09f39", size = 979405, upload-time = "2026-02-03T20:26:34.552Z" }, + { url = "https://files.pythonhosted.org/packages/05/37/3a196f8918743f2104cb66b1f56218079ecac6e128c061de7df7f4faef02/elasticsearch-9.3.0-py3-none-any.whl", hash = "sha256:67bd2bb4f0800f58c2847d29cd57d6e7bf5bc273483b4f17421f93e75ba09f39", size = 979405 }, ] [[package]] @@ -1981,18 +1981,18 @@ dependencies = [ { name = "dnspython" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238 } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604 }, ] [[package]] name = "emoji" version = "2.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/78/0d2db9382c92a163d7095fc08efff7800880f830a152cfced40161e7638d/emoji-2.15.0.tar.gz", hash = "sha256:eae4ab7d86456a70a00a985125a03263a5eac54cd55e51d7e184b1ed3b6757e4", size = 615483, upload-time = "2025-09-21T12:13:02.755Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/78/0d2db9382c92a163d7095fc08efff7800880f830a152cfced40161e7638d/emoji-2.15.0.tar.gz", hash = "sha256:eae4ab7d86456a70a00a985125a03263a5eac54cd55e51d7e184b1ed3b6757e4", size = 615483 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/5e/4b5aaaabddfacfe36ba7768817bd1f71a7a810a43705e531f3ae4c690767/emoji-2.15.0-py3-none-any.whl", hash = "sha256:205296793d66a89d88af4688fa57fd6496732eb48917a87175a023c8138995eb", size = 608433, upload-time = "2025-09-21T12:13:01.197Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/4b5aaaabddfacfe36ba7768817bd1f71a7a810a43705e531f3ae4c690767/emoji-2.15.0-py3-none-any.whl", hash = "sha256:205296793d66a89d88af4688fa57fd6496732eb48917a87175a023c8138995eb", size = 608433 }, ] [[package]] @@ -2008,30 +2008,30 @@ name = "espeakng-loader" version = "0.2.4" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/92/f44ed7f531143c3c6c97d56e2b0f9be8728dc05e18b96d46eb539230ed46/espeakng_loader-0.2.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b77477ae2ddf62a748e04e49714eabb2f3a24f344166200b00539083bd669904", size = 9938387, upload-time = "2025-01-17T01:22:42.064Z" }, - { url = "https://files.pythonhosted.org/packages/a8/26/258c0cd43b9bc1043301c5f61767d6a6c3b679df82790c9cb43a3277b865/espeakng_loader-0.2.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d27cdca31112226e7299d8562e889d3e38a1e48055c9ee381b45d669072ee59f", size = 9892565, upload-time = "2025-01-17T01:22:40.365Z" }, - { url = "https://files.pythonhosted.org/packages/de/1e/25ec5ab07528c0fbb215a61800a38eca05c8a99445515a02d7fa5debcb32/espeakng_loader-0.2.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08721baf27d13d461f6be6eed9a65277e70d68234ff484fd8b9897b222cdcb6d", size = 10078484, upload-time = "2025-01-17T01:22:43.373Z" }, - { url = "https://files.pythonhosted.org/packages/d9/ad/1b768d8daffc2996e07bbcb6f534d8de3202cd75fce1f1c45eced1ce6465/espeakng_loader-0.2.4-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:d1e798141b46a050cdb75fcf3c17db969bb2c40394f3f4a48910655d547508b9", size = 10037736, upload-time = "2025-01-17T01:22:42.576Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ed/a3d872fbad4f3a3f3db0e8c31768ab14e77cd77306de16b8b20b1e1df7ea/espeakng_loader-0.2.4-py3-none-win_amd64.whl", hash = "sha256:41f1e08ac9deda2efd1ea9de0b81dab9f5ae3c4b24284f76533d0a7b1dd7abd7", size = 9437292, upload-time = "2025-01-17T01:23:27.463Z" }, - { url = "https://files.pythonhosted.org/packages/29/64/0b75bc50ec53b4e000bac913625511215aa96124adf5dba8c4baa17c02cd/espeakng_loader-0.2.4-py3-none-win_arm64.whl", hash = "sha256:d7a2928843eaeb2df82f99a370f44e8a630f59b02f9b0d1f168a03c4eeb76b89", size = 9426841, upload-time = "2025-01-17T01:23:21.766Z" }, + { url = "https://files.pythonhosted.org/packages/f8/92/f44ed7f531143c3c6c97d56e2b0f9be8728dc05e18b96d46eb539230ed46/espeakng_loader-0.2.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b77477ae2ddf62a748e04e49714eabb2f3a24f344166200b00539083bd669904", size = 9938387 }, + { url = "https://files.pythonhosted.org/packages/a8/26/258c0cd43b9bc1043301c5f61767d6a6c3b679df82790c9cb43a3277b865/espeakng_loader-0.2.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d27cdca31112226e7299d8562e889d3e38a1e48055c9ee381b45d669072ee59f", size = 9892565 }, + { url = "https://files.pythonhosted.org/packages/de/1e/25ec5ab07528c0fbb215a61800a38eca05c8a99445515a02d7fa5debcb32/espeakng_loader-0.2.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08721baf27d13d461f6be6eed9a65277e70d68234ff484fd8b9897b222cdcb6d", size = 10078484 }, + { url = "https://files.pythonhosted.org/packages/d9/ad/1b768d8daffc2996e07bbcb6f534d8de3202cd75fce1f1c45eced1ce6465/espeakng_loader-0.2.4-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:d1e798141b46a050cdb75fcf3c17db969bb2c40394f3f4a48910655d547508b9", size = 10037736 }, + { url = "https://files.pythonhosted.org/packages/9d/ed/a3d872fbad4f3a3f3db0e8c31768ab14e77cd77306de16b8b20b1e1df7ea/espeakng_loader-0.2.4-py3-none-win_amd64.whl", hash = "sha256:41f1e08ac9deda2efd1ea9de0b81dab9f5ae3c4b24284f76533d0a7b1dd7abd7", size = 9437292 }, + { url = "https://files.pythonhosted.org/packages/29/64/0b75bc50ec53b4e000bac913625511215aa96124adf5dba8c4baa17c02cd/espeakng_loader-0.2.4-py3-none-win_arm64.whl", hash = "sha256:d7a2928843eaeb2df82f99a370f44e8a630f59b02f9b0d1f168a03c4eeb76b89", size = 9426841 }, ] [[package]] name = "et-xmlfile" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059 }, ] [[package]] name = "fake-useragent" version = "2.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/43/948d10bf42735709edb5ae51e23297d034086f17fc7279fef385a7acb473/fake_useragent-2.2.0.tar.gz", hash = "sha256:4e6ab6571e40cc086d788523cf9e018f618d07f9050f822ff409a4dfe17c16b2", size = 158898, upload-time = "2025-04-14T15:32:19.238Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/43/948d10bf42735709edb5ae51e23297d034086f17fc7279fef385a7acb473/fake_useragent-2.2.0.tar.gz", hash = "sha256:4e6ab6571e40cc086d788523cf9e018f618d07f9050f822ff409a4dfe17c16b2", size = 158898 } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/37/b3ea9cd5558ff4cb51957caca2193981c6b0ff30bd0d2630ac62505d99d0/fake_useragent-2.2.0-py3-none-any.whl", hash = "sha256:67f35ca4d847b0d298187443aaf020413746e56acd985a611908c73dba2daa24", size = 161695, upload-time = "2025-04-14T15:32:17.732Z" }, + { url = "https://files.pythonhosted.org/packages/51/37/b3ea9cd5558ff4cb51957caca2193981c6b0ff30bd0d2630ac62505d99d0/fake_useragent-2.2.0-py3-none-any.whl", hash = "sha256:67f35ca4d847b0d298187443aaf020413746e56acd985a611908c73dba2daa24", size = 161695 }, ] [[package]] @@ -2041,9 +2041,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fa/e5/b16bf568a2f20fe7423282db4a4059dbcadef70e9029c1c106836f8edd84/faker-40.11.1.tar.gz", hash = "sha256:61965046e79e8cfde4337d243eac04c0d31481a7c010033141103b43f603100c", size = 1957415, upload-time = "2026-03-23T14:05:50.233Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/e5/b16bf568a2f20fe7423282db4a4059dbcadef70e9029c1c106836f8edd84/faker-40.11.1.tar.gz", hash = "sha256:61965046e79e8cfde4337d243eac04c0d31481a7c010033141103b43f603100c", size = 1957415 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/ec/3c4b78eb0d2f6a81fb8cc9286745845bff661e6815741eff7a6ac5fcc9ea/faker-40.11.1-py3-none-any.whl", hash = "sha256:3af3a213ba8fb33ce6ba2af7aef2ac91363dae35d0cec0b2b0337d189e5bee2a", size = 1989484, upload-time = "2026-03-23T14:05:48.793Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ec/3c4b78eb0d2f6a81fb8cc9286745845bff661e6815741eff7a6ac5fcc9ea/faker-40.11.1-py3-none-any.whl", hash = "sha256:3af3a213ba8fb33ce6ba2af7aef2ac91363dae35d0cec0b2b0337d189e5bee2a", size = 1989484 }, ] [[package]] @@ -2057,9 +2057,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/73/5903c4b13beae98618d64eb9870c3fac4f605523dd0312ca5c80dadbd5b9/fastapi-0.135.2.tar.gz", hash = "sha256:88a832095359755527b7f63bb4c6bc9edb8329a026189eed83d6c1afcf419d56", size = 395833, upload-time = "2026-03-23T14:12:41.697Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/73/5903c4b13beae98618d64eb9870c3fac4f605523dd0312ca5c80dadbd5b9/fastapi-0.135.2.tar.gz", hash = "sha256:88a832095359755527b7f63bb4c6bc9edb8329a026189eed83d6c1afcf419d56", size = 395833 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/ea/18f6d0457f9efb2fc6fa594857f92810cadb03024975726db6546b3d6fcf/fastapi-0.135.2-py3-none-any.whl", hash = "sha256:0af0447d541867e8db2a6a25c23a8c4bd80e2394ac5529bd87501bbb9e240ca5", size = 117407, upload-time = "2026-03-23T14:12:43.284Z" }, + { url = "https://files.pythonhosted.org/packages/8f/ea/18f6d0457f9efb2fc6fa594857f92810cadb03024975726db6546b3d6fcf/fastapi-0.135.2-py3-none-any.whl", hash = "sha256:0af0447d541867e8db2a6a25c23a8c4bd80e2394ac5529bd87501bbb9e240ca5", size = 117407 }, ] [[package]] @@ -2074,9 +2074,9 @@ dependencies = [ { name = "pyjwt", extra = ["crypto"] }, { name = "python-multipart" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/79/98b9d733274cfe0c776fde637dc53b421a0142f51a9e3e9fecd72741f7c1/fastapi_users-15.0.5.tar.gz", hash = "sha256:097f69701894e650c346df89b1cdb0a09cf139234f4cb9a8ece275af4e98e202", size = 121394, upload-time = "2026-03-27T09:01:17.161Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/79/98b9d733274cfe0c776fde637dc53b421a0142f51a9e3e9fecd72741f7c1/fastapi_users-15.0.5.tar.gz", hash = "sha256:097f69701894e650c346df89b1cdb0a09cf139234f4cb9a8ece275af4e98e202", size = 121394 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/8e/1ed6bbe36c486b98217c4be6769d61fcb98c38b334fb39706de661a905b6/fastapi_users-15.0.5-py3-none-any.whl", hash = "sha256:10fd4f3e85ed66f694a6ca2ecac609af4e59d1f9ec64d1557f5912dccfd87c7f", size = 39038, upload-time = "2026-03-27T09:01:16.158Z" }, + { url = "https://files.pythonhosted.org/packages/5a/8e/1ed6bbe36c486b98217c4be6769d61fcb98c38b334fb39706de661a905b6/fastapi_users-15.0.5-py3-none-any.whl", hash = "sha256:10fd4f3e85ed66f694a6ca2ecac609af4e59d1f9ec64d1557f5912dccfd87c7f", size = 39038 }, ] [package.optional-dependencies] @@ -2095,44 +2095,44 @@ dependencies = [ { name = "fastapi-users" }, { name = "sqlalchemy", extra = ["asyncio"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/12/bc9e6146ae31564741cefc87ee6e37fa5b566933f0afe8aa030779d60e60/fastapi_users_db_sqlalchemy-7.0.0.tar.gz", hash = "sha256:6823eeedf8a92f819276a2b2210ef1dcfd71fe8b6e37f7b4da8d1c60e3dfd595", size = 10877, upload-time = "2025-01-04T13:09:05.086Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/12/bc9e6146ae31564741cefc87ee6e37fa5b566933f0afe8aa030779d60e60/fastapi_users_db_sqlalchemy-7.0.0.tar.gz", hash = "sha256:6823eeedf8a92f819276a2b2210ef1dcfd71fe8b6e37f7b4da8d1c60e3dfd595", size = 10877 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/08/9968963c1fb8c34627b7f1fbcdfe9438540f87dc7c9bfb59bb4fd19a4ecf/fastapi_users_db_sqlalchemy-7.0.0-py3-none-any.whl", hash = "sha256:5fceac018e7cfa69efc70834dd3035b3de7988eb4274154a0dbe8b14f5aa001e", size = 6891, upload-time = "2025-01-04T13:09:02.869Z" }, + { url = "https://files.pythonhosted.org/packages/a6/08/9968963c1fb8c34627b7f1fbcdfe9438540f87dc7c9bfb59bb4fd19a4ecf/fastapi_users_db_sqlalchemy-7.0.0-py3-none-any.whl", hash = "sha256:5fceac018e7cfa69efc70834dd3035b3de7988eb4274154a0dbe8b14f5aa001e", size = 6891 }, ] [[package]] name = "fastavro" version = "1.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/8b/fa2d3287fd2267be6261d0177c6809a7fa12c5600ddb33490c8dc29e77b2/fastavro-1.12.1.tar.gz", hash = "sha256:2f285be49e45bc047ab2f6bed040bb349da85db3f3c87880e4b92595ea093b2b", size = 1025661, upload-time = "2025-10-10T15:40:55.41Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/8b/fa2d3287fd2267be6261d0177c6809a7fa12c5600ddb33490c8dc29e77b2/fastavro-1.12.1.tar.gz", hash = "sha256:2f285be49e45bc047ab2f6bed040bb349da85db3f3c87880e4b92595ea093b2b", size = 1025661 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/f0/10bd1a3d08667fa0739e2b451fe90e06df575ec8b8ba5d3135c70555c9bd/fastavro-1.12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:509818cb24b98a804fc80be9c5fed90f660310ae3d59382fc811bfa187122167", size = 1009057, upload-time = "2025-10-10T15:41:24.556Z" }, - { url = "https://files.pythonhosted.org/packages/78/ad/0d985bc99e1fa9e74c636658000ba38a5cd7f5ab2708e9c62eaf736ecf1a/fastavro-1.12.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089e155c0c76e0d418d7e79144ce000524dd345eab3bc1e9c5ae69d500f71b14", size = 3391866, upload-time = "2025-10-10T15:41:26.882Z" }, - { url = "https://files.pythonhosted.org/packages/0d/9e/b4951dc84ebc34aac69afcbfbb22ea4a91080422ec2bfd2c06076ff1d419/fastavro-1.12.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44cbff7518901c91a82aab476fcab13d102e4999499df219d481b9e15f61af34", size = 3458005, upload-time = "2025-10-10T15:41:29.017Z" }, - { url = "https://files.pythonhosted.org/packages/af/f8/5a8df450a9f55ca8441f22ea0351d8c77809fc121498b6970daaaf667a21/fastavro-1.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a275e48df0b1701bb764b18a8a21900b24cf882263cb03d35ecdba636bbc830b", size = 3295258, upload-time = "2025-10-10T15:41:31.564Z" }, - { url = "https://files.pythonhosted.org/packages/99/b2/40f25299111d737e58b85696e91138a66c25b7334f5357e7ac2b0e8966f8/fastavro-1.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2de72d786eb38be6b16d556b27232b1bf1b2797ea09599507938cdb7a9fe3e7c", size = 3430328, upload-time = "2025-10-10T15:41:33.689Z" }, - { url = "https://files.pythonhosted.org/packages/e0/07/85157a7c57c5f8b95507d7829b5946561e5ee656ff80e9dd9a757f53ddaf/fastavro-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:9090f0dee63fe022ee9cc5147483366cc4171c821644c22da020d6b48f576b4f", size = 444140, upload-time = "2025-10-10T15:41:34.902Z" }, - { url = "https://files.pythonhosted.org/packages/bb/57/26d5efef9182392d5ac9f253953c856ccb66e4c549fd3176a1e94efb05c9/fastavro-1.12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:78df838351e4dff9edd10a1c41d1324131ffecbadefb9c297d612ef5363c049a", size = 1000599, upload-time = "2025-10-10T15:41:36.554Z" }, - { url = "https://files.pythonhosted.org/packages/33/cb/8ab55b21d018178eb126007a56bde14fd01c0afc11d20b5f2624fe01e698/fastavro-1.12.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:780476c23175d2ae457c52f45b9ffa9d504593499a36cd3c1929662bf5b7b14b", size = 3335933, upload-time = "2025-10-10T15:41:39.07Z" }, - { url = "https://files.pythonhosted.org/packages/fe/03/9c94ec9bf873eb1ffb0aa694f4e71940154e6e9728ddfdc46046d7e8ced4/fastavro-1.12.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0714b285160fcd515eb0455540f40dd6dac93bdeacdb03f24e8eac3d8aa51f8d", size = 3402066, upload-time = "2025-10-10T15:41:41.608Z" }, - { url = "https://files.pythonhosted.org/packages/75/c8/cb472347c5a584ccb8777a649ebb28278fccea39d005fc7df19996f41df8/fastavro-1.12.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a8bc2dcec5843d499f2489bfe0747999108f78c5b29295d877379f1972a3d41a", size = 3240038, upload-time = "2025-10-10T15:41:43.743Z" }, - { url = "https://files.pythonhosted.org/packages/e1/77/569ce9474c40304b3a09e109494e020462b83e405545b78069ddba5f614e/fastavro-1.12.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b1921ac35f3d89090a5816b626cf46e67dbecf3f054131f84d56b4e70496f45", size = 3369398, upload-time = "2025-10-10T15:41:45.719Z" }, - { url = "https://files.pythonhosted.org/packages/4a/1f/9589e35e9ea68035385db7bdbf500d36b8891db474063fb1ccc8215ee37c/fastavro-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:5aa777b8ee595b50aa084104cd70670bf25a7bbb9fd8bb5d07524b0785ee1699", size = 444220, upload-time = "2025-10-10T15:41:47.39Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d2/78435fe737df94bd8db2234b2100f5453737cffd29adee2504a2b013de84/fastavro-1.12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c3d67c47f177e486640404a56f2f50b165fe892cc343ac3a34673b80cc7f1dd6", size = 1086611, upload-time = "2025-10-10T15:41:48.818Z" }, - { url = "https://files.pythonhosted.org/packages/b6/be/428f99b10157230ddac77ec8cc167005b29e2bd5cbe228345192bb645f30/fastavro-1.12.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5217f773492bac43dae15ff2931432bce2d7a80be7039685a78d3fab7df910bd", size = 3541001, upload-time = "2025-10-10T15:41:50.871Z" }, - { url = "https://files.pythonhosted.org/packages/16/08/a2eea4f20b85897740efe44887e1ac08f30dfa4bfc3de8962bdcbb21a5a1/fastavro-1.12.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:469fecb25cba07f2e1bfa4c8d008477cd6b5b34a59d48715e1b1a73f6160097d", size = 3432217, upload-time = "2025-10-10T15:41:53.149Z" }, - { url = "https://files.pythonhosted.org/packages/87/bb/b4c620b9eb6e9838c7f7e4b7be0762834443adf9daeb252a214e9ad3178c/fastavro-1.12.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d71c8aa841ef65cfab709a22bb887955f42934bced3ddb571e98fdbdade4c609", size = 3366742, upload-time = "2025-10-10T15:41:55.237Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d1/e69534ccdd5368350646fea7d93be39e5f77c614cca825c990bd9ca58f67/fastavro-1.12.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b81fc04e85dfccf7c028e0580c606e33aa8472370b767ef058aae2c674a90746", size = 3383743, upload-time = "2025-10-10T15:41:57.68Z" }, - { url = "https://files.pythonhosted.org/packages/58/54/b7b4a0c3fb5fcba38128542da1b26c4e6d69933c923f493548bdfd63ab6a/fastavro-1.12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9445da127751ba65975d8e4bdabf36bfcfdad70fc35b2d988e3950cce0ec0e7c", size = 1001377, upload-time = "2025-10-10T15:41:59.241Z" }, - { url = "https://files.pythonhosted.org/packages/1e/4f/0e589089c7df0d8f57d7e5293fdc34efec9a3b758a0d4d0c99a7937e2492/fastavro-1.12.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed924233272719b5d5a6a0b4d80ef3345fc7e84fc7a382b6232192a9112d38a6", size = 3320401, upload-time = "2025-10-10T15:42:01.682Z" }, - { url = "https://files.pythonhosted.org/packages/f9/19/260110d56194ae29d7e423a336fccea8bcd103196d00f0b364b732bdb84e/fastavro-1.12.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3616e2f0e1c9265e92954fa099db79c6e7817356d3ff34f4bcc92699ae99697c", size = 3350894, upload-time = "2025-10-10T15:42:04.073Z" }, - { url = "https://files.pythonhosted.org/packages/d0/96/58b0411e8be9694d5972bee3167d6c1fd1fdfdf7ce253c1a19a327208f4f/fastavro-1.12.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cb0337b42fd3c047fcf0e9b7597bd6ad25868de719f29da81eabb6343f08d399", size = 3229644, upload-time = "2025-10-10T15:42:06.221Z" }, - { url = "https://files.pythonhosted.org/packages/5b/db/38660660eac82c30471d9101f45b3acfdcbadfe42d8f7cdb129459a45050/fastavro-1.12.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:64961ab15b74b7c168717bbece5660e0f3d457837c3cc9d9145181d011199fa7", size = 3329704, upload-time = "2025-10-10T15:42:08.384Z" }, - { url = "https://files.pythonhosted.org/packages/9d/a9/1672910f458ecb30b596c9e59e41b7c00309b602a0494341451e92e62747/fastavro-1.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:792356d320f6e757e89f7ac9c22f481e546c886454a6709247f43c0dd7058004", size = 452911, upload-time = "2025-10-10T15:42:09.795Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/2e15d0938ded1891b33eff252e8500605508b799c2e57188a933f0bd744c/fastavro-1.12.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120aaf82ac19d60a1016afe410935fe94728752d9c2d684e267e5b7f0e70f6d9", size = 3541999, upload-time = "2025-10-10T15:42:11.794Z" }, - { url = "https://files.pythonhosted.org/packages/a7/1c/6dfd082a205be4510543221b734b1191299e6a1810c452b6bc76dfa6968e/fastavro-1.12.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6a3462934b20a74f9ece1daa49c2e4e749bd9a35fa2657b53bf62898fba80f5", size = 3433972, upload-time = "2025-10-10T15:42:14.485Z" }, - { url = "https://files.pythonhosted.org/packages/24/90/9de694625a1a4b727b1ad0958d220cab25a9b6cf7f16a5c7faa9ea7b2261/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1f81011d54dd47b12437b51dd93a70a9aa17b61307abf26542fc3c13efbc6c51", size = 3368752, upload-time = "2025-10-10T15:42:16.618Z" }, - { url = "https://files.pythonhosted.org/packages/fa/93/b44f67589e4d439913dab6720f7e3507b0fa8b8e56d06f6fc875ced26afb/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43ded16b3f4a9f1a42f5970c2aa618acb23ea59c4fcaa06680bdf470b255e5a8", size = 3386636, upload-time = "2025-10-10T15:42:18.974Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f0/10bd1a3d08667fa0739e2b451fe90e06df575ec8b8ba5d3135c70555c9bd/fastavro-1.12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:509818cb24b98a804fc80be9c5fed90f660310ae3d59382fc811bfa187122167", size = 1009057 }, + { url = "https://files.pythonhosted.org/packages/78/ad/0d985bc99e1fa9e74c636658000ba38a5cd7f5ab2708e9c62eaf736ecf1a/fastavro-1.12.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089e155c0c76e0d418d7e79144ce000524dd345eab3bc1e9c5ae69d500f71b14", size = 3391866 }, + { url = "https://files.pythonhosted.org/packages/0d/9e/b4951dc84ebc34aac69afcbfbb22ea4a91080422ec2bfd2c06076ff1d419/fastavro-1.12.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44cbff7518901c91a82aab476fcab13d102e4999499df219d481b9e15f61af34", size = 3458005 }, + { url = "https://files.pythonhosted.org/packages/af/f8/5a8df450a9f55ca8441f22ea0351d8c77809fc121498b6970daaaf667a21/fastavro-1.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a275e48df0b1701bb764b18a8a21900b24cf882263cb03d35ecdba636bbc830b", size = 3295258 }, + { url = "https://files.pythonhosted.org/packages/99/b2/40f25299111d737e58b85696e91138a66c25b7334f5357e7ac2b0e8966f8/fastavro-1.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2de72d786eb38be6b16d556b27232b1bf1b2797ea09599507938cdb7a9fe3e7c", size = 3430328 }, + { url = "https://files.pythonhosted.org/packages/e0/07/85157a7c57c5f8b95507d7829b5946561e5ee656ff80e9dd9a757f53ddaf/fastavro-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:9090f0dee63fe022ee9cc5147483366cc4171c821644c22da020d6b48f576b4f", size = 444140 }, + { url = "https://files.pythonhosted.org/packages/bb/57/26d5efef9182392d5ac9f253953c856ccb66e4c549fd3176a1e94efb05c9/fastavro-1.12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:78df838351e4dff9edd10a1c41d1324131ffecbadefb9c297d612ef5363c049a", size = 1000599 }, + { url = "https://files.pythonhosted.org/packages/33/cb/8ab55b21d018178eb126007a56bde14fd01c0afc11d20b5f2624fe01e698/fastavro-1.12.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:780476c23175d2ae457c52f45b9ffa9d504593499a36cd3c1929662bf5b7b14b", size = 3335933 }, + { url = "https://files.pythonhosted.org/packages/fe/03/9c94ec9bf873eb1ffb0aa694f4e71940154e6e9728ddfdc46046d7e8ced4/fastavro-1.12.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0714b285160fcd515eb0455540f40dd6dac93bdeacdb03f24e8eac3d8aa51f8d", size = 3402066 }, + { url = "https://files.pythonhosted.org/packages/75/c8/cb472347c5a584ccb8777a649ebb28278fccea39d005fc7df19996f41df8/fastavro-1.12.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a8bc2dcec5843d499f2489bfe0747999108f78c5b29295d877379f1972a3d41a", size = 3240038 }, + { url = "https://files.pythonhosted.org/packages/e1/77/569ce9474c40304b3a09e109494e020462b83e405545b78069ddba5f614e/fastavro-1.12.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b1921ac35f3d89090a5816b626cf46e67dbecf3f054131f84d56b4e70496f45", size = 3369398 }, + { url = "https://files.pythonhosted.org/packages/4a/1f/9589e35e9ea68035385db7bdbf500d36b8891db474063fb1ccc8215ee37c/fastavro-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:5aa777b8ee595b50aa084104cd70670bf25a7bbb9fd8bb5d07524b0785ee1699", size = 444220 }, + { url = "https://files.pythonhosted.org/packages/6c/d2/78435fe737df94bd8db2234b2100f5453737cffd29adee2504a2b013de84/fastavro-1.12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c3d67c47f177e486640404a56f2f50b165fe892cc343ac3a34673b80cc7f1dd6", size = 1086611 }, + { url = "https://files.pythonhosted.org/packages/b6/be/428f99b10157230ddac77ec8cc167005b29e2bd5cbe228345192bb645f30/fastavro-1.12.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5217f773492bac43dae15ff2931432bce2d7a80be7039685a78d3fab7df910bd", size = 3541001 }, + { url = "https://files.pythonhosted.org/packages/16/08/a2eea4f20b85897740efe44887e1ac08f30dfa4bfc3de8962bdcbb21a5a1/fastavro-1.12.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:469fecb25cba07f2e1bfa4c8d008477cd6b5b34a59d48715e1b1a73f6160097d", size = 3432217 }, + { url = "https://files.pythonhosted.org/packages/87/bb/b4c620b9eb6e9838c7f7e4b7be0762834443adf9daeb252a214e9ad3178c/fastavro-1.12.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d71c8aa841ef65cfab709a22bb887955f42934bced3ddb571e98fdbdade4c609", size = 3366742 }, + { url = "https://files.pythonhosted.org/packages/3d/d1/e69534ccdd5368350646fea7d93be39e5f77c614cca825c990bd9ca58f67/fastavro-1.12.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b81fc04e85dfccf7c028e0580c606e33aa8472370b767ef058aae2c674a90746", size = 3383743 }, + { url = "https://files.pythonhosted.org/packages/58/54/b7b4a0c3fb5fcba38128542da1b26c4e6d69933c923f493548bdfd63ab6a/fastavro-1.12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9445da127751ba65975d8e4bdabf36bfcfdad70fc35b2d988e3950cce0ec0e7c", size = 1001377 }, + { url = "https://files.pythonhosted.org/packages/1e/4f/0e589089c7df0d8f57d7e5293fdc34efec9a3b758a0d4d0c99a7937e2492/fastavro-1.12.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed924233272719b5d5a6a0b4d80ef3345fc7e84fc7a382b6232192a9112d38a6", size = 3320401 }, + { url = "https://files.pythonhosted.org/packages/f9/19/260110d56194ae29d7e423a336fccea8bcd103196d00f0b364b732bdb84e/fastavro-1.12.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3616e2f0e1c9265e92954fa099db79c6e7817356d3ff34f4bcc92699ae99697c", size = 3350894 }, + { url = "https://files.pythonhosted.org/packages/d0/96/58b0411e8be9694d5972bee3167d6c1fd1fdfdf7ce253c1a19a327208f4f/fastavro-1.12.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cb0337b42fd3c047fcf0e9b7597bd6ad25868de719f29da81eabb6343f08d399", size = 3229644 }, + { url = "https://files.pythonhosted.org/packages/5b/db/38660660eac82c30471d9101f45b3acfdcbadfe42d8f7cdb129459a45050/fastavro-1.12.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:64961ab15b74b7c168717bbece5660e0f3d457837c3cc9d9145181d011199fa7", size = 3329704 }, + { url = "https://files.pythonhosted.org/packages/9d/a9/1672910f458ecb30b596c9e59e41b7c00309b602a0494341451e92e62747/fastavro-1.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:792356d320f6e757e89f7ac9c22f481e546c886454a6709247f43c0dd7058004", size = 452911 }, + { url = "https://files.pythonhosted.org/packages/dc/8d/2e15d0938ded1891b33eff252e8500605508b799c2e57188a933f0bd744c/fastavro-1.12.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120aaf82ac19d60a1016afe410935fe94728752d9c2d684e267e5b7f0e70f6d9", size = 3541999 }, + { url = "https://files.pythonhosted.org/packages/a7/1c/6dfd082a205be4510543221b734b1191299e6a1810c452b6bc76dfa6968e/fastavro-1.12.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6a3462934b20a74f9ece1daa49c2e4e749bd9a35fa2657b53bf62898fba80f5", size = 3433972 }, + { url = "https://files.pythonhosted.org/packages/24/90/9de694625a1a4b727b1ad0958d220cab25a9b6cf7f16a5c7faa9ea7b2261/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1f81011d54dd47b12437b51dd93a70a9aa17b61307abf26542fc3c13efbc6c51", size = 3368752 }, + { url = "https://files.pythonhosted.org/packages/fa/93/b44f67589e4d439913dab6720f7e3507b0fa8b8e56d06f6fc875ced26afb/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43ded16b3f4a9f1a42f5970c2aa618acb23ea59c4fcaa06680bdf470b255e5a8", size = 3386636 }, ] [[package]] @@ -2148,48 +2148,48 @@ dependencies = [ { name = "tqdm" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/05/99/49ee85903dee060d9f08297b4a342e5e0bcfca2f027a07b4ee0a38ab13f9/faster_whisper-1.2.1-py3-none-any.whl", hash = "sha256:79a66ad50688c0b794dd501dc340a736992a6342f7f95e5811be60b5224a26a7", size = 1118909, upload-time = "2025-10-31T11:35:47.794Z" }, + { url = "https://files.pythonhosted.org/packages/05/99/49ee85903dee060d9f08297b4a342e5e0bcfca2f027a07b4ee0a38ab13f9/faster_whisper-1.2.1-py3-none-any.whl", hash = "sha256:79a66ad50688c0b794dd501dc340a736992a6342f7f95e5811be60b5224a26a7", size = 1118909 }, ] [[package]] name = "fastuuid" version = "0.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/7d/d9daedf0f2ebcacd20d599928f8913e9d2aea1d56d2d355a93bfa2b611d7/fastuuid-0.14.0.tar.gz", hash = "sha256:178947fc2f995b38497a74172adee64fdeb8b7ec18f2a5934d037641ba265d26", size = 18232, upload-time = "2025-10-19T22:19:22.402Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/7d/d9daedf0f2ebcacd20d599928f8913e9d2aea1d56d2d355a93bfa2b611d7/fastuuid-0.14.0.tar.gz", hash = "sha256:178947fc2f995b38497a74172adee64fdeb8b7ec18f2a5934d037641ba265d26", size = 18232 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/a2/e78fcc5df65467f0d207661b7ef86c5b7ac62eea337c0c0fcedbeee6fb13/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77e94728324b63660ebf8adb27055e92d2e4611645bf12ed9d88d30486471d0a", size = 510164, upload-time = "2025-10-19T22:31:45.635Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b3/c846f933f22f581f558ee63f81f29fa924acd971ce903dab1a9b6701816e/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:caa1f14d2102cb8d353096bc6ef6c13b2c81f347e6ab9d6fbd48b9dea41c153d", size = 261837, upload-time = "2025-10-19T22:38:38.53Z" }, - { url = "https://files.pythonhosted.org/packages/54/ea/682551030f8c4fa9a769d9825570ad28c0c71e30cf34020b85c1f7ee7382/fastuuid-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d23ef06f9e67163be38cece704170486715b177f6baae338110983f99a72c070", size = 251370, upload-time = "2025-10-19T22:40:26.07Z" }, - { url = "https://files.pythonhosted.org/packages/14/dd/5927f0a523d8e6a76b70968e6004966ee7df30322f5fc9b6cdfb0276646a/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c9ec605ace243b6dbe3bd27ebdd5d33b00d8d1d3f580b39fdd15cd96fd71796", size = 277766, upload-time = "2025-10-19T22:37:23.779Z" }, - { url = "https://files.pythonhosted.org/packages/16/6e/c0fb547eef61293153348f12e0f75a06abb322664b34a1573a7760501336/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:808527f2407f58a76c916d6aa15d58692a4a019fdf8d4c32ac7ff303b7d7af09", size = 278105, upload-time = "2025-10-19T22:26:56.821Z" }, - { url = "https://files.pythonhosted.org/packages/2d/b1/b9c75e03b768f61cf2e84ee193dc18601aeaf89a4684b20f2f0e9f52b62c/fastuuid-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fb3c0d7fef6674bbeacdd6dbd386924a7b60b26de849266d1ff6602937675c8", size = 301564, upload-time = "2025-10-19T22:30:31.604Z" }, - { url = "https://files.pythonhosted.org/packages/fc/fa/f7395fdac07c7a54f18f801744573707321ca0cee082e638e36452355a9d/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab3f5d36e4393e628a4df337c2c039069344db5f4b9d2a3c9cea48284f1dd741", size = 459659, upload-time = "2025-10-19T22:31:32.341Z" }, - { url = "https://files.pythonhosted.org/packages/66/49/c9fd06a4a0b1f0f048aacb6599e7d96e5d6bc6fa680ed0d46bf111929d1b/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b9a0ca4f03b7e0b01425281ffd44e99d360e15c895f1907ca105854ed85e2057", size = 478430, upload-time = "2025-10-19T22:26:22.962Z" }, - { url = "https://files.pythonhosted.org/packages/be/9c/909e8c95b494e8e140e8be6165d5fc3f61fdc46198c1554df7b3e1764471/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3acdf655684cc09e60fb7e4cf524e8f42ea760031945aa8086c7eae2eeeabeb8", size = 450894, upload-time = "2025-10-19T22:27:01.647Z" }, - { url = "https://files.pythonhosted.org/packages/90/eb/d29d17521976e673c55ef7f210d4cdd72091a9ec6755d0fd4710d9b3c871/fastuuid-0.14.0-cp312-cp312-win32.whl", hash = "sha256:9579618be6280700ae36ac42c3efd157049fe4dd40ca49b021280481c78c3176", size = 154374, upload-time = "2025-10-19T22:29:19.879Z" }, - { url = "https://files.pythonhosted.org/packages/cc/fc/f5c799a6ea6d877faec0472d0b27c079b47c86b1cdc577720a5386483b36/fastuuid-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:d9e4332dc4ba054434a9594cbfaf7823b57993d7d8e7267831c3e059857cf397", size = 156550, upload-time = "2025-10-19T22:27:49.658Z" }, - { url = "https://files.pythonhosted.org/packages/a5/83/ae12dd39b9a39b55d7f90abb8971f1a5f3c321fd72d5aa83f90dc67fe9ed/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77a09cb7427e7af74c594e409f7731a0cf887221de2f698e1ca0ebf0f3139021", size = 510720, upload-time = "2025-10-19T22:42:34.633Z" }, - { url = "https://files.pythonhosted.org/packages/53/b0/a4b03ff5d00f563cc7546b933c28cb3f2a07344b2aec5834e874f7d44143/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9bd57289daf7b153bfa3e8013446aa144ce5e8c825e9e366d455155ede5ea2dc", size = 262024, upload-time = "2025-10-19T22:30:25.482Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6d/64aee0a0f6a58eeabadd582e55d0d7d70258ffdd01d093b30c53d668303b/fastuuid-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac60fc860cdf3c3f327374db87ab8e064c86566ca8c49d2e30df15eda1b0c2d5", size = 251679, upload-time = "2025-10-19T22:36:14.096Z" }, - { url = "https://files.pythonhosted.org/packages/60/f5/a7e9cda8369e4f7919d36552db9b2ae21db7915083bc6336f1b0082c8b2e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab32f74bd56565b186f036e33129da77db8be09178cd2f5206a5d4035fb2a23f", size = 277862, upload-time = "2025-10-19T22:36:23.302Z" }, - { url = "https://files.pythonhosted.org/packages/f0/d3/8ce11827c783affffd5bd4d6378b28eb6cc6d2ddf41474006b8d62e7448e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e678459cf4addaedd9936bbb038e35b3f6b2061330fd8f2f6a1d80414c0f87", size = 278278, upload-time = "2025-10-19T22:29:43.809Z" }, - { url = "https://files.pythonhosted.org/packages/a2/51/680fb6352d0bbade04036da46264a8001f74b7484e2fd1f4da9e3db1c666/fastuuid-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e3cc56742f76cd25ecb98e4b82a25f978ccffba02e4bdce8aba857b6d85d87b", size = 301788, upload-time = "2025-10-19T22:36:06.825Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7c/2014b5785bd8ebdab04ec857635ebd84d5ee4950186a577db9eff0fb8ff6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cb9a030f609194b679e1660f7e32733b7a0f332d519c5d5a6a0a580991290022", size = 459819, upload-time = "2025-10-19T22:35:31.623Z" }, - { url = "https://files.pythonhosted.org/packages/01/d2/524d4ceeba9160e7a9bc2ea3e8f4ccf1ad78f3bde34090ca0c51f09a5e91/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:09098762aad4f8da3a888eb9ae01c84430c907a297b97166b8abc07b640f2995", size = 478546, upload-time = "2025-10-19T22:26:03.023Z" }, - { url = "https://files.pythonhosted.org/packages/bc/17/354d04951ce114bf4afc78e27a18cfbd6ee319ab1829c2d5fb5e94063ac6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1383fff584fa249b16329a059c68ad45d030d5a4b70fb7c73a08d98fd53bcdab", size = 450921, upload-time = "2025-10-19T22:31:02.151Z" }, - { url = "https://files.pythonhosted.org/packages/fb/be/d7be8670151d16d88f15bb121c5b66cdb5ea6a0c2a362d0dcf30276ade53/fastuuid-0.14.0-cp313-cp313-win32.whl", hash = "sha256:a0809f8cc5731c066c909047f9a314d5f536c871a7a22e815cc4967c110ac9ad", size = 154559, upload-time = "2025-10-19T22:36:36.011Z" }, - { url = "https://files.pythonhosted.org/packages/22/1d/5573ef3624ceb7abf4a46073d3554e37191c868abc3aecd5289a72f9810a/fastuuid-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0df14e92e7ad3276327631c9e7cec09e32572ce82089c55cb1bb8df71cf394ed", size = 156539, upload-time = "2025-10-19T22:33:35.898Z" }, - { url = "https://files.pythonhosted.org/packages/16/c9/8c7660d1fe3862e3f8acabd9be7fc9ad71eb270f1c65cce9a2b7a31329ab/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b852a870a61cfc26c884af205d502881a2e59cc07076b60ab4a951cc0c94d1ad", size = 510600, upload-time = "2025-10-19T22:43:44.17Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f4/a989c82f9a90d0ad995aa957b3e572ebef163c5299823b4027986f133dfb/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c7502d6f54cd08024c3ea9b3514e2d6f190feb2f46e6dbcd3747882264bb5f7b", size = 262069, upload-time = "2025-10-19T22:43:38.38Z" }, - { url = "https://files.pythonhosted.org/packages/da/6c/a1a24f73574ac995482b1326cf7ab41301af0fabaa3e37eeb6b3df00e6e2/fastuuid-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ca61b592120cf314cfd66e662a5b54a578c5a15b26305e1b8b618a6f22df714", size = 251543, upload-time = "2025-10-19T22:32:22.537Z" }, - { url = "https://files.pythonhosted.org/packages/1a/20/2a9b59185ba7a6c7b37808431477c2d739fcbdabbf63e00243e37bd6bf49/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa75b6657ec129d0abded3bec745e6f7ab642e6dba3a5272a68247e85f5f316f", size = 277798, upload-time = "2025-10-19T22:33:53.821Z" }, - { url = "https://files.pythonhosted.org/packages/ef/33/4105ca574f6ded0af6a797d39add041bcfb468a1255fbbe82fcb6f592da2/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a0dfea3972200f72d4c7df02c8ac70bad1bb4c58d7e0ec1e6f341679073a7f", size = 278283, upload-time = "2025-10-19T22:29:02.812Z" }, - { url = "https://files.pythonhosted.org/packages/fe/8c/fca59f8e21c4deb013f574eae05723737ddb1d2937ce87cb2a5d20992dc3/fastuuid-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bf539a7a95f35b419f9ad105d5a8a35036df35fdafae48fb2fd2e5f318f0d75", size = 301627, upload-time = "2025-10-19T22:35:54.985Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e2/f78c271b909c034d429218f2798ca4e89eeda7983f4257d7865976ddbb6c/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:9a133bf9cc78fdbd1179cb58a59ad0100aa32d8675508150f3658814aeefeaa4", size = 459778, upload-time = "2025-10-19T22:28:00.999Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f0/5ff209d865897667a2ff3e7a572267a9ced8f7313919f6d6043aed8b1caa/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_i686.whl", hash = "sha256:f54d5b36c56a2d5e1a31e73b950b28a0d83eb0c37b91d10408875a5a29494bad", size = 478605, upload-time = "2025-10-19T22:36:21.764Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c8/2ce1c78f983a2c4987ea865d9516dbdfb141a120fd3abb977ae6f02ba7ca/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:ec27778c6ca3393ef662e2762dba8af13f4ec1aaa32d08d77f71f2a70ae9feb8", size = 450837, upload-time = "2025-10-19T22:34:37.178Z" }, - { url = "https://files.pythonhosted.org/packages/df/60/dad662ec9a33b4a5fe44f60699258da64172c39bd041da2994422cdc40fe/fastuuid-0.14.0-cp314-cp314-win32.whl", hash = "sha256:e23fc6a83f112de4be0cc1990e5b127c27663ae43f866353166f87df58e73d06", size = 154532, upload-time = "2025-10-19T22:35:18.217Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f6/da4db31001e854025ffd26bc9ba0740a9cbba2c3259695f7c5834908b336/fastuuid-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:df61342889d0f5e7a32f7284e55ef95103f2110fee433c2ae7c2c0956d76ac8a", size = 156457, upload-time = "2025-10-19T22:33:44.579Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/e78fcc5df65467f0d207661b7ef86c5b7ac62eea337c0c0fcedbeee6fb13/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77e94728324b63660ebf8adb27055e92d2e4611645bf12ed9d88d30486471d0a", size = 510164 }, + { url = "https://files.pythonhosted.org/packages/2b/b3/c846f933f22f581f558ee63f81f29fa924acd971ce903dab1a9b6701816e/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:caa1f14d2102cb8d353096bc6ef6c13b2c81f347e6ab9d6fbd48b9dea41c153d", size = 261837 }, + { url = "https://files.pythonhosted.org/packages/54/ea/682551030f8c4fa9a769d9825570ad28c0c71e30cf34020b85c1f7ee7382/fastuuid-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d23ef06f9e67163be38cece704170486715b177f6baae338110983f99a72c070", size = 251370 }, + { url = "https://files.pythonhosted.org/packages/14/dd/5927f0a523d8e6a76b70968e6004966ee7df30322f5fc9b6cdfb0276646a/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c9ec605ace243b6dbe3bd27ebdd5d33b00d8d1d3f580b39fdd15cd96fd71796", size = 277766 }, + { url = "https://files.pythonhosted.org/packages/16/6e/c0fb547eef61293153348f12e0f75a06abb322664b34a1573a7760501336/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:808527f2407f58a76c916d6aa15d58692a4a019fdf8d4c32ac7ff303b7d7af09", size = 278105 }, + { url = "https://files.pythonhosted.org/packages/2d/b1/b9c75e03b768f61cf2e84ee193dc18601aeaf89a4684b20f2f0e9f52b62c/fastuuid-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fb3c0d7fef6674bbeacdd6dbd386924a7b60b26de849266d1ff6602937675c8", size = 301564 }, + { url = "https://files.pythonhosted.org/packages/fc/fa/f7395fdac07c7a54f18f801744573707321ca0cee082e638e36452355a9d/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab3f5d36e4393e628a4df337c2c039069344db5f4b9d2a3c9cea48284f1dd741", size = 459659 }, + { url = "https://files.pythonhosted.org/packages/66/49/c9fd06a4a0b1f0f048aacb6599e7d96e5d6bc6fa680ed0d46bf111929d1b/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b9a0ca4f03b7e0b01425281ffd44e99d360e15c895f1907ca105854ed85e2057", size = 478430 }, + { url = "https://files.pythonhosted.org/packages/be/9c/909e8c95b494e8e140e8be6165d5fc3f61fdc46198c1554df7b3e1764471/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3acdf655684cc09e60fb7e4cf524e8f42ea760031945aa8086c7eae2eeeabeb8", size = 450894 }, + { url = "https://files.pythonhosted.org/packages/90/eb/d29d17521976e673c55ef7f210d4cdd72091a9ec6755d0fd4710d9b3c871/fastuuid-0.14.0-cp312-cp312-win32.whl", hash = "sha256:9579618be6280700ae36ac42c3efd157049fe4dd40ca49b021280481c78c3176", size = 154374 }, + { url = "https://files.pythonhosted.org/packages/cc/fc/f5c799a6ea6d877faec0472d0b27c079b47c86b1cdc577720a5386483b36/fastuuid-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:d9e4332dc4ba054434a9594cbfaf7823b57993d7d8e7267831c3e059857cf397", size = 156550 }, + { url = "https://files.pythonhosted.org/packages/a5/83/ae12dd39b9a39b55d7f90abb8971f1a5f3c321fd72d5aa83f90dc67fe9ed/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77a09cb7427e7af74c594e409f7731a0cf887221de2f698e1ca0ebf0f3139021", size = 510720 }, + { url = "https://files.pythonhosted.org/packages/53/b0/a4b03ff5d00f563cc7546b933c28cb3f2a07344b2aec5834e874f7d44143/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9bd57289daf7b153bfa3e8013446aa144ce5e8c825e9e366d455155ede5ea2dc", size = 262024 }, + { url = "https://files.pythonhosted.org/packages/9c/6d/64aee0a0f6a58eeabadd582e55d0d7d70258ffdd01d093b30c53d668303b/fastuuid-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac60fc860cdf3c3f327374db87ab8e064c86566ca8c49d2e30df15eda1b0c2d5", size = 251679 }, + { url = "https://files.pythonhosted.org/packages/60/f5/a7e9cda8369e4f7919d36552db9b2ae21db7915083bc6336f1b0082c8b2e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab32f74bd56565b186f036e33129da77db8be09178cd2f5206a5d4035fb2a23f", size = 277862 }, + { url = "https://files.pythonhosted.org/packages/f0/d3/8ce11827c783affffd5bd4d6378b28eb6cc6d2ddf41474006b8d62e7448e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e678459cf4addaedd9936bbb038e35b3f6b2061330fd8f2f6a1d80414c0f87", size = 278278 }, + { url = "https://files.pythonhosted.org/packages/a2/51/680fb6352d0bbade04036da46264a8001f74b7484e2fd1f4da9e3db1c666/fastuuid-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e3cc56742f76cd25ecb98e4b82a25f978ccffba02e4bdce8aba857b6d85d87b", size = 301788 }, + { url = "https://files.pythonhosted.org/packages/fa/7c/2014b5785bd8ebdab04ec857635ebd84d5ee4950186a577db9eff0fb8ff6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cb9a030f609194b679e1660f7e32733b7a0f332d519c5d5a6a0a580991290022", size = 459819 }, + { url = "https://files.pythonhosted.org/packages/01/d2/524d4ceeba9160e7a9bc2ea3e8f4ccf1ad78f3bde34090ca0c51f09a5e91/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:09098762aad4f8da3a888eb9ae01c84430c907a297b97166b8abc07b640f2995", size = 478546 }, + { url = "https://files.pythonhosted.org/packages/bc/17/354d04951ce114bf4afc78e27a18cfbd6ee319ab1829c2d5fb5e94063ac6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1383fff584fa249b16329a059c68ad45d030d5a4b70fb7c73a08d98fd53bcdab", size = 450921 }, + { url = "https://files.pythonhosted.org/packages/fb/be/d7be8670151d16d88f15bb121c5b66cdb5ea6a0c2a362d0dcf30276ade53/fastuuid-0.14.0-cp313-cp313-win32.whl", hash = "sha256:a0809f8cc5731c066c909047f9a314d5f536c871a7a22e815cc4967c110ac9ad", size = 154559 }, + { url = "https://files.pythonhosted.org/packages/22/1d/5573ef3624ceb7abf4a46073d3554e37191c868abc3aecd5289a72f9810a/fastuuid-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0df14e92e7ad3276327631c9e7cec09e32572ce82089c55cb1bb8df71cf394ed", size = 156539 }, + { url = "https://files.pythonhosted.org/packages/16/c9/8c7660d1fe3862e3f8acabd9be7fc9ad71eb270f1c65cce9a2b7a31329ab/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b852a870a61cfc26c884af205d502881a2e59cc07076b60ab4a951cc0c94d1ad", size = 510600 }, + { url = "https://files.pythonhosted.org/packages/4c/f4/a989c82f9a90d0ad995aa957b3e572ebef163c5299823b4027986f133dfb/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c7502d6f54cd08024c3ea9b3514e2d6f190feb2f46e6dbcd3747882264bb5f7b", size = 262069 }, + { url = "https://files.pythonhosted.org/packages/da/6c/a1a24f73574ac995482b1326cf7ab41301af0fabaa3e37eeb6b3df00e6e2/fastuuid-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ca61b592120cf314cfd66e662a5b54a578c5a15b26305e1b8b618a6f22df714", size = 251543 }, + { url = "https://files.pythonhosted.org/packages/1a/20/2a9b59185ba7a6c7b37808431477c2d739fcbdabbf63e00243e37bd6bf49/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa75b6657ec129d0abded3bec745e6f7ab642e6dba3a5272a68247e85f5f316f", size = 277798 }, + { url = "https://files.pythonhosted.org/packages/ef/33/4105ca574f6ded0af6a797d39add041bcfb468a1255fbbe82fcb6f592da2/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a0dfea3972200f72d4c7df02c8ac70bad1bb4c58d7e0ec1e6f341679073a7f", size = 278283 }, + { url = "https://files.pythonhosted.org/packages/fe/8c/fca59f8e21c4deb013f574eae05723737ddb1d2937ce87cb2a5d20992dc3/fastuuid-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bf539a7a95f35b419f9ad105d5a8a35036df35fdafae48fb2fd2e5f318f0d75", size = 301627 }, + { url = "https://files.pythonhosted.org/packages/cb/e2/f78c271b909c034d429218f2798ca4e89eeda7983f4257d7865976ddbb6c/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:9a133bf9cc78fdbd1179cb58a59ad0100aa32d8675508150f3658814aeefeaa4", size = 459778 }, + { url = "https://files.pythonhosted.org/packages/1e/f0/5ff209d865897667a2ff3e7a572267a9ced8f7313919f6d6043aed8b1caa/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_i686.whl", hash = "sha256:f54d5b36c56a2d5e1a31e73b950b28a0d83eb0c37b91d10408875a5a29494bad", size = 478605 }, + { url = "https://files.pythonhosted.org/packages/e0/c8/2ce1c78f983a2c4987ea865d9516dbdfb141a120fd3abb977ae6f02ba7ca/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:ec27778c6ca3393ef662e2762dba8af13f4ec1aaa32d08d77f71f2a70ae9feb8", size = 450837 }, + { url = "https://files.pythonhosted.org/packages/df/60/dad662ec9a33b4a5fe44f60699258da64172c39bd041da2994422cdc40fe/fastuuid-0.14.0-cp314-cp314-win32.whl", hash = "sha256:e23fc6a83f112de4be0cc1990e5b127c27663ae43f866353166f87df58e73d06", size = 154532 }, + { url = "https://files.pythonhosted.org/packages/1f/f6/da4db31001e854025ffd26bc9ba0740a9cbba2c3259695f7c5834908b336/fastuuid-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:df61342889d0f5e7a32f7284e55ef95103f2110fee433c2ae7c2c0956d76ac8a", size = 156457 }, ] [[package]] @@ -2199,27 +2199,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "future" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dd/5e/d5f9105d59c1325759d838af4e973695081fbbc97182baf73afc78dec266/ffmpeg-python-0.2.0.tar.gz", hash = "sha256:65225db34627c578ef0e11c8b1eb528bb35e024752f6f10b78c011f6f64c4127", size = 21543, upload-time = "2019-07-06T00:19:08.989Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/5e/d5f9105d59c1325759d838af4e973695081fbbc97182baf73afc78dec266/ffmpeg-python-0.2.0.tar.gz", hash = "sha256:65225db34627c578ef0e11c8b1eb528bb35e024752f6f10b78c011f6f64c4127", size = 21543 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/0c/56be52741f75bad4dc6555991fabd2e07b432d333da82c11ad701123888a/ffmpeg_python-0.2.0-py3-none-any.whl", hash = "sha256:ac441a0404e053f8b6a1113a77c0f452f1cfc62f6344a769475ffdc0f56c23c5", size = 25024, upload-time = "2019-07-06T00:19:07.215Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0c/56be52741f75bad4dc6555991fabd2e07b432d333da82c11ad701123888a/ffmpeg_python-0.2.0-py3-none-any.whl", hash = "sha256:ac441a0404e053f8b6a1113a77c0f452f1cfc62f6344a769475ffdc0f56c23c5", size = 25024 }, ] [[package]] name = "filelock" version = "3.25.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759 }, ] [[package]] name = "filetype" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020, upload-time = "2022-11-02T17:34:04.141Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970, upload-time = "2022-11-02T17:34:01.425Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970 }, ] [[package]] @@ -2235,9 +2235,9 @@ dependencies = [ { name = "requests" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/6b/8201b737c0667bf70748b86a6fb117aefc648154b4e05c5ee649432cbc3d/firecrawl_py-4.21.0.tar.gz", hash = "sha256:14a7e0967d816c711c3c53325c9371e2f780a787d1e94333a34d8aea7a43a237", size = 174256, upload-time = "2026-03-25T16:22:00.002Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/6b/8201b737c0667bf70748b86a6fb117aefc648154b4e05c5ee649432cbc3d/firecrawl_py-4.21.0.tar.gz", hash = "sha256:14a7e0967d816c711c3c53325c9371e2f780a787d1e94333a34d8aea7a43a237", size = 174256 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/f1/1c0f1e5b33a318d7b9705b9e23c4397253d730e516e3d8a2f6aaea4b71a2/firecrawl_py-4.21.0-py3-none-any.whl", hash = "sha256:4e431f36117b4f2aaae633e747859a91626b0f2c6aaa6b7f86dfb7669a3595eb", size = 217607, upload-time = "2026-03-25T16:21:58.708Z" }, + { url = "https://files.pythonhosted.org/packages/18/f1/1c0f1e5b33a318d7b9705b9e23c4397253d730e516e3d8a2f6aaea4b71a2/firecrawl_py-4.21.0-py3-none-any.whl", hash = "sha256:4e431f36117b4f2aaae633e747859a91626b0f2c6aaa6b7f86dfb7669a3595eb", size = 217607 }, ] [[package]] @@ -2251,9 +2251,9 @@ dependencies = [ { name = "tokenizers" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/1f/176cb4a857a70c3538f637e19389ab6aed21548a1ba1d1424fccc8bba108/FlashRank-0.2.10.tar.gz", hash = "sha256:f8f82a25c32fdfc668a09dc4089421d6aab8e7f71308424b541f40bb3f01d9db", size = 18905, upload-time = "2025-01-06T13:33:01.657Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/1f/176cb4a857a70c3538f637e19389ab6aed21548a1ba1d1424fccc8bba108/FlashRank-0.2.10.tar.gz", hash = "sha256:f8f82a25c32fdfc668a09dc4089421d6aab8e7f71308424b541f40bb3f01d9db", size = 18905 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/99/72639cc1c9221c5bc77a2df1c2d352fe11965553bdf7d3e0856e7fcc8fd6/FlashRank-0.2.10-py3-none-any.whl", hash = "sha256:5d3272ae657d793c132d1e7917ed9e2adf49e0e1c60735583a67b051c6f0434a", size = 14511, upload-time = "2025-01-06T13:32:59.42Z" }, + { url = "https://files.pythonhosted.org/packages/ec/99/72639cc1c9221c5bc77a2df1c2d352fe11965553bdf7d3e0856e7fcc8fd6/FlashRank-0.2.10-py3-none-any.whl", hash = "sha256:5d3272ae657d793c132d1e7917ed9e2adf49e0e1c60735583a67b051c6f0434a", size = 14511 }, ] [[package]] @@ -2261,7 +2261,7 @@ name = "flatbuffers" version = "25.12.19" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661 }, ] [[package]] @@ -2275,9 +2275,9 @@ dependencies = [ { name = "pytz" }, { name = "tornado" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/a1/357f1b5d8946deafdcfdd604f51baae9de10aafa2908d0b7322597155f92/flower-2.0.1.tar.gz", hash = "sha256:5ab717b979530770c16afb48b50d2a98d23c3e9fe39851dcf6bc4d01845a02a0", size = 3220408, upload-time = "2023-08-13T14:37:46.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/a1/357f1b5d8946deafdcfdd604f51baae9de10aafa2908d0b7322597155f92/flower-2.0.1.tar.gz", hash = "sha256:5ab717b979530770c16afb48b50d2a98d23c3e9fe39851dcf6bc4d01845a02a0", size = 3220408 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/ff/ee2f67c0ff146ec98b5df1df637b2bc2d17beeb05df9f427a67bd7a7d79c/flower-2.0.1-py2.py3-none-any.whl", hash = "sha256:9db2c621eeefbc844c8dd88be64aef61e84e2deb29b271e02ab2b5b9f01068e2", size = 383553, upload-time = "2023-08-13T14:37:41.552Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ff/ee2f67c0ff146ec98b5df1df637b2bc2d17beeb05df9f427a67bd7a7d79c/flower-2.0.1-py2.py3-none-any.whl", hash = "sha256:9db2c621eeefbc844c8dd88be64aef61e84e2deb29b271e02ab2b5b9f01068e2", size = 383553 }, ] [[package]] @@ -2287,50 +2287,50 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/a5/15fe839297d761e04c4578b11013ed46353e63b44b5e42b59c2078602fa1/flupy-1.2.3.tar.gz", hash = "sha256:220b6d40dea238cd2d66784c0d4d2a5483447a48acd343385768e0c740af9609", size = 12327, upload-time = "2025-07-15T14:08:21.14Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/a5/15fe839297d761e04c4578b11013ed46353e63b44b5e42b59c2078602fa1/flupy-1.2.3.tar.gz", hash = "sha256:220b6d40dea238cd2d66784c0d4d2a5483447a48acd343385768e0c740af9609", size = 12327 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/26/d4d1629f846ae2913e88f74955a3c3f41f3863e74c5fbc1cb79af9550717/flupy-1.2.3-py3-none-any.whl", hash = "sha256:be0f5a393bad2b3534697fbab17081993cd3f5817169dd3a61e8b2e0887612e6", size = 12512, upload-time = "2025-07-18T20:15:21.384Z" }, + { url = "https://files.pythonhosted.org/packages/7c/26/d4d1629f846ae2913e88f74955a3c3f41f3863e74c5fbc1cb79af9550717/flupy-1.2.3-py3-none-any.whl", hash = "sha256:be0f5a393bad2b3534697fbab17081993cd3f5817169dd3a61e8b2e0887612e6", size = 12512 }, ] [[package]] name = "fonttools" version = "4.62.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737 } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, - { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, - { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, - { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, - { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, - { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, - { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, - { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" }, - { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" }, - { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" }, - { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" }, - { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" }, - { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" }, - { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" }, - { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, - { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, - { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, - { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, - { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, - { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, - { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, - { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, - { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, - { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, - { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, - { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, - { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219 }, + { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891 }, + { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197 }, + { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768 }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512 }, + { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723 }, + { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278 }, + { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414 }, + { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155 }, + { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802 }, + { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926 }, + { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575 }, + { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693 }, + { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920 }, + { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928 }, + { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514 }, + { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442 }, + { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901 }, + { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608 }, + { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726 }, + { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422 }, + { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979 }, + { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733 }, + { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663 }, + { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288 }, + { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023 }, + { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599 }, + { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933 }, + { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232 }, + { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987 }, + { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021 }, + { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147 }, + { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647 }, ] [[package]] @@ -2340,107 +2340,107 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/9e/2c14b6dc812defa58e9d515b44b4d126b48338545e14089413df50dadda3/fractional-indexing-0.1.3.tar.gz", hash = "sha256:59e25f441c213bf7f921aa329a6bf3e5930ee8d8852df0b4e930aaf39a39ac5e", size = 7791, upload-time = "2023-08-13T10:03:16.946Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/9e/2c14b6dc812defa58e9d515b44b4d126b48338545e14089413df50dadda3/fractional-indexing-0.1.3.tar.gz", hash = "sha256:59e25f441c213bf7f921aa329a6bf3e5930ee8d8852df0b4e930aaf39a39ac5e", size = 7791 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/d1/627b86b1eb95ec2c7e012f0036e28a9335f3177f6c7e15047df6ee544bbc/fractional_indexing-0.1.3-py3-none-any.whl", hash = "sha256:389abb9da24add388af3f0146ee4fcf2ebec5816ee728fa672657e394d9a7824", size = 7971, upload-time = "2023-08-13T10:03:15.504Z" }, + { url = "https://files.pythonhosted.org/packages/02/d1/627b86b1eb95ec2c7e012f0036e28a9335f3177f6c7e15047df6ee544bbc/fractional_indexing-0.1.3-py3-none-any.whl", hash = "sha256:389abb9da24add388af3f0146ee4fcf2ebec5816ee728fa672657e394d9a7824", size = 7971 }, ] [[package]] name = "frozenlist" version = "1.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875 } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, - { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, - { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, - { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, - { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, - { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, - { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, - { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, - { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, - { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, - { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, - { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, - { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, - { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, - { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, - { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, - { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, - { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, - { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, - { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, - { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, - { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, - { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, - { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, - { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, - { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, - { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, - { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, - { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, - { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, - { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, - { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, - { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, - { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, - { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, - { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, - { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, - { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, - { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, - { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, - { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, - { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, - { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, - { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, - { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, - { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, - { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, - { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, - { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, - { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, - { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, - { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, - { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, - { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, - { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, - { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, - { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, - { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, - { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, - { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, - { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, - { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782 }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594 }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448 }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411 }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014 }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909 }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049 }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485 }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619 }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320 }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820 }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518 }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096 }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985 }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591 }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102 }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717 }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651 }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417 }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391 }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048 }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549 }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833 }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363 }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314 }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365 }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763 }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110 }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717 }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628 }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882 }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676 }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235 }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742 }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725 }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506 }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161 }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676 }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638 }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067 }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101 }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901 }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395 }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659 }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492 }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034 }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749 }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127 }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698 }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749 }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298 }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015 }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038 }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130 }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845 }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131 }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542 }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308 }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210 }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972 }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536 }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330 }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627 }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238 }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738 }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739 }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186 }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196 }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830 }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289 }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318 }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814 }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762 }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470 }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042 }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148 }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676 }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451 }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507 }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409 }, ] [[package]] name = "fsspec" version = "2025.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/f4/5721faf47b8c499e776bc34c6a8fc17efdf7fdef0b00f398128bc5dcb4ac/fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972", size = 298491, upload-time = "2025-03-07T21:47:56.461Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/f4/5721faf47b8c499e776bc34c6a8fc17efdf7fdef0b00f398128bc5dcb4ac/fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972", size = 298491 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615, upload-time = "2025-03-07T21:47:54.809Z" }, + { url = "https://files.pythonhosted.org/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615 }, ] [package.optional-dependencies] @@ -2452,9 +2452,9 @@ http = [ name = "future" version = "1.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490, upload-time = "2024-02-21T11:52:38.461Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490 } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326, upload-time = "2024-02-21T11:52:35.956Z" }, + { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326 }, ] [[package]] @@ -2467,9 +2467,9 @@ dependencies = [ { name = "requests" }, { name = "uritemplate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/91/603bcaf8cd1b3927de64bf56c3a8915f6653ea7281919140c5bcff2bfe7b/github3.py-4.0.1.tar.gz", hash = "sha256:30d571076753efc389edc7f9aaef338a4fcb24b54d8968d5f39b1342f45ddd36", size = 36214038, upload-time = "2023-04-26T17:56:37.677Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/91/603bcaf8cd1b3927de64bf56c3a8915f6653ea7281919140c5bcff2bfe7b/github3.py-4.0.1.tar.gz", hash = "sha256:30d571076753efc389edc7f9aaef338a4fcb24b54d8968d5f39b1342f45ddd36", size = 36214038 } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/2394d4fb542574678b0ba342daf734d4d811768da3c2ee0c84d509dcb26c/github3.py-4.0.1-py3-none-any.whl", hash = "sha256:a89af7de25650612d1da2f0609622bcdeb07ee8a45a1c06b2d16a05e4234e753", size = 151800, upload-time = "2023-04-26T17:56:25.015Z" }, + { url = "https://files.pythonhosted.org/packages/61/ad/2394d4fb542574678b0ba342daf734d4d811768da3c2ee0c84d509dcb26c/github3.py-4.0.1-py3-none-any.whl", hash = "sha256:a89af7de25650612d1da2f0609622bcdeb07ee8a45a1c06b2d16a05e4234e753", size = 151800 }, ] [[package]] @@ -2486,9 +2486,9 @@ dependencies = [ { name = "starlette" }, { name = "tiktoken" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/fe/a915f0c32a3d7920206a677f73c185b3eadf4ec151fb05aedd52e64713f7/gitingest-0.3.1.tar.gz", hash = "sha256:4587cab873d4e08bdb16d612bb153c23e0ce59771a1d57a438239c5e39f05ebf", size = 70681, upload-time = "2025-07-31T13:56:19.845Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/fe/a915f0c32a3d7920206a677f73c185b3eadf4ec151fb05aedd52e64713f7/gitingest-0.3.1.tar.gz", hash = "sha256:4587cab873d4e08bdb16d612bb153c23e0ce59771a1d57a438239c5e39f05ebf", size = 70681 } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/15/f200ab2e73287e67d1dce6fbacf421552ae9fbafdc5f0cc8dd0d2fe4fc47/gitingest-0.3.1-py3-none-any.whl", hash = "sha256:8143a5e6a7140ede9f680e13d3931ac07c82ac9bd8bab9ad1fba017c8c1e8666", size = 68343, upload-time = "2025-07-31T13:56:17.729Z" }, + { url = "https://files.pythonhosted.org/packages/00/15/f200ab2e73287e67d1dce6fbacf421552ae9fbafdc5f0cc8dd0d2fe4fc47/gitingest-0.3.1-py3-none-any.whl", hash = "sha256:8143a5e6a7140ede9f680e13d3931ac07c82ac9bd8bab9ad1fba017c8c1e8666", size = 68343 }, ] [[package]] @@ -2502,9 +2502,9 @@ dependencies = [ { name = "protobuf" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/22/98/586ec94553b569080caef635f98a3723db36a38eac0e3d7eb3ea9d2e4b9a/google_api_core-2.30.0.tar.gz", hash = "sha256:02edfa9fab31e17fc0befb5f161b3bf93c9096d99aed584625f38065c511ad9b", size = 176959, upload-time = "2026-02-18T20:28:11.926Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/98/586ec94553b569080caef635f98a3723db36a38eac0e3d7eb3ea9d2e4b9a/google_api_core-2.30.0.tar.gz", hash = "sha256:02edfa9fab31e17fc0befb5f161b3bf93c9096d99aed584625f38065c511ad9b", size = 176959 } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/27/09c33d67f7e0dcf06d7ac17d196594e66989299374bfb0d4331d1038e76b/google_api_core-2.30.0-py3-none-any.whl", hash = "sha256:80be49ee937ff9aba0fd79a6eddfde35fe658b9953ab9b79c57dd7061afa8df5", size = 173288, upload-time = "2026-02-18T20:28:10.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/27/09c33d67f7e0dcf06d7ac17d196594e66989299374bfb0d4331d1038e76b/google_api_core-2.30.0-py3-none-any.whl", hash = "sha256:80be49ee937ff9aba0fd79a6eddfde35fe658b9953ab9b79c57dd7061afa8df5", size = 173288 }, ] [package.optional-dependencies] @@ -2524,9 +2524,9 @@ dependencies = [ { name = "httplib2" }, { name = "uritemplate" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/f4/e14b6815d3b1885328dd209676a3a4c704882743ac94e18ef0093894f5c8/google_api_python_client-2.193.0.tar.gz", hash = "sha256:8f88d16e89d11341e0a8b199cafde0fb7e6b44260dffb88d451577cbd1bb5d33", size = 14281006, upload-time = "2026-03-17T18:25:29.415Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/f4/e14b6815d3b1885328dd209676a3a4c704882743ac94e18ef0093894f5c8/google_api_python_client-2.193.0.tar.gz", hash = "sha256:8f88d16e89d11341e0a8b199cafde0fb7e6b44260dffb88d451577cbd1bb5d33", size = 14281006 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/6d/fe75167797790a56d17799b75e1129bb93f7ff061efc7b36e9731bd4be2b/google_api_python_client-2.193.0-py3-none-any.whl", hash = "sha256:c42aa324b822109901cfecab5dc4fc3915d35a7b376835233c916c70610322db", size = 14856490, upload-time = "2026-03-17T18:25:26.608Z" }, + { url = "https://files.pythonhosted.org/packages/f0/6d/fe75167797790a56d17799b75e1129bb93f7ff061efc7b36e9731bd4be2b/google_api_python_client-2.193.0-py3-none-any.whl", hash = "sha256:c42aa324b822109901cfecab5dc4fc3915d35a7b376835233c916c70610322db", size = 14856490 }, ] [[package]] @@ -2537,9 +2537,9 @@ dependencies = [ { name = "cryptography" }, { name = "pyasn1-modules" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ea/80/6a696a07d3d3b0a92488933532f03dbefa4a24ab80fb231395b9a2a1be77/google_auth-2.49.1.tar.gz", hash = "sha256:16d40da1c3c5a0533f57d268fe72e0ebb0ae1cc3b567024122651c045d879b64", size = 333825, upload-time = "2026-03-12T19:30:58.135Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/80/6a696a07d3d3b0a92488933532f03dbefa4a24ab80fb231395b9a2a1be77/google_auth-2.49.1.tar.gz", hash = "sha256:16d40da1c3c5a0533f57d268fe72e0ebb0ae1cc3b567024122651c045d879b64", size = 333825 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/eb/c6c2478d8a8d633460be40e2a8a6f8f429171997a35a96f81d3b680dec83/google_auth-2.49.1-py3-none-any.whl", hash = "sha256:195ebe3dca18eddd1b3db5edc5189b76c13e96f29e73043b923ebcf3f1a860f7", size = 240737, upload-time = "2026-03-12T19:30:53.159Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/c6c2478d8a8d633460be40e2a8a6f8f429171997a35a96f81d3b680dec83/google_auth-2.49.1-py3-none-any.whl", hash = "sha256:195ebe3dca18eddd1b3db5edc5189b76c13e96f29e73043b923ebcf3f1a860f7", size = 240737 }, ] [package.optional-dependencies] @@ -2555,9 +2555,9 @@ dependencies = [ { name = "google-auth" }, { name = "httplib2" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/ad/c1f2b1175096a8d04cf202ad5ea6065f108d26be6fc7215876bde4a7981d/google_auth_httplib2-0.3.0.tar.gz", hash = "sha256:177898a0175252480d5ed916aeea183c2df87c1f9c26705d74ae6b951c268b0b", size = 11134, upload-time = "2025-12-15T22:13:51.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/ad/c1f2b1175096a8d04cf202ad5ea6065f108d26be6fc7215876bde4a7981d/google_auth_httplib2-0.3.0.tar.gz", hash = "sha256:177898a0175252480d5ed916aeea183c2df87c1f9c26705d74ae6b951c268b0b", size = 11134 } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/d5/3c97526c8796d3caf5f4b3bed2b05e8a7102326f00a334e7a438237f3b22/google_auth_httplib2-0.3.0-py3-none-any.whl", hash = "sha256:426167e5df066e3f5a0fc7ea18768c08e7296046594ce4c8c409c2457dd1f776", size = 9529, upload-time = "2025-12-15T22:13:51.048Z" }, + { url = "https://files.pythonhosted.org/packages/99/d5/3c97526c8796d3caf5f4b3bed2b05e8a7102326f00a334e7a438237f3b22/google_auth_httplib2-0.3.0-py3-none-any.whl", hash = "sha256:426167e5df066e3f5a0fc7ea18768c08e7296046594ce4c8c409c2457dd1f776", size = 9529 }, ] [[package]] @@ -2568,9 +2568,9 @@ dependencies = [ { name = "google-auth" }, { name = "requests-oauthlib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/b4/1b19567e4c567b796f5c593d89895f3cfae5a38e04f27c6af87618fd0942/google_auth_oauthlib-1.3.0.tar.gz", hash = "sha256:cd39e807ac7229d6b8b9c1e297321d36fcc8a9e4857dff4301870985df51a528", size = 21777, upload-time = "2026-02-27T14:13:01.489Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/b4/1b19567e4c567b796f5c593d89895f3cfae5a38e04f27c6af87618fd0942/google_auth_oauthlib-1.3.0.tar.gz", hash = "sha256:cd39e807ac7229d6b8b9c1e297321d36fcc8a9e4857dff4301870985df51a528", size = 21777 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/56/909fd5632226d3fba31d7aeffd4754410735d49362f5809956fe3e9af344/google_auth_oauthlib-1.3.0-py3-none-any.whl", hash = "sha256:386b3fb85cf4a5b819c6ad23e3128d975216b4cac76324de1d90b128aaf38f29", size = 19308, upload-time = "2026-02-27T14:12:47.865Z" }, + { url = "https://files.pythonhosted.org/packages/2f/56/909fd5632226d3fba31d7aeffd4754410735d49362f5809956fe3e9af344/google_auth_oauthlib-1.3.0-py3-none-any.whl", hash = "sha256:386b3fb85cf4a5b819c6ad23e3128d975216b4cac76324de1d90b128aaf38f29", size = 19308 }, ] [[package]] @@ -2584,9 +2584,9 @@ dependencies = [ { name = "proto-plus" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/f9/208ae25a03f822fcc7f762198cdedaefdbac4f923f72e5c39d3bdbf2ec60/google_cloud_vision-3.13.0.tar.gz", hash = "sha256:680f668d331858a3340eac41b732903d30dc69ed08020ffd1d5ca32580bdf546", size = 592075, upload-time = "2026-03-26T22:18:38.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/f9/208ae25a03f822fcc7f762198cdedaefdbac4f923f72e5c39d3bdbf2ec60/google_cloud_vision-3.13.0.tar.gz", hash = "sha256:680f668d331858a3340eac41b732903d30dc69ed08020ffd1d5ca32580bdf546", size = 592075 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/74/775192dc2a930191e821c5cd841d399576ae7bca4db98ee5cc262ac56de0/google_cloud_vision-3.13.0-py3-none-any.whl", hash = "sha256:f6979e93ad60a7e556b152de2857f7d3b9b740afd022cea1c76548ef80c29b87", size = 543152, upload-time = "2026-03-26T22:13:13.127Z" }, + { url = "https://files.pythonhosted.org/packages/c8/74/775192dc2a930191e821c5cd841d399576ae7bca4db98ee5cc262ac56de0/google_cloud_vision-3.13.0-py3-none-any.whl", hash = "sha256:f6979e93ad60a7e556b152de2857f7d3b9b740afd022cea1c76548ef80c29b87", size = 543152 }, ] [[package]] @@ -2605,9 +2605,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/2c/f059982dbcb658cc535c81bbcbe7e2c040d675f4b563b03cdb01018a4bc3/google_genai-1.68.0.tar.gz", hash = "sha256:ac30c0b8bc630f9372993a97e4a11dae0e36f2e10d7c55eacdca95a9fa14ca96", size = 511285, upload-time = "2026-03-18T01:03:18.243Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/2c/f059982dbcb658cc535c81bbcbe7e2c040d675f4b563b03cdb01018a4bc3/google_genai-1.68.0.tar.gz", hash = "sha256:ac30c0b8bc630f9372993a97e4a11dae0e36f2e10d7c55eacdca95a9fa14ca96", size = 511285 } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/de/7d3ee9c94b74c3578ea4f88d45e8de9405902f857932334d81e89bce3dfa/google_genai-1.68.0-py3-none-any.whl", hash = "sha256:a1bc9919c0e2ea2907d1e319b65471d3d6d58c54822039a249fe1323e4178d15", size = 750912, upload-time = "2026-03-18T01:03:15.983Z" }, + { url = "https://files.pythonhosted.org/packages/84/de/7d3ee9c94b74c3578ea4f88d45e8de9405902f857932334d81e89bce3dfa/google_genai-1.68.0-py3-none-any.whl", hash = "sha256:a1bc9919c0e2ea2907d1e319b65471d3d6d58c54822039a249fe1323e4178d15", size = 750912 }, ] [[package]] @@ -2617,52 +2617,52 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/c0/4a54c386282c13449eca8bbe2ddb518181dc113e78d240458a68856b4d69/googleapis_common_protos-1.73.1.tar.gz", hash = "sha256:13114f0e9d2391756a0194c3a8131974ed7bffb06086569ba193364af59163b6", size = 147506, upload-time = "2026-03-26T22:17:38.451Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/c0/4a54c386282c13449eca8bbe2ddb518181dc113e78d240458a68856b4d69/googleapis_common_protos-1.73.1.tar.gz", hash = "sha256:13114f0e9d2391756a0194c3a8131974ed7bffb06086569ba193364af59163b6", size = 147506 } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/82/fcb6520612bec0c39b973a6c0954b6a0d948aadfe8f7e9487f60ceb8bfa6/googleapis_common_protos-1.73.1-py3-none-any.whl", hash = "sha256:e51f09eb0a43a8602f5a915870972e6b4a394088415c79d79605a46d8e826ee8", size = 297556, upload-time = "2026-03-26T22:15:58.455Z" }, + { url = "https://files.pythonhosted.org/packages/dc/82/fcb6520612bec0c39b973a6c0954b6a0d948aadfe8f7e9487f60ceb8bfa6/googleapis_common_protos-1.73.1-py3-none-any.whl", hash = "sha256:e51f09eb0a43a8602f5a915870972e6b4a394088415c79d79605a46d8e826ee8", size = 297556 }, ] [[package]] name = "greenlet" version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267, upload-time = "2026-02-20T20:54:15.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358, upload-time = "2026-02-20T20:17:43.971Z" }, - { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217, upload-time = "2026-02-20T20:47:31.462Z" }, - { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792, upload-time = "2026-02-20T20:55:58.423Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250, upload-time = "2026-02-20T21:02:46.596Z" }, - { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875, upload-time = "2026-02-20T20:21:01.102Z" }, - { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467, upload-time = "2026-02-20T20:49:33.495Z" }, - { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001, upload-time = "2026-02-20T20:21:09.154Z" }, - { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081, upload-time = "2026-02-20T20:17:28.121Z" }, - { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331, upload-time = "2026-02-20T20:17:23.34Z" }, - { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, - { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, - { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, - { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268, upload-time = "2026-02-20T21:02:48.024Z" }, - { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, - { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, - { url = "https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124", size = 230961, upload-time = "2026-02-20T20:16:58.461Z" }, - { url = "https://files.pythonhosted.org/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327", size = 230221, upload-time = "2026-02-20T20:17:37.152Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650, upload-time = "2026-02-20T20:18:00.783Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295, upload-time = "2026-02-20T20:47:34.036Z" }, - { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163, upload-time = "2026-02-20T20:56:01.295Z" }, - { url = "https://files.pythonhosted.org/packages/cd/ac/85804f74f1ccea31ba518dcc8ee6f14c79f73fe36fa1beba38930806df09/greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9", size = 675371, upload-time = "2026-02-20T21:02:49.664Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160, upload-time = "2026-02-20T20:21:04.015Z" }, - { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181, upload-time = "2026-02-20T20:49:36.052Z" }, - { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713, upload-time = "2026-02-20T20:21:11.684Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ca/2101ca3d9223a1dc125140dbc063644dca76df6ff356531eb27bc267b446/greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492", size = 232034, upload-time = "2026-02-20T20:20:08.186Z" }, - { url = "https://files.pythonhosted.org/packages/f6/4a/ecf894e962a59dea60f04877eea0fd5724618da89f1867b28ee8b91e811f/greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71", size = 231437, upload-time = "2026-02-20T20:18:59.722Z" }, - { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617, upload-time = "2026-02-20T20:19:29.856Z" }, - { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189, upload-time = "2026-02-20T20:47:35.742Z" }, - { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225, upload-time = "2026-02-20T20:56:02.527Z" }, - { url = "https://files.pythonhosted.org/packages/d1/67/8197b7e7e602150938049d8e7f30de1660cfb87e4c8ee349b42b67bdb2e1/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf", size = 666581, upload-time = "2026-02-20T21:02:51.526Z" }, - { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907, upload-time = "2026-02-20T20:21:05.259Z" }, - { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857, upload-time = "2026-02-20T20:49:37.309Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010, upload-time = "2026-02-20T20:21:13.427Z" }, - { url = "https://files.pythonhosted.org/packages/29/4b/45d90626aef8e65336bed690106d1382f7a43665e2249017e9527df8823b/greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a", size = 237086, upload-time = "2026-02-20T20:20:45.786Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ab/1608e5a7578e62113506740b88066bf09888322a311cff602105e619bd87/greenlet-3.3.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:ac8d61d4343b799d1e526db579833d72f23759c71e07181c2d2944e429eb09cd", size = 280358 }, + { url = "https://files.pythonhosted.org/packages/a5/23/0eae412a4ade4e6623ff7626e38998cb9b11e9ff1ebacaa021e4e108ec15/greenlet-3.3.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ceec72030dae6ac0c8ed7591b96b70410a8be370b6a477b1dbc072856ad02bd", size = 601217 }, + { url = "https://files.pythonhosted.org/packages/f8/16/5b1678a9c07098ecb9ab2dd159fafaf12e963293e61ee8d10ecb55273e5e/greenlet-3.3.2-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2a5be83a45ce6188c045bcc44b0ee037d6a518978de9a5d97438548b953a1ac", size = 611792 }, + { url = "https://files.pythonhosted.org/packages/5c/c5/cc09412a29e43406eba18d61c70baa936e299bc27e074e2be3806ed29098/greenlet-3.3.2-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9e21c84035c490506c17002f5c8ab25f980205c3e61ddb3a2a2a2e6c411fcb", size = 626250 }, + { url = "https://files.pythonhosted.org/packages/50/1f/5155f55bd71cabd03765a4aac9ac446be129895271f73872c36ebd4b04b6/greenlet-3.3.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e99d1749147ac21dde49b99c9abffcbc1e2d55c67501465ef0930d6e78e070", size = 613875 }, + { url = "https://files.pythonhosted.org/packages/fc/dd/845f249c3fcd69e32df80cdab059b4be8b766ef5830a3d0aa9d6cad55beb/greenlet-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c956a19350e2c37f2c48b336a3afb4bff120b36076d9d7fb68cb44e05d95b79", size = 1571467 }, + { url = "https://files.pythonhosted.org/packages/2a/50/2649fe21fcc2b56659a452868e695634722a6655ba245d9f77f5656010bf/greenlet-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c6f8ba97d17a1e7d664151284cb3315fc5f8353e75221ed4324f84eb162b395", size = 1640001 }, + { url = "https://files.pythonhosted.org/packages/9b/40/cc802e067d02af8b60b6771cea7d57e21ef5e6659912814babb42b864713/greenlet-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:34308836d8370bddadb41f5a7ce96879b72e2fdfb4e87729330c6ab52376409f", size = 231081 }, + { url = "https://files.pythonhosted.org/packages/58/2e/fe7f36ff1982d6b10a60d5e0740c759259a7d6d2e1dc41da6d96de32fff6/greenlet-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:d3a62fa76a32b462a97198e4c9e99afb9ab375115e74e9a83ce180e7a496f643", size = 230331 }, + { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120 }, + { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238 }, + { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219 }, + { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268 }, + { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774 }, + { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277 }, + { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455 }, + { url = "https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124", size = 230961 }, + { url = "https://files.pythonhosted.org/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327", size = 230221 }, + { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650 }, + { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295 }, + { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163 }, + { url = "https://files.pythonhosted.org/packages/cd/ac/85804f74f1ccea31ba518dcc8ee6f14c79f73fe36fa1beba38930806df09/greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9", size = 675371 }, + { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160 }, + { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181 }, + { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713 }, + { url = "https://files.pythonhosted.org/packages/f3/ca/2101ca3d9223a1dc125140dbc063644dca76df6ff356531eb27bc267b446/greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492", size = 232034 }, + { url = "https://files.pythonhosted.org/packages/f6/4a/ecf894e962a59dea60f04877eea0fd5724618da89f1867b28ee8b91e811f/greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71", size = 231437 }, + { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617 }, + { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189 }, + { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225 }, + { url = "https://files.pythonhosted.org/packages/d1/67/8197b7e7e602150938049d8e7f30de1660cfb87e4c8ee349b42b67bdb2e1/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf", size = 666581 }, + { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907 }, + { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857 }, + { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010 }, + { url = "https://files.pythonhosted.org/packages/29/4b/45d90626aef8e65336bed690106d1382f7a43665e2249017e9527df8823b/greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a", size = 237086 }, ] [[package]] @@ -2673,9 +2673,9 @@ dependencies = [ { name = "griffecli" }, { name = "griffelib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4a/49/eb6d2935e27883af92c930ed40cc4c69bcd32c402be43b8ca4ab20510f67/griffe-2.0.2.tar.gz", hash = "sha256:c5d56326d159f274492e9bf93a9895cec101155d944caa66d0fc4e0c13751b92", size = 293757, upload-time = "2026-03-27T11:34:52.205Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/49/eb6d2935e27883af92c930ed40cc4c69bcd32c402be43b8ca4ab20510f67/griffe-2.0.2.tar.gz", hash = "sha256:c5d56326d159f274492e9bf93a9895cec101155d944caa66d0fc4e0c13751b92", size = 293757 } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/c0/2bb018eecf9a83c68db9cd9fffd9dab25f102ad30ed869451046e46d1187/griffe-2.0.2-py3-none-any.whl", hash = "sha256:2b31816460aee1996af26050a1fc6927a2e5936486856707f55508e4c9b5960b", size = 5141, upload-time = "2026-03-27T11:34:47.721Z" }, + { url = "https://files.pythonhosted.org/packages/94/c0/2bb018eecf9a83c68db9cd9fffd9dab25f102ad30ed869451046e46d1187/griffe-2.0.2-py3-none-any.whl", hash = "sha256:2b31816460aee1996af26050a1fc6927a2e5936486856707f55508e4c9b5960b", size = 5141 }, ] [[package]] @@ -2686,18 +2686,18 @@ dependencies = [ { name = "colorama" }, { name = "griffelib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/e0/6a7d661d71bb043656a109b91d84a42b5342752542074ec83b16a6eb97f0/griffecli-2.0.2.tar.gz", hash = "sha256:40a1ad4181fc39685d025e119ae2c5b669acdc1f19b705fb9bf971f4e6f6dffb", size = 56281, upload-time = "2026-03-27T11:34:50.087Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/e0/6a7d661d71bb043656a109b91d84a42b5342752542074ec83b16a6eb97f0/griffecli-2.0.2.tar.gz", hash = "sha256:40a1ad4181fc39685d025e119ae2c5b669acdc1f19b705fb9bf971f4e6f6dffb", size = 56281 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/e8/90d93356c88ac34c20cb5edffca68138df55ca9bbd1a06eccfbcec8fdbe5/griffecli-2.0.2-py3-none-any.whl", hash = "sha256:0d44d39e59afa81e288a3e1c3bf352cc4fa537483326ac06b8bb6a51fd8303a0", size = 9500, upload-time = "2026-03-27T11:34:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e8/90d93356c88ac34c20cb5edffca68138df55ca9bbd1a06eccfbcec8fdbe5/griffecli-2.0.2-py3-none-any.whl", hash = "sha256:0d44d39e59afa81e288a3e1c3bf352cc4fa537483326ac06b8bb6a51fd8303a0", size = 9500 }, ] [[package]] name = "griffelib" version = "2.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461, upload-time = "2026-03-27T11:34:51.091Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461 } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, + { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357 }, ] [[package]] @@ -2712,9 +2712,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/c7/a2153b639062f59f9bc93a1b5507c0c4a6b654b8a9edbf432ec2f4a62d2d/groq-1.1.2.tar.gz", hash = "sha256:9ec2b5b6a1c4856a8c6c38741353c5ab37472a4e3fded02af783750d849cc988", size = 154033, upload-time = "2026-03-25T23:16:10.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/c7/a2153b639062f59f9bc93a1b5507c0c4a6b654b8a9edbf432ec2f4a62d2d/groq-1.1.2.tar.gz", hash = "sha256:9ec2b5b6a1c4856a8c6c38741353c5ab37472a4e3fded02af783750d849cc988", size = 154033 } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/b0/83e3892a4597a4b8ebf8a662aeaf314765c4c2340516eb1d049b459b24fc/groq-1.1.2-py3-none-any.whl", hash = "sha256:348cb7a674b6aa7105719b533f6fc48fd32b503bc9256924aaed6dc186f778b5", size = 141700, upload-time = "2026-03-25T23:16:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/34/b0/83e3892a4597a4b8ebf8a662aeaf314765c4c2340516eb1d049b459b24fc/groq-1.1.2-py3-none-any.whl", hash = "sha256:348cb7a674b6aa7105719b533f6fc48fd32b503bc9256924aaed6dc186f778b5", size = 141700 }, ] [[package]] @@ -2724,38 +2724,38 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, - { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, - { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, - { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, - { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, - { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, - { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, - { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, - { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" }, - { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" }, - { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" }, - { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" }, - { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" }, - { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" }, - { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, - { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, - { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, - { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, - { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, - { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, - { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, - { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, - { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, - { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, - { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985 }, + { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853 }, + { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766 }, + { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027 }, + { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766 }, + { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161 }, + { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303 }, + { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222 }, + { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123 }, + { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657 }, + { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143 }, + { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926 }, + { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628 }, + { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574 }, + { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639 }, + { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838 }, + { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878 }, + { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412 }, + { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899 }, + { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393 }, + { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591 }, + { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685 }, + { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803 }, + { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206 }, + { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826 }, + { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897 }, + { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404 }, + { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837 }, + { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439 }, + { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852 }, ] [[package]] @@ -2767,18 +2767,18 @@ dependencies = [ { name = "grpcio" }, { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/cd/89ce482a931b543b92cdd9b2888805518c4620e0094409acb8c81dd4610a/grpcio_status-1.78.0.tar.gz", hash = "sha256:a34cfd28101bfea84b5aa0f936b4b423019e9213882907166af6b3bddc59e189", size = 13808, upload-time = "2026-02-06T10:01:48.034Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/cd/89ce482a931b543b92cdd9b2888805518c4620e0094409acb8c81dd4610a/grpcio_status-1.78.0.tar.gz", hash = "sha256:a34cfd28101bfea84b5aa0f936b4b423019e9213882907166af6b3bddc59e189", size = 13808 } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/8a/1241ec22c41028bddd4a052ae9369267b4475265ad0ce7140974548dc3fa/grpcio_status-1.78.0-py3-none-any.whl", hash = "sha256:b492b693d4bf27b47a6c32590701724f1d3b9444b36491878fb71f6208857f34", size = 14523, upload-time = "2026-02-06T10:01:32.584Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/1241ec22c41028bddd4a052ae9369267b4475265ad0ce7140974548dc3fa/grpcio_status-1.78.0-py3-none-any.whl", hash = "sha256:b492b693d4bf27b47a6c32590701724f1d3b9444b36491878fb71f6208857f34", size = 14523 }, ] [[package]] name = "h11" version = "0.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, ] [[package]] @@ -2789,50 +2789,50 @@ dependencies = [ { name = "hpack" }, { name = "hyperframe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026 } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, + { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779 }, ] [[package]] name = "hf-xet" version = "1.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/08/23c84a26716382c89151b5b447b4beb19e3345f3a93d3b73009a71a57ad3/hf_xet-1.4.2.tar.gz", hash = "sha256:b7457b6b482d9e0743bd116363239b1fa904a5e65deede350fbc0c4ea67c71ea", size = 672357, upload-time = "2026-03-13T06:58:51.077Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/08/23c84a26716382c89151b5b447b4beb19e3345f3a93d3b73009a71a57ad3/hf_xet-1.4.2.tar.gz", hash = "sha256:b7457b6b482d9e0743bd116363239b1fa904a5e65deede350fbc0c4ea67c71ea", size = 672357 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/06/e8cf74c3c48e5485c7acc5a990d0d8516cdfb5fdf80f799174f1287cc1b5/hf_xet-1.4.2-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ac8202ae1e664b2c15cdfc7298cbb25e80301ae596d602ef7870099a126fcad4", size = 3796125, upload-time = "2026-03-13T06:58:33.177Z" }, - { url = "https://files.pythonhosted.org/packages/66/d4/b73ebab01cbf60777323b7de9ef05550790451eb5172a220d6b9845385ec/hf_xet-1.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6d2f8ee39fa9fba9af929f8c0d0482f8ee6e209179ad14a909b6ad78ffcb7c81", size = 3555985, upload-time = "2026-03-13T06:58:31.797Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e7/ded6d1bd041c3f2bca9e913a0091adfe32371988e047dd3a68a2463c15a2/hf_xet-1.4.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4642a6cf249c09da8c1f87fe50b24b2a3450b235bf8adb55700b52f0ea6e2eb6", size = 4212085, upload-time = "2026-03-13T06:58:24.323Z" }, - { url = "https://files.pythonhosted.org/packages/97/c1/a0a44d1f98934f7bdf17f7a915b934f9fca44bb826628c553589900f6df8/hf_xet-1.4.2-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:769431385e746c92dc05492dde6f687d304584b89c33d79def8367ace06cb555", size = 3988266, upload-time = "2026-03-13T06:58:22.887Z" }, - { url = "https://files.pythonhosted.org/packages/7a/82/be713b439060e7d1f1d93543c8053d4ef2fe7e6922c5b31642eaa26f3c4b/hf_xet-1.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c9dd1c1bc4cc56168f81939b0e05b4c36dd2d28c13dc1364b17af89aa0082496", size = 4188513, upload-time = "2026-03-13T06:58:40.858Z" }, - { url = "https://files.pythonhosted.org/packages/21/a6/cbd4188b22abd80ebd0edbb2b3e87f2633e958983519980815fb8314eae5/hf_xet-1.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fca58a2ae4e6f6755cc971ac6fcdf777ea9284d7e540e350bb000813b9a3008d", size = 4428287, upload-time = "2026-03-13T06:58:42.601Z" }, - { url = "https://files.pythonhosted.org/packages/b2/4e/84e45b25e2e3e903ed3db68d7eafa96dae9a1d1f6d0e7fc85120347a852f/hf_xet-1.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:163aab46854ccae0ab6a786f8edecbbfbaa38fcaa0184db6feceebf7000c93c0", size = 3665574, upload-time = "2026-03-13T06:58:53.881Z" }, - { url = "https://files.pythonhosted.org/packages/ee/71/c5ac2b9a7ae39c14e91973035286e73911c31980fe44e7b1d03730c00adc/hf_xet-1.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:09b138422ecbe50fd0c84d4da5ff537d27d487d3607183cd10e3e53f05188e82", size = 3528760, upload-time = "2026-03-13T06:58:52.187Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0f/fcd2504015eab26358d8f0f232a1aed6b8d363a011adef83fe130bff88f7/hf_xet-1.4.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:949dcf88b484bb9d9276ca83f6599e4aa03d493c08fc168c124ad10b2e6f75d7", size = 3796493, upload-time = "2026-03-13T06:58:39.267Z" }, - { url = "https://files.pythonhosted.org/packages/82/56/19c25105ff81731ca6d55a188b5de2aa99d7a2644c7aa9de1810d5d3b726/hf_xet-1.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:41659966020d59eb9559c57de2cde8128b706a26a64c60f0531fa2318f409418", size = 3555797, upload-time = "2026-03-13T06:58:37.546Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/8933c073186849b5e06762aa89847991d913d10a95d1603eb7f2c3834086/hf_xet-1.4.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c588e21d80010119458dd5d02a69093f0d115d84e3467efe71ffb2c67c19146", size = 4212127, upload-time = "2026-03-13T06:58:30.539Z" }, - { url = "https://files.pythonhosted.org/packages/eb/01/f89ebba4e369b4ed699dcb60d3152753870996f41c6d22d3d7cac01310e1/hf_xet-1.4.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a296744d771a8621ad1d50c098d7ab975d599800dae6d48528ba3944e5001ba0", size = 3987788, upload-time = "2026-03-13T06:58:29.139Z" }, - { url = "https://files.pythonhosted.org/packages/84/4d/8a53e5ffbc2cc33bbf755382ac1552c6d9af13f623ed125fe67cc3e6772f/hf_xet-1.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f563f7efe49588b7d0629d18d36f46d1658fe7e08dce3fa3d6526e1c98315e2d", size = 4188315, upload-time = "2026-03-13T06:58:48.017Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b8/b7a1c1b5592254bd67050632ebbc1b42cc48588bf4757cb03c2ef87e704a/hf_xet-1.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5b2e0132c56d7ee1bf55bdb638c4b62e7106f6ac74f0b786fed499d5548c5570", size = 4428306, upload-time = "2026-03-13T06:58:49.502Z" }, - { url = "https://files.pythonhosted.org/packages/a0/0c/40779e45b20e11c7c5821a94135e0207080d6b3d76e7b78ccb413c6f839b/hf_xet-1.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:2f45c712c2fa1215713db10df6ac84b49d0e1c393465440e9cb1de73ecf7bbf6", size = 3665826, upload-time = "2026-03-13T06:58:59.88Z" }, - { url = "https://files.pythonhosted.org/packages/51/4c/e2688c8ad1760d7c30f7c429c79f35f825932581bc7c9ec811436d2f21a0/hf_xet-1.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6d53df40616f7168abfccff100d232e9d460583b9d86fa4912c24845f192f2b8", size = 3529113, upload-time = "2026-03-13T06:58:58.491Z" }, - { url = "https://files.pythonhosted.org/packages/b4/86/b40b83a2ff03ef05c4478d2672b1fc2b9683ff870e2b25f4f3af240f2e7b/hf_xet-1.4.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:71f02d6e4cdd07f344f6844845d78518cc7186bd2bc52d37c3b73dc26a3b0bc5", size = 3800339, upload-time = "2026-03-13T06:58:36.245Z" }, - { url = "https://files.pythonhosted.org/packages/64/2e/af4475c32b4378b0e92a587adb1aa3ec53e3450fd3e5fe0372a874531c00/hf_xet-1.4.2-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e9b38d876e94d4bdcf650778d6ebbaa791dd28de08db9736c43faff06ede1b5a", size = 3559664, upload-time = "2026-03-13T06:58:34.787Z" }, - { url = "https://files.pythonhosted.org/packages/3c/4c/781267da3188db679e601de18112021a5cb16506fe86b246e22c5401a9c4/hf_xet-1.4.2-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:77e8c180b7ef12d8a96739a4e1e558847002afe9ea63b6f6358b2271a8bdda1c", size = 4217422, upload-time = "2026-03-13T06:58:27.472Z" }, - { url = "https://files.pythonhosted.org/packages/68/47/d6cf4a39ecf6c7705f887a46f6ef5c8455b44ad9eb0d391aa7e8a2ff7fea/hf_xet-1.4.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c3b3c6a882016b94b6c210957502ff7877802d0dbda8ad142c8595db8b944271", size = 3992847, upload-time = "2026-03-13T06:58:25.989Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ef/e80815061abff54697239803948abc665c6b1d237102c174f4f7a9a5ffc5/hf_xet-1.4.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d9a634cc929cfbaf2e1a50c0e532ae8c78fa98618426769480c58501e8c8ac2", size = 4193843, upload-time = "2026-03-13T06:58:44.59Z" }, - { url = "https://files.pythonhosted.org/packages/54/75/07f6aa680575d9646c4167db6407c41340cbe2357f5654c4e72a1b01ca14/hf_xet-1.4.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6b0932eb8b10317ea78b7da6bab172b17be03bbcd7809383d8d5abd6a2233e04", size = 4432751, upload-time = "2026-03-13T06:58:46.533Z" }, - { url = "https://files.pythonhosted.org/packages/cd/71/193eabd7e7d4b903c4aa983a215509c6114915a5a237525ec562baddb868/hf_xet-1.4.2-cp37-abi3-win_amd64.whl", hash = "sha256:ad185719fb2e8ac26f88c8100562dbf9dbdcc3d9d2add00faa94b5f106aea53f", size = 3671149, upload-time = "2026-03-13T06:58:57.07Z" }, - { url = "https://files.pythonhosted.org/packages/b4/7e/ccf239da366b37ba7f0b36095450efae4a64980bdc7ec2f51354205fdf39/hf_xet-1.4.2-cp37-abi3-win_arm64.whl", hash = "sha256:32c012286b581f783653e718c1862aea5b9eb140631685bb0c5e7012c8719a87", size = 3533426, upload-time = "2026-03-13T06:58:55.46Z" }, + { url = "https://files.pythonhosted.org/packages/18/06/e8cf74c3c48e5485c7acc5a990d0d8516cdfb5fdf80f799174f1287cc1b5/hf_xet-1.4.2-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ac8202ae1e664b2c15cdfc7298cbb25e80301ae596d602ef7870099a126fcad4", size = 3796125 }, + { url = "https://files.pythonhosted.org/packages/66/d4/b73ebab01cbf60777323b7de9ef05550790451eb5172a220d6b9845385ec/hf_xet-1.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6d2f8ee39fa9fba9af929f8c0d0482f8ee6e209179ad14a909b6ad78ffcb7c81", size = 3555985 }, + { url = "https://files.pythonhosted.org/packages/ff/e7/ded6d1bd041c3f2bca9e913a0091adfe32371988e047dd3a68a2463c15a2/hf_xet-1.4.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4642a6cf249c09da8c1f87fe50b24b2a3450b235bf8adb55700b52f0ea6e2eb6", size = 4212085 }, + { url = "https://files.pythonhosted.org/packages/97/c1/a0a44d1f98934f7bdf17f7a915b934f9fca44bb826628c553589900f6df8/hf_xet-1.4.2-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:769431385e746c92dc05492dde6f687d304584b89c33d79def8367ace06cb555", size = 3988266 }, + { url = "https://files.pythonhosted.org/packages/7a/82/be713b439060e7d1f1d93543c8053d4ef2fe7e6922c5b31642eaa26f3c4b/hf_xet-1.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c9dd1c1bc4cc56168f81939b0e05b4c36dd2d28c13dc1364b17af89aa0082496", size = 4188513 }, + { url = "https://files.pythonhosted.org/packages/21/a6/cbd4188b22abd80ebd0edbb2b3e87f2633e958983519980815fb8314eae5/hf_xet-1.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fca58a2ae4e6f6755cc971ac6fcdf777ea9284d7e540e350bb000813b9a3008d", size = 4428287 }, + { url = "https://files.pythonhosted.org/packages/b2/4e/84e45b25e2e3e903ed3db68d7eafa96dae9a1d1f6d0e7fc85120347a852f/hf_xet-1.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:163aab46854ccae0ab6a786f8edecbbfbaa38fcaa0184db6feceebf7000c93c0", size = 3665574 }, + { url = "https://files.pythonhosted.org/packages/ee/71/c5ac2b9a7ae39c14e91973035286e73911c31980fe44e7b1d03730c00adc/hf_xet-1.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:09b138422ecbe50fd0c84d4da5ff537d27d487d3607183cd10e3e53f05188e82", size = 3528760 }, + { url = "https://files.pythonhosted.org/packages/1e/0f/fcd2504015eab26358d8f0f232a1aed6b8d363a011adef83fe130bff88f7/hf_xet-1.4.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:949dcf88b484bb9d9276ca83f6599e4aa03d493c08fc168c124ad10b2e6f75d7", size = 3796493 }, + { url = "https://files.pythonhosted.org/packages/82/56/19c25105ff81731ca6d55a188b5de2aa99d7a2644c7aa9de1810d5d3b726/hf_xet-1.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:41659966020d59eb9559c57de2cde8128b706a26a64c60f0531fa2318f409418", size = 3555797 }, + { url = "https://files.pythonhosted.org/packages/bf/e3/8933c073186849b5e06762aa89847991d913d10a95d1603eb7f2c3834086/hf_xet-1.4.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c588e21d80010119458dd5d02a69093f0d115d84e3467efe71ffb2c67c19146", size = 4212127 }, + { url = "https://files.pythonhosted.org/packages/eb/01/f89ebba4e369b4ed699dcb60d3152753870996f41c6d22d3d7cac01310e1/hf_xet-1.4.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a296744d771a8621ad1d50c098d7ab975d599800dae6d48528ba3944e5001ba0", size = 3987788 }, + { url = "https://files.pythonhosted.org/packages/84/4d/8a53e5ffbc2cc33bbf755382ac1552c6d9af13f623ed125fe67cc3e6772f/hf_xet-1.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f563f7efe49588b7d0629d18d36f46d1658fe7e08dce3fa3d6526e1c98315e2d", size = 4188315 }, + { url = "https://files.pythonhosted.org/packages/d1/b8/b7a1c1b5592254bd67050632ebbc1b42cc48588bf4757cb03c2ef87e704a/hf_xet-1.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5b2e0132c56d7ee1bf55bdb638c4b62e7106f6ac74f0b786fed499d5548c5570", size = 4428306 }, + { url = "https://files.pythonhosted.org/packages/a0/0c/40779e45b20e11c7c5821a94135e0207080d6b3d76e7b78ccb413c6f839b/hf_xet-1.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:2f45c712c2fa1215713db10df6ac84b49d0e1c393465440e9cb1de73ecf7bbf6", size = 3665826 }, + { url = "https://files.pythonhosted.org/packages/51/4c/e2688c8ad1760d7c30f7c429c79f35f825932581bc7c9ec811436d2f21a0/hf_xet-1.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6d53df40616f7168abfccff100d232e9d460583b9d86fa4912c24845f192f2b8", size = 3529113 }, + { url = "https://files.pythonhosted.org/packages/b4/86/b40b83a2ff03ef05c4478d2672b1fc2b9683ff870e2b25f4f3af240f2e7b/hf_xet-1.4.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:71f02d6e4cdd07f344f6844845d78518cc7186bd2bc52d37c3b73dc26a3b0bc5", size = 3800339 }, + { url = "https://files.pythonhosted.org/packages/64/2e/af4475c32b4378b0e92a587adb1aa3ec53e3450fd3e5fe0372a874531c00/hf_xet-1.4.2-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e9b38d876e94d4bdcf650778d6ebbaa791dd28de08db9736c43faff06ede1b5a", size = 3559664 }, + { url = "https://files.pythonhosted.org/packages/3c/4c/781267da3188db679e601de18112021a5cb16506fe86b246e22c5401a9c4/hf_xet-1.4.2-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:77e8c180b7ef12d8a96739a4e1e558847002afe9ea63b6f6358b2271a8bdda1c", size = 4217422 }, + { url = "https://files.pythonhosted.org/packages/68/47/d6cf4a39ecf6c7705f887a46f6ef5c8455b44ad9eb0d391aa7e8a2ff7fea/hf_xet-1.4.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c3b3c6a882016b94b6c210957502ff7877802d0dbda8ad142c8595db8b944271", size = 3992847 }, + { url = "https://files.pythonhosted.org/packages/2d/ef/e80815061abff54697239803948abc665c6b1d237102c174f4f7a9a5ffc5/hf_xet-1.4.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d9a634cc929cfbaf2e1a50c0e532ae8c78fa98618426769480c58501e8c8ac2", size = 4193843 }, + { url = "https://files.pythonhosted.org/packages/54/75/07f6aa680575d9646c4167db6407c41340cbe2357f5654c4e72a1b01ca14/hf_xet-1.4.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6b0932eb8b10317ea78b7da6bab172b17be03bbcd7809383d8d5abd6a2233e04", size = 4432751 }, + { url = "https://files.pythonhosted.org/packages/cd/71/193eabd7e7d4b903c4aa983a215509c6114915a5a237525ec562baddb868/hf_xet-1.4.2-cp37-abi3-win_amd64.whl", hash = "sha256:ad185719fb2e8ac26f88c8100562dbf9dbdcc3d9d2add00faa94b5f106aea53f", size = 3671149 }, + { url = "https://files.pythonhosted.org/packages/b4/7e/ccf239da366b37ba7f0b36095450efae4a64980bdc7ec2f51354205fdf39/hf_xet-1.4.2-cp37-abi3-win_arm64.whl", hash = "sha256:32c012286b581f783653e718c1862aea5b9eb140631685bb0c5e7012c8719a87", size = 3533426 }, ] [[package]] name = "hpack" version = "4.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 }, ] [[package]] @@ -2843,9 +2843,9 @@ dependencies = [ { name = "six" }, { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215, upload-time = "2020-06-22T23:32:38.834Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/b6/b55c3f49042f1df3dcd422b7f224f939892ee94f22abcf503a9b7339eaf2/html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f", size = 272215 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173, upload-time = "2020-06-22T23:32:36.781Z" }, + { url = "https://files.pythonhosted.org/packages/6c/dd/a834df6482147d48e225a49515aabc28974ad5a4ca3215c18a882565b028/html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", size = 112173 }, ] [[package]] @@ -2859,9 +2859,9 @@ dependencies = [ { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/10/ead9dabc999f353c3aa5d0dc0835b1e355215a5ecb489a7f4ef2ddad5e33/htmldate-1.9.4.tar.gz", hash = "sha256:1129063e02dd0354b74264de71e950c0c3fcee191178321418ccad2074cc8ed0", size = 44690, upload-time = "2025-11-04T17:46:44.983Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/10/ead9dabc999f353c3aa5d0dc0835b1e355215a5ecb489a7f4ef2ddad5e33/htmldate-1.9.4.tar.gz", hash = "sha256:1129063e02dd0354b74264de71e950c0c3fcee191178321418ccad2074cc8ed0", size = 44690 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/bd/adfcdaaad5805c0c5156aeefd64c1e868c05e9c1cd6fd21751f168cd88c7/htmldate-1.9.4-py3-none-any.whl", hash = "sha256:1b94bcc4e08232a5b692159903acf95548b6a7492dddca5bb123d89d6325921c", size = 31558, upload-time = "2025-11-04T17:46:43.258Z" }, + { url = "https://files.pythonhosted.org/packages/a1/bd/adfcdaaad5805c0c5156aeefd64c1e868c05e9c1cd6fd21751f168cd88c7/htmldate-1.9.4-py3-none-any.whl", hash = "sha256:1b94bcc4e08232a5b692159903acf95548b6a7492dddca5bb123d89d6325921c", size = 31558 }, ] [[package]] @@ -2872,9 +2872,9 @@ dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, ] [[package]] @@ -2884,38 +2884,38 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyparsing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c1/1f/e86365613582c027dda5ddb64e1010e57a3d53e99ab8a72093fa13d565ec/httplib2-0.31.2.tar.gz", hash = "sha256:385e0869d7397484f4eab426197a4c020b606edd43372492337c0b4010ae5d24", size = 250800, upload-time = "2026-01-23T11:04:44.165Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/1f/e86365613582c027dda5ddb64e1010e57a3d53e99ab8a72093fa13d565ec/httplib2-0.31.2.tar.gz", hash = "sha256:385e0869d7397484f4eab426197a4c020b606edd43372492337c0b4010ae5d24", size = 250800 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/90/fd509079dfcab01102c0fdd87f3a9506894bc70afcf9e9785ef6b2b3aff6/httplib2-0.31.2-py3-none-any.whl", hash = "sha256:dbf0c2fa3862acf3c55c078ea9c0bc4481d7dc5117cae71be9514912cf9f8349", size = 91099, upload-time = "2026-01-23T11:04:42.78Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/fd509079dfcab01102c0fdd87f3a9506894bc70afcf9e9785ef6b2b3aff6/httplib2-0.31.2-py3-none-any.whl", hash = "sha256:dbf0c2fa3862acf3c55c078ea9c0bc4481d7dc5117cae71be9514912cf9f8349", size = 91099 }, ] [[package]] name = "httptools" version = "0.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961 } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" }, - { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" }, - { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" }, - { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" }, - { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" }, - { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694, upload-time = "2025-10-10T03:54:45.923Z" }, - { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" }, - { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" }, - { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" }, - { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" }, - { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" }, - { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" }, - { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743, upload-time = "2025-10-10T03:54:53.448Z" }, - { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, - { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, - { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, - { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, - { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, - { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, - { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" }, + { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280 }, + { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004 }, + { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655 }, + { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440 }, + { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186 }, + { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192 }, + { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694 }, + { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889 }, + { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180 }, + { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596 }, + { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268 }, + { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517 }, + { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337 }, + { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743 }, + { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619 }, + { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714 }, + { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909 }, + { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831 }, + { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631 }, + { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910 }, + { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205 }, ] [[package]] @@ -2928,9 +2928,9 @@ dependencies = [ { name = "httpcore" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, ] [package.optional-dependencies] @@ -2945,18 +2945,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/07/db4ad128da3926be22eec586aa87dafd8840c9eb03fe88505fbed016b5c6/httpx_oauth-0.16.1.tar.gz", hash = "sha256:7402f061f860abc092ea4f5c90acfc576a40bbb79633c1d2920f1ca282c296ee", size = 44148, upload-time = "2024-12-20T07:23:02.589Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/07/db4ad128da3926be22eec586aa87dafd8840c9eb03fe88505fbed016b5c6/httpx_oauth-0.16.1.tar.gz", hash = "sha256:7402f061f860abc092ea4f5c90acfc576a40bbb79633c1d2920f1ca282c296ee", size = 44148 } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/4b/2b81e876abf77b4af3372aff731f4f6722840ebc7dcfd85778eaba271733/httpx_oauth-0.16.1-py3-none-any.whl", hash = "sha256:2fcad82f80f28d0473a0fc4b4eda223dc952050af7e3a8c8781342d850f09fb5", size = 38056, upload-time = "2024-12-20T07:23:00.394Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/2b81e876abf77b4af3372aff731f4f6722840ebc7dcfd85778eaba271733/httpx_oauth-0.16.1-py3-none-any.whl", hash = "sha256:2fcad82f80f28d0473a0fc4b4eda223dc952050af7e3a8c8781342d850f09fb5", size = 38056 }, ] [[package]] name = "httpx-sse" version = "0.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, + { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960 }, ] [[package]] @@ -2973,27 +2973,27 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782, upload-time = "2026-02-06T09:24:13.098Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" }, + { url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395 }, ] [[package]] name = "humanize" version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/66/a3921783d54be8a6870ac4ccffcd15c4dc0dd7fcce51c6d63b8c63935276/humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10", size = 83599, upload-time = "2025-12-20T20:16:13.19Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/66/a3921783d54be8a6870ac4ccffcd15c4dc0dd7fcce51c6d63b8c63935276/humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10", size = 83599 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769", size = 132203, upload-time = "2025-12-20T20:16:11.67Z" }, + { url = "https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769", size = 132203 }, ] [[package]] name = "hyperframe" version = "6.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 }, ] [[package]] @@ -3003,18 +3003,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088, upload-time = "2026-02-04T16:19:41.26Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088 } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl", hash = "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca", size = 14689, upload-time = "2026-02-04T16:19:40.051Z" }, + { url = "https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl", hash = "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca", size = 14689 }, ] [[package]] name = "idna" version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008 }, ] [[package]] @@ -3024,45 +3024,45 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304, upload-time = "2024-09-11T14:56:08.937Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514, upload-time = "2024-09-11T14:56:07.019Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514 }, ] [[package]] name = "importlib-resources" version = "6.5.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461 }, ] [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, ] [[package]] name = "installer" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/18/ceeb4e3ab3aa54495775775b38ae42b10a92f42ce42dfa44da684289b8c8/installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631", size = 474349, upload-time = "2023-03-17T20:39:38.871Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/18/ceeb4e3ab3aa54495775775b38ae42b10a92f42ce42dfa44da684289b8c8/installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631", size = 474349 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", size = 453838, upload-time = "2023-03-17T20:39:36.219Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", size = 453838 }, ] [[package]] name = "isodate" version = "0.7.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705 } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320 }, ] [[package]] @@ -3072,18 +3072,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "more-itertools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777 }, ] [[package]] name = "jaraco-context" version = "6.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, + { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871 }, ] [[package]] @@ -3093,18 +3093,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "more-itertools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481 }, ] [[package]] name = "jeepney" version = "0.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010 }, ] [[package]] @@ -3114,95 +3114,91 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, ] [[package]] name = "jiter" version = "0.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" }, - { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" }, - { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" }, - { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" }, - { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" }, - { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" }, - { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" }, - { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" }, - { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" }, - { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" }, - { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" }, - { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" }, - { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" }, - { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" }, - { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" }, - { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" }, - { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" }, - { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" }, - { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" }, - { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" }, - { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" }, - { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" }, - { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" }, - { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" }, - { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" }, - { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" }, - { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" }, - { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" }, - { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" }, - { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" }, - { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" }, - { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" }, - { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" }, - { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" }, - { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" }, - { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" }, - { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" }, - { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" }, - { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" }, - { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" }, - { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" }, - { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" }, - { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" }, - { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" }, - { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" }, - { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, - { url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" }, - { url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" }, - { url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" }, - { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, + { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958 }, + { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597 }, + { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821 }, + { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163 }, + { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709 }, + { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480 }, + { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735 }, + { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814 }, + { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990 }, + { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021 }, + { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024 }, + { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424 }, + { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818 }, + { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897 }, + { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507 }, + { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560 }, + { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232 }, + { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727 }, + { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799 }, + { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120 }, + { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664 }, + { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543 }, + { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262 }, + { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630 }, + { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602 }, + { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939 }, + { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616 }, + { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850 }, + { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551 }, + { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950 }, + { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852 }, + { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804 }, + { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787 }, + { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880 }, + { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702 }, + { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319 }, + { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289 }, + { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165 }, + { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634 }, + { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933 }, + { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842 }, + { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108 }, + { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027 }, + { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199 }, + { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423 }, + { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438 }, + { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774 }, + { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238 }, + { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892 }, + { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309 }, + { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607 }, + { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986 }, + { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756 }, + { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196 }, + { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215 }, + { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152 }, ] [[package]] name = "jmespath" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377 } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419 }, ] [[package]] name = "joblib" version = "1.5.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071 }, ] [[package]] @@ -3212,9 +3208,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/d8/423895b918706c80db1cee679c13fbe810200b9a9d9a9442c7a58d35c3f2/json_schema_to_pydantic-0.4.11.tar.gz", hash = "sha256:35448ed711a28dd33396b095c8492939b4925aa30eb31942e9b8e08d04279465", size = 56597, upload-time = "2026-03-09T20:53:55.692Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/d8/423895b918706c80db1cee679c13fbe810200b9a9d9a9442c7a58d35c3f2/json_schema_to_pydantic-0.4.11.tar.gz", hash = "sha256:35448ed711a28dd33396b095c8492939b4925aa30eb31942e9b8e08d04279465", size = 56597 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/64/7cfeb8c6d2a5e73e0f8d732032aa62be9a7724c04beb461d677de0b4beb3/json_schema_to_pydantic-0.4.11-py3-none-any.whl", hash = "sha256:da2ccc39d070ee03dbcf0517d16720e3e33f7aa8d61257ace09af8c51bd46348", size = 17842, upload-time = "2026-03-09T20:53:54.576Z" }, + { url = "https://files.pythonhosted.org/packages/f3/64/7cfeb8c6d2a5e73e0f8d732032aa62be9a7724c04beb461d677de0b4beb3/json_schema_to_pydantic-0.4.11-py3-none-any.whl", hash = "sha256:da2ccc39d070ee03dbcf0517d16720e3e33f7aa8d61257ace09af8c51bd46348", size = 17842 }, ] [[package]] @@ -3224,9 +3220,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/87/bcda8e46c88d0e34cad2f09ee2d0c7f5957bccdb9791b0b934ec84d84be4/jsonlines-4.0.0.tar.gz", hash = "sha256:0c6d2c09117550c089995247f605ae4cf77dd1533041d366351f6f298822ea74", size = 11359, upload-time = "2023-09-01T12:34:44.187Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/87/bcda8e46c88d0e34cad2f09ee2d0c7f5957bccdb9791b0b934ec84d84be4/jsonlines-4.0.0.tar.gz", hash = "sha256:0c6d2c09117550c089995247f605ae4cf77dd1533041d366351f6f298822ea74", size = 11359 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl", hash = "sha256:185b334ff2ca5a91362993f42e83588a360cf95ce4b71a73548502bda52a7c55", size = 8701, upload-time = "2023-09-01T12:34:42.563Z" }, + { url = "https://files.pythonhosted.org/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl", hash = "sha256:185b334ff2ca5a91362993f42e83588a360cf95ce4b71a73548502bda52a7c55", size = 8701 }, ] [[package]] @@ -3236,27 +3232,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpointer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699 } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898 }, ] [[package]] name = "jsonpointer" version = "3.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659 }, ] [[package]] name = "jsonref" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425 }, ] [[package]] @@ -3269,9 +3265,9 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778, upload-time = "2024-07-08T18:40:05.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462, upload-time = "2024-07-08T18:40:00.165Z" }, + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, ] [[package]] @@ -3281,9 +3277,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855 } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437 }, ] [[package]] @@ -3293,9 +3289,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lxml", extra = ["html-clean"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/f3/45890c1b314f0d04e19c1c83d534e611513150939a7cf039664d9ab1e649/justext-3.0.2.tar.gz", hash = "sha256:13496a450c44c4cd5b5a75a5efcd9996066d2a189794ea99a49949685a0beb05", size = 828521, upload-time = "2025-02-25T20:21:49.934Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/f3/45890c1b314f0d04e19c1c83d534e611513150939a7cf039664d9ab1e649/justext-3.0.2.tar.gz", hash = "sha256:13496a450c44c4cd5b5a75a5efcd9996066d2a189794ea99a49949685a0beb05", size = 828521 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/ac/52f4e86d1924a7fc05af3aeb34488570eccc39b4af90530dd6acecdf16b5/justext-3.0.2-py2.py3-none-any.whl", hash = "sha256:62b1c562b15c3c6265e121cc070874243a443bfd53060e869393f09d6b6cc9a7", size = 837940, upload-time = "2025-02-25T20:21:44.179Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ac/52f4e86d1924a7fc05af3aeb34488570eccc39b4af90530dd6acecdf16b5/justext-3.0.2-py2.py3-none-any.whl", hash = "sha256:62b1c562b15c3c6265e121cc070874243a443bfd53060e869393f09d6b6cc9a7", size = 837940 }, ] [[package]] @@ -3310,95 +3306,91 @@ dependencies = [ { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "secretstorage", marker = "sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516 } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160 }, ] [[package]] name = "kiwisolver" version = "1.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, - { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, - { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, - { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, - { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, - { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, - { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, - { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, - { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, - { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, - { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, - { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, - { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, - { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, - { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, - { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, - { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, - { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, - { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, - { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, - { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, - { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, - { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, - { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, - { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, - { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, - { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, - { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, - { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, - { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, - { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, - { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, - { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, - { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, - { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, - { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, - { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, - { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, - { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, - { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, - { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, - { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, - { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, - { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, - { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, - { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, - { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, - { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, - { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, - { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, - { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, - { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, - { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, - { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, - { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, - { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, - { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, - { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, - { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, - { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, - { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, - { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, - { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, - { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, - { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158 }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388 }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068 }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934 }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537 }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685 }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024 }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241 }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742 }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966 }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417 }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238 }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947 }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569 }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997 }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166 }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395 }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065 }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903 }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751 }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793 }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041 }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292 }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865 }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369 }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989 }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645 }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237 }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573 }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998 }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700 }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537 }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514 }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848 }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542 }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447 }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918 }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856 }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580 }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018 }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804 }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482 }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328 }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410 }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231 }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489 }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063 }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913 }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782 }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815 }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925 }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322 }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857 }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376 }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549 }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680 }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905 }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086 }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577 }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794 }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646 }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511 }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858 }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539 }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310 }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244 }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154 }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377 }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288 }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158 }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260 }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403 }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687 }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032 }, ] [[package]] @@ -3413,9 +3405,9 @@ dependencies = [ { name = "torch" }, { name = "transformers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e8/48/88b8cdf28b068d070195c2817175549dee48e7682e3ab8994bee5f69217e/kokoro-0.9.4.tar.gz", hash = "sha256:fbf633262797f8cf46fdac3315cf9cade67dc8b762c0feccf334892772fb9ac4", size = 26215928, upload-time = "2025-04-05T22:01:35.294Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/48/88b8cdf28b068d070195c2817175549dee48e7682e3ab8994bee5f69217e/kokoro-0.9.4.tar.gz", hash = "sha256:fbf633262797f8cf46fdac3315cf9cade67dc8b762c0feccf334892772fb9ac4", size = 26215928 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/cc/75f41633c75224ba820a4533163bc8b070b6bf25416014074c63284c2d4e/kokoro-0.9.4-py3-none-any.whl", hash = "sha256:a129dc6364a286bd6a92c396e9862459d3d3e45f2c15596ed5a94dcee5789efd", size = 32592, upload-time = "2025-04-05T22:01:23.018Z" }, + { url = "https://files.pythonhosted.org/packages/ea/cc/75f41633c75224ba820a4533163bc8b070b6bf25416014074c63284c2d4e/kokoro-0.9.4-py3-none-any.whl", hash = "sha256:a129dc6364a286bd6a92c396e9862459d3d3e45f2c15596ed5a94dcee5789efd", size = 32592 }, ] [[package]] @@ -3428,9 +3420,9 @@ dependencies = [ { name = "tzdata" }, { name = "vine" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b6/a5/607e533ed6c83ae1a696969b8e1c137dfebd5759a2e9682e26ff1b97740b/kombu-5.6.2.tar.gz", hash = "sha256:8060497058066c6f5aed7c26d7cd0d3b574990b09de842a8c5aaed0b92cc5a55", size = 472594, upload-time = "2025-12-29T20:30:07.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/a5/607e533ed6c83ae1a696969b8e1c137dfebd5759a2e9682e26ff1b97740b/kombu-5.6.2.tar.gz", hash = "sha256:8060497058066c6f5aed7c26d7cd0d3b574990b09de842a8c5aaed0b92cc5a55", size = 472594 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/0f/834427d8c03ff1d7e867d3db3d176470c64871753252b21b4f4897d1fa45/kombu-5.6.2-py3-none-any.whl", hash = "sha256:efcfc559da324d41d61ca311b0c64965ea35b4c55cc04ee36e55386145dace93", size = 214219, upload-time = "2025-12-29T20:30:05.74Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0f/834427d8c03ff1d7e867d3db3d176470c64871753252b21b4f4897d1fa45/kombu-5.6.2-py3-none-any.whl", hash = "sha256:efcfc559da324d41d61ca311b0c64965ea35b4c55cc04ee36e55386145dace93", size = 214219 }, ] [package.optional-dependencies] @@ -3453,9 +3445,9 @@ dependencies = [ { name = "urllib3" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/8f/85bf51ad4150f64e8c665daf0d9dfe9787ae92005efb9a4d1cba592bd79d/kubernetes-35.0.0.tar.gz", hash = "sha256:3d00d344944239821458b9efd484d6df9f011da367ecb155dadf9513f05f09ee", size = 1094642, upload-time = "2026-01-16T01:05:27.76Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/8f/85bf51ad4150f64e8c665daf0d9dfe9787ae92005efb9a4d1cba592bd79d/kubernetes-35.0.0.tar.gz", hash = "sha256:3d00d344944239821458b9efd484d6df9f011da367ecb155dadf9513f05f09ee", size = 1094642 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/70/05b685ea2dffcb2adbf3cdcea5d8865b7bc66f67249084cf845012a0ff13/kubernetes-35.0.0-py2.py3-none-any.whl", hash = "sha256:39e2b33b46e5834ef6c3985ebfe2047ab39135d41de51ce7641a7ca5b372a13d", size = 2017602, upload-time = "2026-01-16T01:05:25.991Z" }, + { url = "https://files.pythonhosted.org/packages/0c/70/05b685ea2dffcb2adbf3cdcea5d8865b7bc66f67249084cf845012a0ff13/kubernetes-35.0.0-py2.py3-none-any.whl", hash = "sha256:39e2b33b46e5834ef6c3985ebfe2047ab39135d41de51ce7641a7ca5b372a13d", size = 2017602 }, ] [[package]] @@ -3467,9 +3459,9 @@ dependencies = [ { name = "langgraph" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/e5/56fdeedaa0ef1be3c53721d382d9e21c63930179567361610ea6102c04ea/langchain-1.2.13.tar.gz", hash = "sha256:d566ef67c8287e7f2e2df3c99bf3953a6beefd2a75a97fe56ecce905e21f3ef4", size = 573819, upload-time = "2026-03-19T17:16:07.641Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e5/56fdeedaa0ef1be3c53721d382d9e21c63930179567361610ea6102c04ea/langchain-1.2.13.tar.gz", hash = "sha256:d566ef67c8287e7f2e2df3c99bf3953a6beefd2a75a97fe56ecce905e21f3ef4", size = 573819 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/1d/a509af07535d8f4621d77f3ba5ec846ee6d52c59d2239e1385ec3b29bf92/langchain-1.2.13-py3-none-any.whl", hash = "sha256:37d4526ac4b0cdd3d7706a6366124c30dc0771bf5340865b37cdc99d5e5ad9b1", size = 112488, upload-time = "2026-03-19T17:16:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/9c/1d/a509af07535d8f4621d77f3ba5ec846ee6d52c59d2239e1385ec3b29bf92/langchain-1.2.13-py3-none-any.whl", hash = "sha256:37d4526ac4b0cdd3d7706a6366124c30dc0771bf5340865b37cdc99d5e5ad9b1", size = 112488 }, ] [[package]] @@ -3481,9 +3473,9 @@ dependencies = [ { name = "langchain-core" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/c7/259d4d805c6ac90c8695714fc15498a4557bb515eb24f692fd611966e383/langchain_anthropic-1.4.0.tar.gz", hash = "sha256:bbf64e99f9149a34ba67813e9582b2160a0968de9e9f54f7ba8d1658f253c2e5", size = 674360, upload-time = "2026-03-17T18:42:20.751Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/c7/259d4d805c6ac90c8695714fc15498a4557bb515eb24f692fd611966e383/langchain_anthropic-1.4.0.tar.gz", hash = "sha256:bbf64e99f9149a34ba67813e9582b2160a0968de9e9f54f7ba8d1658f253c2e5", size = 674360 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/c0/77f99373276d4f06c38a887ef6023f101cfc7ba3b2bf9af37064cdbadde5/langchain_anthropic-1.4.0-py3-none-any.whl", hash = "sha256:c84f55722336935f7574d5771598e674f3959fdca0b51de14c9788dbf52761be", size = 48463, upload-time = "2026-03-17T18:42:19.742Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c0/77f99373276d4f06c38a887ef6023f101cfc7ba3b2bf9af37064cdbadde5/langchain_anthropic-1.4.0-py3-none-any.whl", hash = "sha256:c84f55722336935f7574d5771598e674f3959fdca0b51de14c9788dbf52761be", size = 48463 }, ] [[package]] @@ -3499,9 +3491,9 @@ dependencies = [ { name = "requests" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/04/b01c09e37414bab9f209efa311502841a3c0de5bc6c35e729c8d8a9893c9/langchain_classic-1.0.3.tar.gz", hash = "sha256:168ef1dfbfb18cae5a9ff0accecc9413a5b5aa3464b53fa841561a3384b6324a", size = 10534933, upload-time = "2026-03-13T13:56:11.96Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/04/b01c09e37414bab9f209efa311502841a3c0de5bc6c35e729c8d8a9893c9/langchain_classic-1.0.3.tar.gz", hash = "sha256:168ef1dfbfb18cae5a9ff0accecc9413a5b5aa3464b53fa841561a3384b6324a", size = 10534933 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/e6/cfdeedec0537ffbf5041773590d25beb7f2aa467cc6630e788c9c7c72c3e/langchain_classic-1.0.3-py3-none-any.whl", hash = "sha256:26df1ec9806b1fbff19d9085a747ea7d8d82d7e3fb1d25132859979de627ef79", size = 1041335, upload-time = "2026-03-13T13:56:09.677Z" }, + { url = "https://files.pythonhosted.org/packages/ab/e6/cfdeedec0537ffbf5041773590d25beb7f2aa467cc6630e788c9c7c72c3e/langchain_classic-1.0.3-py3-none-any.whl", hash = "sha256:26df1ec9806b1fbff19d9085a747ea7d8d82d7e3fb1d25132859979de627ef79", size = 1041335 }, ] [[package]] @@ -3522,9 +3514,9 @@ dependencies = [ { name = "sqlalchemy" }, { name = "tenacity" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/53/97/a03585d42b9bdb6fbd935282d6e3348b10322a24e6ce12d0c99eb461d9af/langchain_community-0.4.1.tar.gz", hash = "sha256:f3b211832728ee89f169ddce8579b80a085222ddb4f4ed445a46e977d17b1e85", size = 33241144, upload-time = "2025-10-27T15:20:32.504Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/97/a03585d42b9bdb6fbd935282d6e3348b10322a24e6ce12d0c99eb461d9af/langchain_community-0.4.1.tar.gz", hash = "sha256:f3b211832728ee89f169ddce8579b80a085222ddb4f4ed445a46e977d17b1e85", size = 33241144 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/a4/c4fde67f193401512337456cabc2148f2c43316e445f5decd9f8806e2992/langchain_community-0.4.1-py3-none-any.whl", hash = "sha256:2135abb2c7748a35c84613108f7ebf30f8505b18c3c18305ffaecfc7651f6c6a", size = 2533285, upload-time = "2025-10-27T15:20:30.767Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a4/c4fde67f193401512337456cabc2148f2c43316e445f5decd9f8806e2992/langchain_community-0.4.1-py3-none-any.whl", hash = "sha256:2135abb2c7748a35c84613108f7ebf30f8505b18c3c18305ffaecfc7651f6c6a", size = 2533285 }, ] [[package]] @@ -3541,9 +3533,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/a3/c4cd6827a1df46c821e7214b7f7b7a28b189e6c9b84ef15c6d629c5e3179/langchain_core-1.2.22.tar.gz", hash = "sha256:8d8f726d03d3652d403da915126626bb6250747e8ba406537d849e68b9f5d058", size = 842487, upload-time = "2026-03-24T18:48:44.9Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/a3/c4cd6827a1df46c821e7214b7f7b7a28b189e6c9b84ef15c6d629c5e3179/langchain_core-1.2.22.tar.gz", hash = "sha256:8d8f726d03d3652d403da915126626bb6250747e8ba406537d849e68b9f5d058", size = 842487 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/a6/2ffacf0f1a3788f250e75d0b52a24896c413be11be3a6d42bcdf46fbea48/langchain_core-1.2.22-py3-none-any.whl", hash = "sha256:7e30d586b75918e828833b9ec1efc25465723566845dd652c277baf751e9c04b", size = 506829, upload-time = "2026-03-24T18:48:43.286Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a6/2ffacf0f1a3788f250e75d0b52a24896c413be11be3a6d42bcdf46fbea48/langchain_core-1.2.22-py3-none-any.whl", hash = "sha256:7e30d586b75918e828833b9ec1efc25465723566845dd652c277baf751e9c04b", size = 506829 }, ] [[package]] @@ -3554,9 +3546,9 @@ dependencies = [ { name = "daytona" }, { name = "deepagents" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/ff/5a4a7ef32f1697c9084c0c908e268f3715b9e1c4963984035737b1ff139a/langchain_daytona-0.0.4.tar.gz", hash = "sha256:037a88cbba4a198d2832303c7f89d5df082e82bad67d8f8eba4a954d6eea3a05", size = 187188, upload-time = "2026-03-11T17:24:11.332Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/ff/5a4a7ef32f1697c9084c0c908e268f3715b9e1c4963984035737b1ff139a/langchain_daytona-0.0.4.tar.gz", hash = "sha256:037a88cbba4a198d2832303c7f89d5df082e82bad67d8f8eba4a954d6eea3a05", size = 187188 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/ce/40298b8839a51f18892a07839fb22e19ad99847a742ea5b43eab1f779033/langchain_daytona-0.0.4-py3-none-any.whl", hash = "sha256:ea50516c03417bd365ba362885e473a43627ea2443eddd304bf8d46868275720", size = 5104, upload-time = "2026-03-11T17:24:10.149Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ce/40298b8839a51f18892a07839fb22e19ad99847a742ea5b43eab1f779033/langchain_daytona-0.0.4-py3-none-any.whl", hash = "sha256:ea50516c03417bd365ba362885e473a43627ea2443eddd304bf8d46868275720", size = 5104 }, ] [[package]] @@ -3569,9 +3561,9 @@ dependencies = [ { name = "langchain-core" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/14/63/e7d148f903cebfef50109da71378f411166f068d66f79b9e16a62dbacf41/langchain_google_genai-4.2.1.tar.gz", hash = "sha256:7f44487a0337535897e3bba9a1d6605d722629e034f757ffa8755af0aa85daa8", size = 278288, upload-time = "2026-02-19T19:29:19.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/14/63/e7d148f903cebfef50109da71378f411166f068d66f79b9e16a62dbacf41/langchain_google_genai-4.2.1.tar.gz", hash = "sha256:7f44487a0337535897e3bba9a1d6605d722629e034f757ffa8755af0aa85daa8", size = 278288 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/7e/46c5973bd8b10a5c4c8a77136cf536e658796380a17c740246074901b038/langchain_google_genai-4.2.1-py3-none-any.whl", hash = "sha256:a7735289cf94ca3a684d830e09196aac8f6e75e647e3a0a1c3c9dc534ceb985e", size = 66500, upload-time = "2026-02-19T19:29:18.002Z" }, + { url = "https://files.pythonhosted.org/packages/ec/7e/46c5973bd8b10a5c4c8a77136cf536e658796380a17c740246074901b038/langchain_google_genai-4.2.1-py3-none-any.whl", hash = "sha256:a7735289cf94ca3a684d830e09196aac8f6e75e647e3a0a1c3c9dc534ceb985e", size = 66500 }, ] [[package]] @@ -3584,9 +3576,9 @@ dependencies = [ { name = "langchain-core" }, { name = "litellm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/37/ccc1f284a42900ca5b267a50da8e50145e9f264b32ee955ce91aa360d188/langchain_litellm-0.6.4.tar.gz", hash = "sha256:663281db392b3de1f07f891d0f80f9d4b26c0f0d2abbf854ef9b186d99c309ee", size = 339457, upload-time = "2026-04-03T16:56:47.886Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/37/ccc1f284a42900ca5b267a50da8e50145e9f264b32ee955ce91aa360d188/langchain_litellm-0.6.4.tar.gz", hash = "sha256:663281db392b3de1f07f891d0f80f9d4b26c0f0d2abbf854ef9b186d99c309ee", size = 339457 } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e8/25c50bbad7a05106c7af65557e165d6cb6159c90854dae61de59debe735d/langchain_litellm-0.6.4-py3-none-any.whl", hash = "sha256:60f4e37be1a47dc88f94fac7085675ef8fa04bba92f48735792d82f492120744", size = 26360, upload-time = "2026-04-03T16:56:46.76Z" }, + { url = "https://files.pythonhosted.org/packages/43/e8/25c50bbad7a05106c7af65557e165d6cb6159c90854dae61de59debe735d/langchain_litellm-0.6.4-py3-none-any.whl", hash = "sha256:60f4e37be1a47dc88f94fac7085675ef8fa04bba92f48735792d82f492120744", size = 26360 }, ] [[package]] @@ -3596,9 +3588,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/38/14121ead61e0e75f79c3a35e5148ac7c2fe754a55f76eab3eed573269524/langchain_text_splitters-1.1.1.tar.gz", hash = "sha256:34861abe7c07d9e49d4dc852d0129e26b32738b60a74486853ec9b6d6a8e01d2", size = 279352, upload-time = "2026-02-18T23:02:42.798Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/38/14121ead61e0e75f79c3a35e5148ac7c2fe754a55f76eab3eed573269524/langchain_text_splitters-1.1.1.tar.gz", hash = "sha256:34861abe7c07d9e49d4dc852d0129e26b32738b60a74486853ec9b6d6a8e01d2", size = 279352 } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/66/d9e0c3b83b0ad75ee746c51ba347cacecb8d656b96e1d513f3e334d1ccab/langchain_text_splitters-1.1.1-py3-none-any.whl", hash = "sha256:5ed0d7bf314ba925041e7d7d17cd8b10f688300d5415fb26c29442f061e329dc", size = 35734, upload-time = "2026-02-18T23:02:41.913Z" }, + { url = "https://files.pythonhosted.org/packages/84/66/d9e0c3b83b0ad75ee746c51ba347cacecb8d656b96e1d513f3e334d1ccab/langchain_text_splitters-1.1.1-py3-none-any.whl", hash = "sha256:5ed0d7bf314ba925041e7d7d17cd8b10f688300d5415fb26c29442f061e329dc", size = 35734 }, ] [[package]] @@ -3609,9 +3601,9 @@ dependencies = [ { name = "langchain-core" }, { name = "unstructured-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/83/7d0821c03868d69c52a385891772a9c6f931a8a6cd7c16a2e397c01a5ee2/langchain_unstructured-1.0.1.tar.gz", hash = "sha256:9b4b832b8fcdef8598ff634ec6fc0e344e6d8fdae854c5727f717d605d28e406", size = 6418, upload-time = "2025-12-27T23:23:03.848Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/83/7d0821c03868d69c52a385891772a9c6f931a8a6cd7c16a2e397c01a5ee2/langchain_unstructured-1.0.1.tar.gz", hash = "sha256:9b4b832b8fcdef8598ff634ec6fc0e344e6d8fdae854c5727f717d605d28e406", size = 6418 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/17/803613614fa4cec18d7dc9953a6029aa038ae4e693ef1b8375d08c30718c/langchain_unstructured-1.0.1-py3-none-any.whl", hash = "sha256:1cdb00b3bccc05daa6f03bc3991b0a76270fd2fc095b358c23bd08c5f0e05f50", size = 6942, upload-time = "2025-12-27T23:23:02.801Z" }, + { url = "https://files.pythonhosted.org/packages/cc/17/803613614fa4cec18d7dc9953a6029aa038ae4e693ef1b8375d08c30718c/langchain_unstructured-1.0.1-py3-none-any.whl", hash = "sha256:1cdb00b3bccc05daa6f03bc3991b0a76270fd2fc095b358c23bd08c5f0e05f50", size = 6942 }, ] [[package]] @@ -3621,7 +3613,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/72/a3add0e4eec4eb9e2569554f7c70f4a3c27712f40e3284d483e88094cc0e/langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0", size = 981474, upload-time = "2021-05-07T07:54:13.562Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/72/a3add0e4eec4eb9e2569554f7c70f4a3c27712f40e3284d483e88094cc0e/langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0", size = 981474 } [[package]] name = "langgraph" @@ -3635,9 +3627,9 @@ dependencies = [ { name = "pydantic" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/b2/e7db624e8b0ee063ecfbf7acc09467c0836a05914a78e819dfb3744a0fac/langgraph-1.1.3.tar.gz", hash = "sha256:ee496c297a9c93b38d8560be15cbb918110f49077d83abd14976cb13ac3b3370", size = 545120, upload-time = "2026-03-18T23:42:58.24Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/b2/e7db624e8b0ee063ecfbf7acc09467c0836a05914a78e819dfb3744a0fac/langgraph-1.1.3.tar.gz", hash = "sha256:ee496c297a9c93b38d8560be15cbb918110f49077d83abd14976cb13ac3b3370", size = 545120 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/f7/221cc479e95e03e260496616e5ce6fb50c1ea01472e3a5bc481a9b8a2f83/langgraph-1.1.3-py3-none-any.whl", hash = "sha256:57cd6964ebab41cbd211f222293a2352404e55f8b2312cecde05e8753739b546", size = 168149, upload-time = "2026-03-18T23:42:56.967Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f7/221cc479e95e03e260496616e5ce6fb50c1ea01472e3a5bc481a9b8a2f83/langgraph-1.1.3-py3-none-any.whl", hash = "sha256:57cd6964ebab41cbd211f222293a2352404e55f8b2312cecde05e8753739b546", size = 168149 }, ] [[package]] @@ -3648,9 +3640,9 @@ dependencies = [ { name = "langchain-core" }, { name = "ormsgpack" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/44/a8df45d1e8b4637e29789fa8bae1db022c953cc7ac80093cfc52e923547e/langgraph_checkpoint-4.0.1.tar.gz", hash = "sha256:b433123735df11ade28829e40ce25b9be614930cd50245ff2af60629234befd9", size = 158135, upload-time = "2026-02-27T21:06:16.092Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/44/a8df45d1e8b4637e29789fa8bae1db022c953cc7ac80093cfc52e923547e/langgraph_checkpoint-4.0.1.tar.gz", hash = "sha256:b433123735df11ade28829e40ce25b9be614930cd50245ff2af60629234befd9", size = 158135 } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/4c/09a4a0c42f5d2fc38d6c4d67884788eff7fd2cfdf367fdf7033de908b4c0/langgraph_checkpoint-4.0.1-py3-none-any.whl", hash = "sha256:e3adcd7a0e0166f3b48b8cf508ce0ea366e7420b5a73aa81289888727769b034", size = 50453, upload-time = "2026-02-27T21:06:14.293Z" }, + { url = "https://files.pythonhosted.org/packages/65/4c/09a4a0c42f5d2fc38d6c4d67884788eff7fd2cfdf367fdf7033de908b4c0/langgraph_checkpoint-4.0.1-py3-none-any.whl", hash = "sha256:e3adcd7a0e0166f3b48b8cf508ce0ea366e7420b5a73aa81289888727769b034", size = 50453 }, ] [[package]] @@ -3663,9 +3655,9 @@ dependencies = [ { name = "psycopg" }, { name = "psycopg-pool" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/7a/8f439966643d32111248a225e6cb33a182d07c90de780c4dbfc1e0377832/langgraph_checkpoint_postgres-3.0.5.tar.gz", hash = "sha256:a8fd7278a63f4f849b5cbc7884a15ca8f41e7d5f7467d0a66b31e8c24492f7eb", size = 127856, upload-time = "2026-03-18T21:25:29.785Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/7a/8f439966643d32111248a225e6cb33a182d07c90de780c4dbfc1e0377832/langgraph_checkpoint_postgres-3.0.5.tar.gz", hash = "sha256:a8fd7278a63f4f849b5cbc7884a15ca8f41e7d5f7467d0a66b31e8c24492f7eb", size = 127856 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/87/b0f98b33a67204bca9d5619bcd9574222f6b025cf3c125eedcec9a50ecbc/langgraph_checkpoint_postgres-3.0.5-py3-none-any.whl", hash = "sha256:86d7040a88fd70087eaafb72251d796696a0a2d856168f5c11ef620771411552", size = 42907, upload-time = "2026-03-18T21:25:28.75Z" }, + { url = "https://files.pythonhosted.org/packages/e8/87/b0f98b33a67204bca9d5619bcd9574222f6b025cf3c125eedcec9a50ecbc/langgraph_checkpoint_postgres-3.0.5-py3-none-any.whl", hash = "sha256:86d7040a88fd70087eaafb72251d796696a0a2d856168f5c11ef620771411552", size = 42907 }, ] [[package]] @@ -3676,9 +3668,9 @@ dependencies = [ { name = "langchain-core" }, { name = "langgraph-checkpoint" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/06/dd61a5c2dce009d1b03b1d56f2a85b3127659fdddf5b3be5d8f1d60820fb/langgraph_prebuilt-1.0.8.tar.gz", hash = "sha256:0cd3cf5473ced8a6cd687cc5294e08d3de57529d8dd14fdc6ae4899549efcf69", size = 164442, upload-time = "2026-02-19T18:14:39.083Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/06/dd61a5c2dce009d1b03b1d56f2a85b3127659fdddf5b3be5d8f1d60820fb/langgraph_prebuilt-1.0.8.tar.gz", hash = "sha256:0cd3cf5473ced8a6cd687cc5294e08d3de57529d8dd14fdc6ae4899549efcf69", size = 164442 } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/41/ec966424ad3f2ed3996d24079d3342c8cd6c0bd0653c12b2a917a685ec6c/langgraph_prebuilt-1.0.8-py3-none-any.whl", hash = "sha256:d16a731e591ba4470f3e313a319c7eee7dbc40895bcf15c821f985a3522a7ce0", size = 35648, upload-time = "2026-02-19T18:14:37.611Z" }, + { url = "https://files.pythonhosted.org/packages/dc/41/ec966424ad3f2ed3996d24079d3342c8cd6c0bd0653c12b2a917a685ec6c/langgraph_prebuilt-1.0.8-py3-none-any.whl", hash = "sha256:d16a731e591ba4470f3e313a319c7eee7dbc40895bcf15c821f985a3522a7ce0", size = 35648 }, ] [[package]] @@ -3689,9 +3681,9 @@ dependencies = [ { name = "httpx" }, { name = "orjson" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/a1/012f0e0f5c9fd26f92bdc9d244756ad673c428230156ef668e6ec7c18cee/langgraph_sdk-0.3.12.tar.gz", hash = "sha256:c9c9ec22b3c0fcd352e2b8f32a815164f69446b8648ca22606329f4ff4c59a71", size = 194932, upload-time = "2026-03-18T22:15:54.592Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/a1/012f0e0f5c9fd26f92bdc9d244756ad673c428230156ef668e6ec7c18cee/langgraph_sdk-0.3.12.tar.gz", hash = "sha256:c9c9ec22b3c0fcd352e2b8f32a815164f69446b8648ca22606329f4ff4c59a71", size = 194932 } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/4d/4f796e86b03878ab20d9b30aaed1ad459eda71a5c5b67f7cfe712f3548f2/langgraph_sdk-0.3.12-py3-none-any.whl", hash = "sha256:44323804965d6ec2a07127b3cf08a0428ea6deaeb172c2d478d5cd25540e3327", size = 95834, upload-time = "2026-03-18T22:15:53.545Z" }, + { url = "https://files.pythonhosted.org/packages/17/4d/4f796e86b03878ab20d9b30aaed1ad459eda71a5c5b67f7cfe712f3548f2/langgraph_sdk-0.3.12-py3-none-any.whl", hash = "sha256:44323804965d6ec2a07127b3cf08a0428ea6deaeb172c2d478d5cd25540e3327", size = 95834 }, ] [[package]] @@ -3709,18 +3701,18 @@ dependencies = [ { name = "xxhash" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/2a/2d5e6c67396fd228670af278c4da7bd6db2b8d11deaf6f108490b6d3f561/langsmith-0.7.22.tar.gz", hash = "sha256:35bfe795d648b069958280760564632fd28ebc9921c04f3e209c0db6a6c7dc04", size = 1134923, upload-time = "2026-03-19T22:45:23.492Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/2a/2d5e6c67396fd228670af278c4da7bd6db2b8d11deaf6f108490b6d3f561/langsmith-0.7.22.tar.gz", hash = "sha256:35bfe795d648b069958280760564632fd28ebc9921c04f3e209c0db6a6c7dc04", size = 1134923 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/94/1f5d72655ab6534129540843776c40eff757387b88e798d8b3bf7e313fd4/langsmith-0.7.22-py3-none-any.whl", hash = "sha256:6e9d5148314d74e86748cb9d3898632cad0320c9323d95f70f969e5bc078eee4", size = 359927, upload-time = "2026-03-19T22:45:21.603Z" }, + { url = "https://files.pythonhosted.org/packages/1a/94/1f5d72655ab6534129540843776c40eff757387b88e798d8b3bf7e313fd4/langsmith-0.7.22-py3-none-any.whl", hash = "sha256:6e9d5148314d74e86748cb9d3898632cad0320c9323d95f70f969e5bc078eee4", size = 359927 }, ] [[package]] name = "latex2mathml" version = "3.79.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dd/8d/2161f46485d9c36c0fa0e1c997faf08bb7843027e59b549598e49f55f8bf/latex2mathml-3.79.0.tar.gz", hash = "sha256:11bde318c2d2d6fcdd105a07509d867cee2208f653278eb80243dec7ea77a0ce", size = 151103, upload-time = "2026-03-12T23:25:08.028Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/8d/2161f46485d9c36c0fa0e1c997faf08bb7843027e59b549598e49f55f8bf/latex2mathml-3.79.0.tar.gz", hash = "sha256:11bde318c2d2d6fcdd105a07509d867cee2208f653278eb80243dec7ea77a0ce", size = 151103 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/92/56a954dd59637dd2ee013581fa3beea0821f17f2c07f818fc51dcc11fd10/latex2mathml-3.79.0-py3-none-any.whl", hash = "sha256:9f10720d4fcf6b22d1b81f6628237832419a7a29783c13aa92fa8d680165e63d", size = 73945, upload-time = "2026-03-12T23:25:09.466Z" }, + { url = "https://files.pythonhosted.org/packages/fd/92/56a954dd59637dd2ee013581fa3beea0821f17f2c07f818fc51dcc11fd10/latex2mathml-3.79.0-py3-none-any.whl", hash = "sha256:9f10720d4fcf6b22d1b81f6628237832419a7a29783c13aa92fa8d680165e63d", size = 73945 }, ] [[package]] @@ -3732,9 +3724,9 @@ dependencies = [ { name = "packaging" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/69/826a5d1f45426c68d8f6539f8d275c0e4fcaa57f0c017ec3100986558a41/limits-5.8.0.tar.gz", hash = "sha256:c9e0d74aed837e8f6f50d1fcebcf5fd8130957287206bc3799adaee5092655da", size = 226104, upload-time = "2026-02-05T07:17:35.859Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/69/826a5d1f45426c68d8f6539f8d275c0e4fcaa57f0c017ec3100986558a41/limits-5.8.0.tar.gz", hash = "sha256:c9e0d74aed837e8f6f50d1fcebcf5fd8130957287206bc3799adaee5092655da", size = 226104 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/98/cb5ca20618d205a09d5bec7591fbc4130369c7e6308d9a676a28ff3ab22c/limits-5.8.0-py3-none-any.whl", hash = "sha256:ae1b008a43eb43073c3c579398bd4eb4c795de60952532dc24720ab45e1ac6b8", size = 60954, upload-time = "2026-02-05T07:17:34.425Z" }, + { url = "https://files.pythonhosted.org/packages/b9/98/cb5ca20618d205a09d5bec7591fbc4130369c7e6308d9a676a28ff3ab22c/limits-5.8.0-py3-none-any.whl", hash = "sha256:ae1b008a43eb43073c3c579398bd4eb4c795de60952532dc24720ab45e1ac6b8", size = 60954 }, ] [[package]] @@ -3745,9 +3737,9 @@ dependencies = [ { name = "httpx" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/fa/d54d7086ceb8e8aa3777ae82f9876ceb7d15a6f583bbebf2fc2bea4cf69c/linkup_sdk-0.13.0.tar.gz", hash = "sha256:dab3f516bb955bdb9dd5815445bfdc7a2c9803dc57c3b4be4038d9e40f3d096a", size = 76440, upload-time = "2026-03-02T13:09:25.665Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/fa/d54d7086ceb8e8aa3777ae82f9876ceb7d15a6f583bbebf2fc2bea4cf69c/linkup_sdk-0.13.0.tar.gz", hash = "sha256:dab3f516bb955bdb9dd5815445bfdc7a2c9803dc57c3b4be4038d9e40f3d096a", size = 76440 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/b8/9a8be932db54dc673c0a2f033831e9963cb4395352ce5a54a423e2fb58de/linkup_sdk-0.13.0-py3-none-any.whl", hash = "sha256:d4f5f4698cbaf4711a3296473f6030613c9c8ac829c83172a51c34c6e653808a", size = 11750, upload-time = "2026-03-02T13:09:24.553Z" }, + { url = "https://files.pythonhosted.org/packages/4c/b8/9a8be932db54dc673c0a2f033831e9963cb4395352ce5a54a423e2fb58de/linkup_sdk-0.13.0-py3-none-any.whl", hash = "sha256:d4f5f4698cbaf4711a3296473f6030613c9c8ac829c83172a51c34c6e653808a", size = 11750 }, ] [[package]] @@ -3768,9 +3760,9 @@ dependencies = [ { name = "tiktoken" }, { name = "tokenizers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/7c/c095649380adc96c8630273c1768c2ad1e74aa2ee1dd8dd05d218a60569f/litellm-1.83.14.tar.gz", hash = "sha256:24aef9b47cdc424c833e32f3727f411741c690832cd1fe4405e0077144fe09c9", size = 14836599, upload-time = "2026-04-26T03:16:10.176Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/7c/c095649380adc96c8630273c1768c2ad1e74aa2ee1dd8dd05d218a60569f/litellm-1.83.14.tar.gz", hash = "sha256:24aef9b47cdc424c833e32f3727f411741c690832cd1fe4405e0077144fe09c9", size = 14836599 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/5c/1b5691575420135e90578543b2bf219497caa33cfd0af64cb38f30288450/litellm-1.83.14-py3-none-any.whl", hash = "sha256:92b11ba2a32cf80707ddf388d18526696c7999a21b418c5e3b6eda1243d2cfdb", size = 16457054, upload-time = "2026-04-26T03:16:05.72Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5c/1b5691575420135e90578543b2bf219497caa33cfd0af64cb38f30288450/litellm-1.83.14-py3-none-any.whl", hash = "sha256:92b11ba2a32cf80707ddf388d18526696c7999a21b418c5e3b6eda1243d2cfdb", size = 16457054 }, ] [[package]] @@ -3782,9 +3774,9 @@ dependencies = [ { name = "httpx" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/f3/f4d6520f8d546e6c5a02f6ebeed5c09774a074b8d2c24ad559ace97a56a6/llama_cloud-0.1.46.tar.gz", hash = "sha256:e86f8791c053590d70cc59e0fc13ce72f9b681a8e658bc61df86d0285288d8ee", size = 127752, upload-time = "2026-01-21T18:40:57.103Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/f3/f4d6520f8d546e6c5a02f6ebeed5c09774a074b8d2c24ad559ace97a56a6/llama_cloud-0.1.46.tar.gz", hash = "sha256:e86f8791c053590d70cc59e0fc13ce72f9b681a8e658bc61df86d0285288d8ee", size = 127752 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/3a/6caaea28c8c804add33c91d356ed7d5a5412d6c9598e1450af95a15e0bcd/llama_cloud-0.1.46-py3-none-any.whl", hash = "sha256:6c6546c09c04a038c86d84d42f00eae8fd3bff49991ad3aab844bd866ecdf352", size = 361989, upload-time = "2026-01-21T18:40:54.863Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3a/6caaea28c8c804add33c91d356ed7d5a5412d6c9598e1450af95a15e0bcd/llama_cloud-0.1.46-py3-none-any.whl", hash = "sha256:6c6546c09c04a038c86d84d42f00eae8fd3bff49991ad3aab844bd866ecdf352", size = 361989 }, ] [[package]] @@ -3801,9 +3793,9 @@ dependencies = [ { name = "python-dotenv" }, { name = "tenacity" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/91/c3c94a58c44d0a12e0df2d5038b188fc283877f56cf2f6c41c60f43258e6/llama_cloud_services-0.6.94.tar.gz", hash = "sha256:127b8440d3d3a964d0c4b3f5fe7fcac3ead482f7645971cc8ae30768dcf63306", size = 64114, upload-time = "2026-02-13T23:29:40.454Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/91/c3c94a58c44d0a12e0df2d5038b188fc283877f56cf2f6c41c60f43258e6/llama_cloud_services-0.6.94.tar.gz", hash = "sha256:127b8440d3d3a964d0c4b3f5fe7fcac3ead482f7645971cc8ae30768dcf63306", size = 64114 } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/ab/876486e4f1c137cfeca8f876abd18eeec35a66a0fd8adb15afba7b28aa8c/llama_cloud_services-0.6.94-py3-none-any.whl", hash = "sha256:ac89785f3689d71298511f751bcf4ca16952a616bd75ff06e0ff164f04b0775b", size = 77098, upload-time = "2026-02-13T23:29:38.958Z" }, + { url = "https://files.pythonhosted.org/packages/14/ab/876486e4f1c137cfeca8f876abd18eeec35a66a0fd8adb15afba7b28aa8c/llama_cloud_services-0.6.94-py3-none-any.whl", hash = "sha256:ac89785f3689d71298511f751bcf4ca16952a616bd75ff06e0ff164f04b0775b", size = 77098 }, ] [[package]] @@ -3840,9 +3832,9 @@ dependencies = [ { name = "typing-inspect" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/eb/a661cc2f70177f59cfe7bfcdb7a4e9352fb073ab46927068151bf2905fbb/llama_index_core-0.14.19.tar.gz", hash = "sha256:7b17f321f0d965495402890991b2bfde49d4197bc46ca5970300cc7b9c2df6a2", size = 11599592, upload-time = "2026-03-25T20:58:25.751Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/eb/a661cc2f70177f59cfe7bfcdb7a4e9352fb073ab46927068151bf2905fbb/llama_index_core-0.14.19.tar.gz", hash = "sha256:7b17f321f0d965495402890991b2bfde49d4197bc46ca5970300cc7b9c2df6a2", size = 11599592 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/b6/6c2678b8597903503b804fe831a203d299bcbcc07bdf35789a484e67f7c0/llama_index_core-0.14.19-py3-none-any.whl", hash = "sha256:807352f16a300f9980d0110cfdaa81d07e201384965e9f7d940c8ead80d463ed", size = 11945679, upload-time = "2026-03-25T20:58:28.265Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b6/6c2678b8597903503b804fe831a203d299bcbcc07bdf35789a484e67f7c0/llama_index_core-0.14.19-py3-none-any.whl", hash = "sha256:807352f16a300f9980d0110cfdaa81d07e201384965e9f7d940c8ead80d463ed", size = 11945679 }, ] [[package]] @@ -3853,9 +3845,9 @@ dependencies = [ { name = "deprecated" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/d0/671b23ccff255c9bce132a84ffd5a6f4541ceefdeab9c1786b08c9722f2e/llama_index_instrumentation-0.5.0.tar.gz", hash = "sha256:eeb724648b25d149de882a5ac9e21c5acb1ce780da214bda2b075341af29ad8e", size = 43831, upload-time = "2026-03-12T20:17:06.742Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/d0/671b23ccff255c9bce132a84ffd5a6f4541ceefdeab9c1786b08c9722f2e/llama_index_instrumentation-0.5.0.tar.gz", hash = "sha256:eeb724648b25d149de882a5ac9e21c5acb1ce780da214bda2b075341af29ad8e", size = 43831 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/45/6dcaccef44e541ffa138e4b45e33e0d40ab2a7d845338483954fcf77bc75/llama_index_instrumentation-0.5.0-py3-none-any.whl", hash = "sha256:aaab83cddd9dd434278891012d8995f47a3bc7ed1736a371db90965348c56a21", size = 16444, upload-time = "2026-03-12T20:17:05.957Z" }, + { url = "https://files.pythonhosted.org/packages/c3/45/6dcaccef44e541ffa138e4b45e33e0d40ab2a7d845338483954fcf77bc75/llama_index_instrumentation-0.5.0-py3-none-any.whl", hash = "sha256:aaab83cddd9dd434278891012d8995f47a3bc7ed1736a371db90965348c56a21", size = 16444 }, ] [[package]] @@ -3867,29 +3859,29 @@ dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/a8/2198a81d96394686f598857d12164256ce11699b99a76fdbaf38b8bc1a2c/llama_index_workflows-2.17.1.tar.gz", hash = "sha256:c62fabe509cf0003ddfe5b2b27f48b3443c7c9a84e9cdc904c6f9ed5f8cbe25d", size = 86723, upload-time = "2026-03-20T15:45:14.216Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/a8/2198a81d96394686f598857d12164256ce11699b99a76fdbaf38b8bc1a2c/llama_index_workflows-2.17.1.tar.gz", hash = "sha256:c62fabe509cf0003ddfe5b2b27f48b3443c7c9a84e9cdc904c6f9ed5f8cbe25d", size = 86723 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/28/20dc2db83adc2d9a11e042eac568f52788eb850e9381ffb1087d51f46672/llama_index_workflows-2.17.1-py3-none-any.whl", hash = "sha256:0d78fc36c2ab5430887c9f34367d59d4c22cf1e6c40ecdc3596214234c2b5010", size = 110539, upload-time = "2026-03-20T15:45:15.341Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/20dc2db83adc2d9a11e042eac568f52788eb850e9381ffb1087d51f46672/llama_index_workflows-2.17.1-py3-none-any.whl", hash = "sha256:0d78fc36c2ab5430887c9f34367d59d4c22cf1e6c40ecdc3596214234c2b5010", size = 110539 }, ] [[package]] name = "llvmlite" version = "0.46.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456, upload-time = "2025-12-08T18:15:36.295Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767, upload-time = "2025-12-08T18:15:00.737Z" }, - { url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176, upload-time = "2025-12-08T18:15:03.936Z" }, - { url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630, upload-time = "2025-12-08T18:15:07.196Z" }, - { url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940, upload-time = "2025-12-08T18:15:10.162Z" }, - { url = "https://files.pythonhosted.org/packages/e6/ff/3eba7eb0aed4b6fca37125387cd417e8c458e750621fce56d2c541f67fa8/llvmlite-0.46.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:30b60892d034bc560e0ec6654737aaa74e5ca327bd8114d82136aa071d611172", size = 37232767, upload-time = "2025-12-08T18:15:13.22Z" }, - { url = "https://files.pythonhosted.org/packages/0e/54/737755c0a91558364b9200702c3c9c15d70ed63f9b98a2c32f1c2aa1f3ba/llvmlite-0.46.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cc19b051753368a9c9f31dc041299059ee91aceec81bd57b0e385e5d5bf1a54", size = 56275176, upload-time = "2025-12-08T18:15:16.339Z" }, - { url = "https://files.pythonhosted.org/packages/e6/91/14f32e1d70905c1c0aa4e6609ab5d705c3183116ca02ac6df2091868413a/llvmlite-0.46.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bca185892908f9ede48c0acd547fe4dc1bafefb8a4967d47db6cf664f9332d12", size = 55128629, upload-time = "2025-12-08T18:15:19.493Z" }, - { url = "https://files.pythonhosted.org/packages/4a/a7/d526ae86708cea531935ae777b6dbcabe7db52718e6401e0fb9c5edea80e/llvmlite-0.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:67438fd30e12349ebb054d86a5a1a57fd5e87d264d2451bcfafbbbaa25b82a35", size = 38138941, upload-time = "2025-12-08T18:15:22.536Z" }, - { url = "https://files.pythonhosted.org/packages/95/ae/af0ffb724814cc2ea64445acad05f71cff5f799bb7efb22e47ee99340dbc/llvmlite-0.46.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:d252edfb9f4ac1fcf20652258e3f102b26b03eef738dc8a6ffdab7d7d341d547", size = 37232768, upload-time = "2025-12-08T18:15:25.055Z" }, - { url = "https://files.pythonhosted.org/packages/c9/19/5018e5352019be753b7b07f7759cdabb69ca5779fea2494be8839270df4c/llvmlite-0.46.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379fdd1c59badeff8982cb47e4694a6143bec3bb49aa10a466e095410522064d", size = 56275173, upload-time = "2025-12-08T18:15:28.109Z" }, - { url = "https://files.pythonhosted.org/packages/9f/c9/d57877759d707e84c082163c543853245f91b70c804115a5010532890f18/llvmlite-0.46.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e8cbfff7f6db0fa2c771ad24154e2a7e457c2444d7673e6de06b8b698c3b269", size = 55128628, upload-time = "2025-12-08T18:15:31.098Z" }, - { url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056, upload-time = "2025-12-08T18:15:33.938Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767 }, + { url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176 }, + { url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630 }, + { url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940 }, + { url = "https://files.pythonhosted.org/packages/e6/ff/3eba7eb0aed4b6fca37125387cd417e8c458e750621fce56d2c541f67fa8/llvmlite-0.46.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:30b60892d034bc560e0ec6654737aaa74e5ca327bd8114d82136aa071d611172", size = 37232767 }, + { url = "https://files.pythonhosted.org/packages/0e/54/737755c0a91558364b9200702c3c9c15d70ed63f9b98a2c32f1c2aa1f3ba/llvmlite-0.46.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cc19b051753368a9c9f31dc041299059ee91aceec81bd57b0e385e5d5bf1a54", size = 56275176 }, + { url = "https://files.pythonhosted.org/packages/e6/91/14f32e1d70905c1c0aa4e6609ab5d705c3183116ca02ac6df2091868413a/llvmlite-0.46.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bca185892908f9ede48c0acd547fe4dc1bafefb8a4967d47db6cf664f9332d12", size = 55128629 }, + { url = "https://files.pythonhosted.org/packages/4a/a7/d526ae86708cea531935ae777b6dbcabe7db52718e6401e0fb9c5edea80e/llvmlite-0.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:67438fd30e12349ebb054d86a5a1a57fd5e87d264d2451bcfafbbbaa25b82a35", size = 38138941 }, + { url = "https://files.pythonhosted.org/packages/95/ae/af0ffb724814cc2ea64445acad05f71cff5f799bb7efb22e47ee99340dbc/llvmlite-0.46.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:d252edfb9f4ac1fcf20652258e3f102b26b03eef738dc8a6ffdab7d7d341d547", size = 37232768 }, + { url = "https://files.pythonhosted.org/packages/c9/19/5018e5352019be753b7b07f7759cdabb69ca5779fea2494be8839270df4c/llvmlite-0.46.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379fdd1c59badeff8982cb47e4694a6143bec3bb49aa10a466e095410522064d", size = 56275173 }, + { url = "https://files.pythonhosted.org/packages/9f/c9/d57877759d707e84c082163c543853245f91b70c804115a5010532890f18/llvmlite-0.46.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e8cbfff7f6db0fa2c771ad24154e2a7e457c2444d7673e6de06b8b698c3b269", size = 55128628 }, + { url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056 }, ] [[package]] @@ -3900,89 +3892,89 @@ dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "win32-setctime", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595 }, ] [[package]] name = "lxml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179, upload-time = "2025-09-22T04:01:23.32Z" }, - { url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", size = 5023044, upload-time = "2025-09-22T04:01:25.118Z" }, - { url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", size = 5359685, upload-time = "2025-09-22T04:01:27.398Z" }, - { url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", size = 5654127, upload-time = "2025-09-22T04:01:29.629Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", size = 5253958, upload-time = "2025-09-22T04:01:31.535Z" }, - { url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", size = 4711541, upload-time = "2025-09-22T04:01:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", size = 5267426, upload-time = "2025-09-22T04:01:35.639Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", size = 5064917, upload-time = "2025-09-22T04:01:37.448Z" }, - { url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", size = 4788795, upload-time = "2025-09-22T04:01:39.165Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", size = 5676759, upload-time = "2025-09-22T04:01:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", size = 5255666, upload-time = "2025-09-22T04:01:43.363Z" }, - { url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", size = 5277989, upload-time = "2025-09-22T04:01:45.215Z" }, - { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456, upload-time = "2025-09-22T04:01:48.243Z" }, - { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793, upload-time = "2025-09-22T04:01:50.042Z" }, - { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836, upload-time = "2025-09-22T04:01:52.145Z" }, - { url = "https://files.pythonhosted.org/packages/53/fd/4e8f0540608977aea078bf6d79f128e0e2c2bba8af1acf775c30baa70460/lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77", size = 8648494, upload-time = "2025-09-22T04:01:54.242Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f4/2a94a3d3dfd6c6b433501b8d470a1960a20ecce93245cf2db1706adf6c19/lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f", size = 4661146, upload-time = "2025-09-22T04:01:56.282Z" }, - { url = "https://files.pythonhosted.org/packages/25/2e/4efa677fa6b322013035d38016f6ae859d06cac67437ca7dc708a6af7028/lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452", size = 4946932, upload-time = "2025-09-22T04:01:58.989Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0f/526e78a6d38d109fdbaa5049c62e1d32fdd70c75fb61c4eadf3045d3d124/lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048", size = 5100060, upload-time = "2025-09-22T04:02:00.812Z" }, - { url = "https://files.pythonhosted.org/packages/81/76/99de58d81fa702cc0ea7edae4f4640416c2062813a00ff24bd70ac1d9c9b/lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df", size = 5019000, upload-time = "2025-09-22T04:02:02.671Z" }, - { url = "https://files.pythonhosted.org/packages/b5/35/9e57d25482bc9a9882cb0037fdb9cc18f4b79d85df94fa9d2a89562f1d25/lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1", size = 5348496, upload-time = "2025-09-22T04:02:04.904Z" }, - { url = "https://files.pythonhosted.org/packages/a6/8e/cb99bd0b83ccc3e8f0f528e9aa1f7a9965dfec08c617070c5db8d63a87ce/lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916", size = 5643779, upload-time = "2025-09-22T04:02:06.689Z" }, - { url = "https://files.pythonhosted.org/packages/d0/34/9e591954939276bb679b73773836c6684c22e56d05980e31d52a9a8deb18/lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd", size = 5244072, upload-time = "2025-09-22T04:02:08.587Z" }, - { url = "https://files.pythonhosted.org/packages/8d/27/b29ff065f9aaca443ee377aff699714fcbffb371b4fce5ac4ca759e436d5/lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6", size = 4718675, upload-time = "2025-09-22T04:02:10.783Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/f756f9c2cd27caa1a6ef8c32ae47aadea697f5c2c6d07b0dae133c244fbe/lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a", size = 5255171, upload-time = "2025-09-22T04:02:12.631Z" }, - { url = "https://files.pythonhosted.org/packages/61/46/bb85ea42d2cb1bd8395484fd72f38e3389611aa496ac7772da9205bbda0e/lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679", size = 5057175, upload-time = "2025-09-22T04:02:14.718Z" }, - { url = "https://files.pythonhosted.org/packages/95/0c/443fc476dcc8e41577f0af70458c50fe299a97bb6b7505bb1ae09aa7f9ac/lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659", size = 4785688, upload-time = "2025-09-22T04:02:16.957Z" }, - { url = "https://files.pythonhosted.org/packages/48/78/6ef0b359d45bb9697bc5a626e1992fa5d27aa3f8004b137b2314793b50a0/lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484", size = 5660655, upload-time = "2025-09-22T04:02:18.815Z" }, - { url = "https://files.pythonhosted.org/packages/ff/ea/e1d33808f386bc1339d08c0dcada6e4712d4ed8e93fcad5f057070b7988a/lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2", size = 5247695, upload-time = "2025-09-22T04:02:20.593Z" }, - { url = "https://files.pythonhosted.org/packages/4f/47/eba75dfd8183673725255247a603b4ad606f4ae657b60c6c145b381697da/lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314", size = 5269841, upload-time = "2025-09-22T04:02:22.489Z" }, - { url = "https://files.pythonhosted.org/packages/76/04/5c5e2b8577bc936e219becb2e98cdb1aca14a4921a12995b9d0c523502ae/lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2", size = 3610700, upload-time = "2025-09-22T04:02:24.465Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0a/4643ccc6bb8b143e9f9640aa54e38255f9d3b45feb2cbe7ae2ca47e8782e/lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7", size = 4010347, upload-time = "2025-09-22T04:02:26.286Z" }, - { url = "https://files.pythonhosted.org/packages/31/ef/dcf1d29c3f530577f61e5fe2f1bd72929acf779953668a8a47a479ae6f26/lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf", size = 3671248, upload-time = "2025-09-22T04:02:27.918Z" }, - { url = "https://files.pythonhosted.org/packages/03/15/d4a377b385ab693ce97b472fe0c77c2b16ec79590e688b3ccc71fba19884/lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe", size = 8659801, upload-time = "2025-09-22T04:02:30.113Z" }, - { url = "https://files.pythonhosted.org/packages/c8/e8/c128e37589463668794d503afaeb003987373c5f94d667124ffd8078bbd9/lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d", size = 4659403, upload-time = "2025-09-22T04:02:32.119Z" }, - { url = "https://files.pythonhosted.org/packages/00/ce/74903904339decdf7da7847bb5741fc98a5451b42fc419a86c0c13d26fe2/lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d", size = 4966974, upload-time = "2025-09-22T04:02:34.155Z" }, - { url = "https://files.pythonhosted.org/packages/1f/d3/131dec79ce61c5567fecf82515bd9bc36395df42501b50f7f7f3bd065df0/lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5", size = 5102953, upload-time = "2025-09-22T04:02:36.054Z" }, - { url = "https://files.pythonhosted.org/packages/3a/ea/a43ba9bb750d4ffdd885f2cd333572f5bb900cd2408b67fdda07e85978a0/lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0", size = 5055054, upload-time = "2025-09-22T04:02:38.154Z" }, - { url = "https://files.pythonhosted.org/packages/60/23/6885b451636ae286c34628f70a7ed1fcc759f8d9ad382d132e1c8d3d9bfd/lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba", size = 5352421, upload-time = "2025-09-22T04:02:40.413Z" }, - { url = "https://files.pythonhosted.org/packages/48/5b/fc2ddfc94ddbe3eebb8e9af6e3fd65e2feba4967f6a4e9683875c394c2d8/lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0", size = 5673684, upload-time = "2025-09-22T04:02:42.288Z" }, - { url = "https://files.pythonhosted.org/packages/29/9c/47293c58cc91769130fbf85531280e8cc7868f7fbb6d92f4670071b9cb3e/lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d", size = 5252463, upload-time = "2025-09-22T04:02:44.165Z" }, - { url = "https://files.pythonhosted.org/packages/9b/da/ba6eceb830c762b48e711ded880d7e3e89fc6c7323e587c36540b6b23c6b/lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37", size = 4698437, upload-time = "2025-09-22T04:02:46.524Z" }, - { url = "https://files.pythonhosted.org/packages/a5/24/7be3f82cb7990b89118d944b619e53c656c97dc89c28cfb143fdb7cd6f4d/lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9", size = 5269890, upload-time = "2025-09-22T04:02:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/1b/bd/dcfb9ea1e16c665efd7538fc5d5c34071276ce9220e234217682e7d2c4a5/lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917", size = 5097185, upload-time = "2025-09-22T04:02:50.746Z" }, - { url = "https://files.pythonhosted.org/packages/21/04/a60b0ff9314736316f28316b694bccbbabe100f8483ad83852d77fc7468e/lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f", size = 4745895, upload-time = "2025-09-22T04:02:52.968Z" }, - { url = "https://files.pythonhosted.org/packages/d6/bd/7d54bd1846e5a310d9c715921c5faa71cf5c0853372adf78aee70c8d7aa2/lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8", size = 5695246, upload-time = "2025-09-22T04:02:54.798Z" }, - { url = "https://files.pythonhosted.org/packages/fd/32/5643d6ab947bc371da21323acb2a6e603cedbe71cb4c99c8254289ab6f4e/lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a", size = 5260797, upload-time = "2025-09-22T04:02:57.058Z" }, - { url = "https://files.pythonhosted.org/packages/33/da/34c1ec4cff1eea7d0b4cd44af8411806ed943141804ac9c5d565302afb78/lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c", size = 5277404, upload-time = "2025-09-22T04:02:58.966Z" }, - { url = "https://files.pythonhosted.org/packages/82/57/4eca3e31e54dc89e2c3507e1cd411074a17565fa5ffc437c4ae0a00d439e/lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b", size = 3670072, upload-time = "2025-09-22T04:03:38.05Z" }, - { url = "https://files.pythonhosted.org/packages/e3/e0/c96cf13eccd20c9421ba910304dae0f619724dcf1702864fd59dd386404d/lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed", size = 4080617, upload-time = "2025-09-22T04:03:39.835Z" }, - { url = "https://files.pythonhosted.org/packages/d5/5d/b3f03e22b3d38d6f188ef044900a9b29b2fe0aebb94625ce9fe244011d34/lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8", size = 3754930, upload-time = "2025-09-22T04:03:41.565Z" }, - { url = "https://files.pythonhosted.org/packages/5e/5c/42c2c4c03554580708fc738d13414801f340c04c3eff90d8d2d227145275/lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d", size = 8910380, upload-time = "2025-09-22T04:03:01.645Z" }, - { url = "https://files.pythonhosted.org/packages/bf/4f/12df843e3e10d18d468a7557058f8d3733e8b6e12401f30b1ef29360740f/lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba", size = 4775632, upload-time = "2025-09-22T04:03:03.814Z" }, - { url = "https://files.pythonhosted.org/packages/e4/0c/9dc31e6c2d0d418483cbcb469d1f5a582a1cd00a1f4081953d44051f3c50/lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601", size = 4975171, upload-time = "2025-09-22T04:03:05.651Z" }, - { url = "https://files.pythonhosted.org/packages/e7/2b/9b870c6ca24c841bdd887504808f0417aa9d8d564114689266f19ddf29c8/lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed", size = 5110109, upload-time = "2025-09-22T04:03:07.452Z" }, - { url = "https://files.pythonhosted.org/packages/bf/0c/4f5f2a4dd319a178912751564471355d9019e220c20d7db3fb8307ed8582/lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37", size = 5041061, upload-time = "2025-09-22T04:03:09.297Z" }, - { url = "https://files.pythonhosted.org/packages/12/64/554eed290365267671fe001a20d72d14f468ae4e6acef1e179b039436967/lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338", size = 5306233, upload-time = "2025-09-22T04:03:11.651Z" }, - { url = "https://files.pythonhosted.org/packages/7a/31/1d748aa275e71802ad9722df32a7a35034246b42c0ecdd8235412c3396ef/lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9", size = 5604739, upload-time = "2025-09-22T04:03:13.592Z" }, - { url = "https://files.pythonhosted.org/packages/8f/41/2c11916bcac09ed561adccacceaedd2bf0e0b25b297ea92aab99fd03d0fa/lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd", size = 5225119, upload-time = "2025-09-22T04:03:15.408Z" }, - { url = "https://files.pythonhosted.org/packages/99/05/4e5c2873d8f17aa018e6afde417c80cc5d0c33be4854cce3ef5670c49367/lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d", size = 4633665, upload-time = "2025-09-22T04:03:17.262Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c9/dcc2da1bebd6275cdc723b515f93edf548b82f36a5458cca3578bc899332/lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9", size = 5234997, upload-time = "2025-09-22T04:03:19.14Z" }, - { url = "https://files.pythonhosted.org/packages/9c/e2/5172e4e7468afca64a37b81dba152fc5d90e30f9c83c7c3213d6a02a5ce4/lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e", size = 5090957, upload-time = "2025-09-22T04:03:21.436Z" }, - { url = "https://files.pythonhosted.org/packages/a5/b3/15461fd3e5cd4ddcb7938b87fc20b14ab113b92312fc97afe65cd7c85de1/lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d", size = 4764372, upload-time = "2025-09-22T04:03:23.27Z" }, - { url = "https://files.pythonhosted.org/packages/05/33/f310b987c8bf9e61c4dd8e8035c416bd3230098f5e3cfa69fc4232de7059/lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec", size = 5634653, upload-time = "2025-09-22T04:03:25.767Z" }, - { url = "https://files.pythonhosted.org/packages/70/ff/51c80e75e0bc9382158133bdcf4e339b5886c6ee2418b5199b3f1a61ed6d/lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272", size = 5233795, upload-time = "2025-09-22T04:03:27.62Z" }, - { url = "https://files.pythonhosted.org/packages/56/4d/4856e897df0d588789dd844dbed9d91782c4ef0b327f96ce53c807e13128/lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f", size = 5257023, upload-time = "2025-09-22T04:03:30.056Z" }, - { url = "https://files.pythonhosted.org/packages/0f/85/86766dfebfa87bea0ab78e9ff7a4b4b45225df4b4d3b8cc3c03c5cd68464/lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312", size = 3911420, upload-time = "2025-09-22T04:03:32.198Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1a/b248b355834c8e32614650b8008c69ffeb0ceb149c793961dd8c0b991bb3/lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca", size = 4406837, upload-time = "2025-09-22T04:03:34.027Z" }, - { url = "https://files.pythonhosted.org/packages/92/aa/df863bcc39c5e0946263454aba394de8a9084dbaff8ad143846b0d844739/lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c", size = 3822205, upload-time = "2025-09-22T04:03:36.249Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887 }, + { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818 }, + { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807 }, + { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179 }, + { url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", size = 5023044 }, + { url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", size = 5359685 }, + { url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", size = 5654127 }, + { url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", size = 5253958 }, + { url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", size = 4711541 }, + { url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", size = 5267426 }, + { url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", size = 5064917 }, + { url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", size = 4788795 }, + { url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", size = 5676759 }, + { url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", size = 5255666 }, + { url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", size = 5277989 }, + { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456 }, + { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793 }, + { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836 }, + { url = "https://files.pythonhosted.org/packages/53/fd/4e8f0540608977aea078bf6d79f128e0e2c2bba8af1acf775c30baa70460/lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77", size = 8648494 }, + { url = "https://files.pythonhosted.org/packages/5d/f4/2a94a3d3dfd6c6b433501b8d470a1960a20ecce93245cf2db1706adf6c19/lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f", size = 4661146 }, + { url = "https://files.pythonhosted.org/packages/25/2e/4efa677fa6b322013035d38016f6ae859d06cac67437ca7dc708a6af7028/lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452", size = 4946932 }, + { url = "https://files.pythonhosted.org/packages/ce/0f/526e78a6d38d109fdbaa5049c62e1d32fdd70c75fb61c4eadf3045d3d124/lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048", size = 5100060 }, + { url = "https://files.pythonhosted.org/packages/81/76/99de58d81fa702cc0ea7edae4f4640416c2062813a00ff24bd70ac1d9c9b/lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df", size = 5019000 }, + { url = "https://files.pythonhosted.org/packages/b5/35/9e57d25482bc9a9882cb0037fdb9cc18f4b79d85df94fa9d2a89562f1d25/lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1", size = 5348496 }, + { url = "https://files.pythonhosted.org/packages/a6/8e/cb99bd0b83ccc3e8f0f528e9aa1f7a9965dfec08c617070c5db8d63a87ce/lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916", size = 5643779 }, + { url = "https://files.pythonhosted.org/packages/d0/34/9e591954939276bb679b73773836c6684c22e56d05980e31d52a9a8deb18/lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd", size = 5244072 }, + { url = "https://files.pythonhosted.org/packages/8d/27/b29ff065f9aaca443ee377aff699714fcbffb371b4fce5ac4ca759e436d5/lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6", size = 4718675 }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f756f9c2cd27caa1a6ef8c32ae47aadea697f5c2c6d07b0dae133c244fbe/lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a", size = 5255171 }, + { url = "https://files.pythonhosted.org/packages/61/46/bb85ea42d2cb1bd8395484fd72f38e3389611aa496ac7772da9205bbda0e/lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679", size = 5057175 }, + { url = "https://files.pythonhosted.org/packages/95/0c/443fc476dcc8e41577f0af70458c50fe299a97bb6b7505bb1ae09aa7f9ac/lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659", size = 4785688 }, + { url = "https://files.pythonhosted.org/packages/48/78/6ef0b359d45bb9697bc5a626e1992fa5d27aa3f8004b137b2314793b50a0/lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484", size = 5660655 }, + { url = "https://files.pythonhosted.org/packages/ff/ea/e1d33808f386bc1339d08c0dcada6e4712d4ed8e93fcad5f057070b7988a/lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2", size = 5247695 }, + { url = "https://files.pythonhosted.org/packages/4f/47/eba75dfd8183673725255247a603b4ad606f4ae657b60c6c145b381697da/lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314", size = 5269841 }, + { url = "https://files.pythonhosted.org/packages/76/04/5c5e2b8577bc936e219becb2e98cdb1aca14a4921a12995b9d0c523502ae/lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2", size = 3610700 }, + { url = "https://files.pythonhosted.org/packages/fe/0a/4643ccc6bb8b143e9f9640aa54e38255f9d3b45feb2cbe7ae2ca47e8782e/lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7", size = 4010347 }, + { url = "https://files.pythonhosted.org/packages/31/ef/dcf1d29c3f530577f61e5fe2f1bd72929acf779953668a8a47a479ae6f26/lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf", size = 3671248 }, + { url = "https://files.pythonhosted.org/packages/03/15/d4a377b385ab693ce97b472fe0c77c2b16ec79590e688b3ccc71fba19884/lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe", size = 8659801 }, + { url = "https://files.pythonhosted.org/packages/c8/e8/c128e37589463668794d503afaeb003987373c5f94d667124ffd8078bbd9/lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d", size = 4659403 }, + { url = "https://files.pythonhosted.org/packages/00/ce/74903904339decdf7da7847bb5741fc98a5451b42fc419a86c0c13d26fe2/lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d", size = 4966974 }, + { url = "https://files.pythonhosted.org/packages/1f/d3/131dec79ce61c5567fecf82515bd9bc36395df42501b50f7f7f3bd065df0/lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5", size = 5102953 }, + { url = "https://files.pythonhosted.org/packages/3a/ea/a43ba9bb750d4ffdd885f2cd333572f5bb900cd2408b67fdda07e85978a0/lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0", size = 5055054 }, + { url = "https://files.pythonhosted.org/packages/60/23/6885b451636ae286c34628f70a7ed1fcc759f8d9ad382d132e1c8d3d9bfd/lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba", size = 5352421 }, + { url = "https://files.pythonhosted.org/packages/48/5b/fc2ddfc94ddbe3eebb8e9af6e3fd65e2feba4967f6a4e9683875c394c2d8/lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0", size = 5673684 }, + { url = "https://files.pythonhosted.org/packages/29/9c/47293c58cc91769130fbf85531280e8cc7868f7fbb6d92f4670071b9cb3e/lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d", size = 5252463 }, + { url = "https://files.pythonhosted.org/packages/9b/da/ba6eceb830c762b48e711ded880d7e3e89fc6c7323e587c36540b6b23c6b/lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37", size = 4698437 }, + { url = "https://files.pythonhosted.org/packages/a5/24/7be3f82cb7990b89118d944b619e53c656c97dc89c28cfb143fdb7cd6f4d/lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9", size = 5269890 }, + { url = "https://files.pythonhosted.org/packages/1b/bd/dcfb9ea1e16c665efd7538fc5d5c34071276ce9220e234217682e7d2c4a5/lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917", size = 5097185 }, + { url = "https://files.pythonhosted.org/packages/21/04/a60b0ff9314736316f28316b694bccbbabe100f8483ad83852d77fc7468e/lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f", size = 4745895 }, + { url = "https://files.pythonhosted.org/packages/d6/bd/7d54bd1846e5a310d9c715921c5faa71cf5c0853372adf78aee70c8d7aa2/lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8", size = 5695246 }, + { url = "https://files.pythonhosted.org/packages/fd/32/5643d6ab947bc371da21323acb2a6e603cedbe71cb4c99c8254289ab6f4e/lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a", size = 5260797 }, + { url = "https://files.pythonhosted.org/packages/33/da/34c1ec4cff1eea7d0b4cd44af8411806ed943141804ac9c5d565302afb78/lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c", size = 5277404 }, + { url = "https://files.pythonhosted.org/packages/82/57/4eca3e31e54dc89e2c3507e1cd411074a17565fa5ffc437c4ae0a00d439e/lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b", size = 3670072 }, + { url = "https://files.pythonhosted.org/packages/e3/e0/c96cf13eccd20c9421ba910304dae0f619724dcf1702864fd59dd386404d/lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed", size = 4080617 }, + { url = "https://files.pythonhosted.org/packages/d5/5d/b3f03e22b3d38d6f188ef044900a9b29b2fe0aebb94625ce9fe244011d34/lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8", size = 3754930 }, + { url = "https://files.pythonhosted.org/packages/5e/5c/42c2c4c03554580708fc738d13414801f340c04c3eff90d8d2d227145275/lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d", size = 8910380 }, + { url = "https://files.pythonhosted.org/packages/bf/4f/12df843e3e10d18d468a7557058f8d3733e8b6e12401f30b1ef29360740f/lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba", size = 4775632 }, + { url = "https://files.pythonhosted.org/packages/e4/0c/9dc31e6c2d0d418483cbcb469d1f5a582a1cd00a1f4081953d44051f3c50/lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601", size = 4975171 }, + { url = "https://files.pythonhosted.org/packages/e7/2b/9b870c6ca24c841bdd887504808f0417aa9d8d564114689266f19ddf29c8/lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed", size = 5110109 }, + { url = "https://files.pythonhosted.org/packages/bf/0c/4f5f2a4dd319a178912751564471355d9019e220c20d7db3fb8307ed8582/lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37", size = 5041061 }, + { url = "https://files.pythonhosted.org/packages/12/64/554eed290365267671fe001a20d72d14f468ae4e6acef1e179b039436967/lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338", size = 5306233 }, + { url = "https://files.pythonhosted.org/packages/7a/31/1d748aa275e71802ad9722df32a7a35034246b42c0ecdd8235412c3396ef/lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9", size = 5604739 }, + { url = "https://files.pythonhosted.org/packages/8f/41/2c11916bcac09ed561adccacceaedd2bf0e0b25b297ea92aab99fd03d0fa/lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd", size = 5225119 }, + { url = "https://files.pythonhosted.org/packages/99/05/4e5c2873d8f17aa018e6afde417c80cc5d0c33be4854cce3ef5670c49367/lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d", size = 4633665 }, + { url = "https://files.pythonhosted.org/packages/0f/c9/dcc2da1bebd6275cdc723b515f93edf548b82f36a5458cca3578bc899332/lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9", size = 5234997 }, + { url = "https://files.pythonhosted.org/packages/9c/e2/5172e4e7468afca64a37b81dba152fc5d90e30f9c83c7c3213d6a02a5ce4/lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e", size = 5090957 }, + { url = "https://files.pythonhosted.org/packages/a5/b3/15461fd3e5cd4ddcb7938b87fc20b14ab113b92312fc97afe65cd7c85de1/lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d", size = 4764372 }, + { url = "https://files.pythonhosted.org/packages/05/33/f310b987c8bf9e61c4dd8e8035c416bd3230098f5e3cfa69fc4232de7059/lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec", size = 5634653 }, + { url = "https://files.pythonhosted.org/packages/70/ff/51c80e75e0bc9382158133bdcf4e339b5886c6ee2418b5199b3f1a61ed6d/lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272", size = 5233795 }, + { url = "https://files.pythonhosted.org/packages/56/4d/4856e897df0d588789dd844dbed9d91782c4ef0b327f96ce53c807e13128/lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f", size = 5257023 }, + { url = "https://files.pythonhosted.org/packages/0f/85/86766dfebfa87bea0ab78e9ff7a4b4b45225df4b4d3b8cc3c03c5cd68464/lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312", size = 3911420 }, + { url = "https://files.pythonhosted.org/packages/fe/1a/b248b355834c8e32614650b8008c69ffeb0ceb149c793961dd8c0b991bb3/lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca", size = 4406837 }, + { url = "https://files.pythonhosted.org/packages/92/aa/df863bcc39c5e0946263454aba394de8a9084dbaff8ad143846b0d844739/lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c", size = 3822205 }, ] [package.optional-dependencies] @@ -3997,9 +3989,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lxml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/a4/5c62acfacd69ff4f5db395100f5cfb9b54e7ac8c69a235e4e939fd13f021/lxml_html_clean-0.4.4.tar.gz", hash = "sha256:58f39a9d632711202ed1d6d0b9b47a904e306c85de5761543b90e3e3f736acfb", size = 23899, upload-time = "2026-02-27T09:35:52.911Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/a4/5c62acfacd69ff4f5db395100f5cfb9b54e7ac8c69a235e4e939fd13f021/lxml_html_clean-0.4.4.tar.gz", hash = "sha256:58f39a9d632711202ed1d6d0b9b47a904e306c85de5761543b90e3e3f736acfb", size = 23899 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/76/7ffc1d3005cf7749123bc47cb3ea343cd97b0ac2211bab40f57283577d0e/lxml_html_clean-0.4.4-py3-none-any.whl", hash = "sha256:ce2ef506614ecb85ee1c5fe0a2aa45b06a19514ec7949e9c8f34f06925cfabcb", size = 14565, upload-time = "2026-02-27T09:35:51.86Z" }, + { url = "https://files.pythonhosted.org/packages/d9/76/7ffc1d3005cf7749123bc47cb3ea343cd97b0ac2211bab40f57283577d0e/lxml_html_clean-0.4.4-py3-none-any.whl", hash = "sha256:ce2ef506614ecb85ee1c5fe0a2aa45b06a19514ec7949e9c8f34f06925cfabcb", size = 14565 }, ] [[package]] @@ -4010,21 +4002,21 @@ dependencies = [ { name = "click" }, { name = "onnxruntime" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/ca/dfb30534be5ad84363e0e8ce08bc6e990ce0430aec1eaafb0633b4bb3f7f/magika-1.0.2.tar.gz", hash = "sha256:8ed912d8f14d044f43fdbd17d6bd2cbdd6e8b8246e89be49f6cd547053636677", size = 3041955, upload-time = "2026-02-25T16:07:03.805Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/ca/dfb30534be5ad84363e0e8ce08bc6e990ce0430aec1eaafb0633b4bb3f7f/magika-1.0.2.tar.gz", hash = "sha256:8ed912d8f14d044f43fdbd17d6bd2cbdd6e8b8246e89be49f6cd547053636677", size = 3041955 } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/46/b8180a34c64470e2f40a3676ef3284a32efd2b3598aa99946ee319eb66e8/magika-1.0.2-py3-none-any.whl", hash = "sha256:c50be7a6a7132ef1a92956694401aaf911bda8fc5e2a591092e0dac5b5865a8a", size = 2969547, upload-time = "2026-02-25T16:06:55.987Z" }, - { url = "https://files.pythonhosted.org/packages/38/f3/a65650c36a472fed1ca1c4868e567cf015c14c73a6bb5fa4a808932e0944/magika-1.0.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1db8e2d57556e7244f5fce9cfd023aa0da05d204ea7313f3c75b32feab2bcd6d", size = 13811935, upload-time = "2026-02-25T16:06:57.589Z" }, - { url = "https://files.pythonhosted.org/packages/ba/9e/429608833917b7d4c4f7071a270bbca96821fb592e275d85bc9eae5a94c8/magika-1.0.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:df4706c18153431548b1d36c8ca11c8a8a415197dcc741281846c61ebfc94a5b", size = 15924817, upload-time = "2026-02-25T16:06:59.765Z" }, - { url = "https://files.pythonhosted.org/packages/1a/12/185a8822994a2f7b5e7d88d19a88d80637917bbb0a6f3f59a2564aabc125/magika-1.0.2-py3-none-win_amd64.whl", hash = "sha256:4937e876d55642423d6416e5db4e5ca7523ab7f855cbc5389efdeac1d149df04", size = 13099543, upload-time = "2026-02-25T16:07:01.942Z" }, + { url = "https://files.pythonhosted.org/packages/12/46/b8180a34c64470e2f40a3676ef3284a32efd2b3598aa99946ee319eb66e8/magika-1.0.2-py3-none-any.whl", hash = "sha256:c50be7a6a7132ef1a92956694401aaf911bda8fc5e2a591092e0dac5b5865a8a", size = 2969547 }, + { url = "https://files.pythonhosted.org/packages/38/f3/a65650c36a472fed1ca1c4868e567cf015c14c73a6bb5fa4a808932e0944/magika-1.0.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1db8e2d57556e7244f5fce9cfd023aa0da05d204ea7313f3c75b32feab2bcd6d", size = 13811935 }, + { url = "https://files.pythonhosted.org/packages/ba/9e/429608833917b7d4c4f7071a270bbca96821fb592e275d85bc9eae5a94c8/magika-1.0.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:df4706c18153431548b1d36c8ca11c8a8a415197dcc741281846c61ebfc94a5b", size = 15924817 }, + { url = "https://files.pythonhosted.org/packages/1a/12/185a8822994a2f7b5e7d88d19a88d80637917bbb0a6f3f59a2564aabc125/magika-1.0.2-py3-none-win_amd64.whl", hash = "sha256:4937e876d55642423d6416e5db4e5ca7523ab7f855cbc5389efdeac1d149df04", size = 13099543 }, ] [[package]] name = "makefun" version = "1.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/cf/6780ab8bc3b84a1cce3e4400aed3d64b6db7d5e227a2f75b6ded5674701a/makefun-1.16.0.tar.gz", hash = "sha256:e14601831570bff1f6d7e68828bcd30d2f5856f24bad5de0ccb22921ceebc947", size = 73565, upload-time = "2025-05-09T15:00:42.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/cf/6780ab8bc3b84a1cce3e4400aed3d64b6db7d5e227a2f75b6ded5674701a/makefun-1.16.0.tar.gz", hash = "sha256:e14601831570bff1f6d7e68828bcd30d2f5856f24bad5de0ccb22921ceebc947", size = 73565 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/c0/4bc973defd1270b89ccaae04cef0d5fa3ea85b59b108ad2c08aeea9afb76/makefun-1.16.0-py2.py3-none-any.whl", hash = "sha256:43baa4c3e7ae2b17de9ceac20b669e9a67ceeadff31581007cca20a07bbe42c4", size = 22923, upload-time = "2025-05-09T15:00:41.042Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c0/4bc973defd1270b89ccaae04cef0d5fa3ea85b59b108ad2c08aeea9afb76/makefun-1.16.0-py2.py3-none-any.whl", hash = "sha256:43baa4c3e7ae2b17de9ceac20b669e9a67ceeadff31581007cca20a07bbe42c4", size = 22923 }, ] [[package]] @@ -4034,18 +4026,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474 } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509 }, ] [[package]] name = "markdown" version = "3.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805 } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180 }, ] [[package]] @@ -4055,9 +4047,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070 } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321 }, ] [[package]] @@ -4068,81 +4060,81 @@ dependencies = [ { name = "beautifulsoup4" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3f/bc/c8c8eea5335341306b0fa7e1cb33c5e1c8d24ef70ddd684da65f41c49c92/markdownify-1.2.2.tar.gz", hash = "sha256:b274f1b5943180b031b699b199cbaeb1e2ac938b75851849a31fd0c3d6603d09", size = 18816, upload-time = "2025-11-16T19:21:18.565Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/bc/c8c8eea5335341306b0fa7e1cb33c5e1c8d24ef70ddd684da65f41c49c92/markdownify-1.2.2.tar.gz", hash = "sha256:b274f1b5943180b031b699b199cbaeb1e2ac938b75851849a31fd0c3d6603d09", size = 18816 } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ce/f1e3e9d959db134cedf06825fae8d5b294bd368aacdd0831a3975b7c4d55/markdownify-1.2.2-py3-none-any.whl", hash = "sha256:3f02d3cc52714084d6e589f70397b6fc9f2f3a8531481bf35e8cc39f975e186a", size = 15724, upload-time = "2025-11-16T19:21:17.622Z" }, + { url = "https://files.pythonhosted.org/packages/43/ce/f1e3e9d959db134cedf06825fae8d5b294bd368aacdd0831a3975b7c4d55/markdownify-1.2.2-py3-none-any.whl", hash = "sha256:3f02d3cc52714084d6e589f70397b6fc9f2f3a8531481bf35e8cc39f975e186a", size = 15724 }, ] [[package]] name = "marko" version = "2.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/2f/050b6d485f052ddf17d76a41f9334d6fb2a8a85df35347a12d97ed3bc5c1/marko-2.2.2.tar.gz", hash = "sha256:6940308e655f63733ca518c47a68ec9510279dbb916c83616e4c4b5829f052e8", size = 143641, upload-time = "2026-01-05T11:04:41.935Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/2f/050b6d485f052ddf17d76a41f9334d6fb2a8a85df35347a12d97ed3bc5c1/marko-2.2.2.tar.gz", hash = "sha256:6940308e655f63733ca518c47a68ec9510279dbb916c83616e4c4b5829f052e8", size = 143641 } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/f8/36d79bac5701e6786f9880c61bbe57574760a13c1af84ab71e5ed21faecc/marko-2.2.2-py3-none-any.whl", hash = "sha256:f064ae8c10416285ad1d96048dc11e98ef04e662d3342ae416f662b70aa7959e", size = 42701, upload-time = "2026-01-05T11:04:40.75Z" }, + { url = "https://files.pythonhosted.org/packages/83/f8/36d79bac5701e6786f9880c61bbe57574760a13c1af84ab71e5ed21faecc/marko-2.2.2-py3-none-any.whl", hash = "sha256:f064ae8c10416285ad1d96048dc11e98ef04e662d3342ae416f662b70aa7959e", size = 42701 }, ] [[package]] name = "markupsafe" version = "3.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615 }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020 }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332 }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947 }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962 }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760 }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529 }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015 }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540 }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105 }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906 }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622 }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029 }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374 }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980 }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990 }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784 }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588 }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041 }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543 }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113 }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911 }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658 }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066 }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639 }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569 }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284 }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801 }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769 }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642 }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612 }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200 }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973 }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619 }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029 }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408 }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005 }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048 }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821 }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606 }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043 }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747 }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341 }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073 }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661 }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069 }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670 }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598 }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261 }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835 }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733 }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672 }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819 }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426 }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146 }, ] [[package]] @@ -4152,9 +4144,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/79/de6c16cc902f4fc372236926b0ce2ab7845268dcc30fb2fbb7f71b418631/marshmallow-3.26.2.tar.gz", hash = "sha256:bbe2adb5a03e6e3571b573f42527c6fe926e17467833660bebd11593ab8dfd57", size = 222095, upload-time = "2025-12-22T06:53:53.309Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/79/de6c16cc902f4fc372236926b0ce2ab7845268dcc30fb2fbb7f71b418631/marshmallow-3.26.2.tar.gz", hash = "sha256:bbe2adb5a03e6e3571b573f42527c6fe926e17467833660bebd11593ab8dfd57", size = 222095 } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/2f/5108cb3ee4ba6501748c4908b908e55f42a5b66245b4cfe0c99326e1ef6e/marshmallow-3.26.2-py3-none-any.whl", hash = "sha256:013fa8a3c4c276c24d26d84ce934dc964e2aa794345a0f8c7e5a7191482c8a73", size = 50964, upload-time = "2025-12-22T06:53:51.801Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/5108cb3ee4ba6501748c4908b908e55f42a5b66245b4cfe0c99326e1ef6e/marshmallow-3.26.2-py3-none-any.whl", hash = "sha256:013fa8a3c4c276c24d26d84ce934dc964e2aa794345a0f8c7e5a7191482c8a73", size = 50964 }, ] [[package]] @@ -4172,43 +4164,43 @@ dependencies = [ { name = "pyparsing", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, { name = "python-dateutil", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, - { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, - { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, - { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, - { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, - { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, - { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, - { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, - { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, - { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, - { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, - { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, - { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, - { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, - { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, - { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, - { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, - { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, - { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, - { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, - { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, - { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, - { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, - { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, - { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, - { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, - { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, - { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, - { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453 }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321 }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944 }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099 }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040 }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717 }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751 }, + { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076 }, + { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794 }, + { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474 }, + { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637 }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678 }, + { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686 }, + { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917 }, + { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679 }, + { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336 }, + { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653 }, + { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356 }, + { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000 }, + { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043 }, + { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075 }, + { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481 }, + { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473 }, + { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896 }, + { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193 }, + { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444 }, + { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719 }, + { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205 }, + { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785 }, + { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361 }, + { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357 }, + { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610 }, + { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011 }, + { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801 }, + { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560 }, ] [[package]] @@ -4231,18 +4223,18 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615, upload-time = "2026-01-24T19:40:30.652Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615 }, ] [[package]] name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, ] [[package]] @@ -4253,9 +4245,9 @@ dependencies = [ { name = "addict" }, { name = "regex" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1a/c7/fb01370a76585b46595a01b52f18e65c8ba6d7a313a05e5d9fff0a8e1c69/misaki-0.9.4.tar.gz", hash = "sha256:3960fa3e6de179a90ee8e628446a4a4f6b8c730b6e3410999cf396189f4d9c40", size = 3756765, upload-time = "2025-04-05T21:57:14.186Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c7/fb01370a76585b46595a01b52f18e65c8ba6d7a313a05e5d9fff0a8e1c69/misaki-0.9.4.tar.gz", hash = "sha256:3960fa3e6de179a90ee8e628446a4a4f6b8c730b6e3410999cf396189f4d9c40", size = 3756765 } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/ec/0ee4110ddb54278b8f21c40a140370ae8f687036c4edf578316602697c56/misaki-0.9.4-py3-none-any.whl", hash = "sha256:90e2eeb169786c014c429e5058d2ea6bcd02d651f2a24450ba6c9ffc0f8da15a", size = 3617774, upload-time = "2025-04-05T21:57:10.678Z" }, + { url = "https://files.pythonhosted.org/packages/82/ec/0ee4110ddb54278b8f21c40a140370ae8f687036c4edf578316602697c56/misaki-0.9.4-py3-none-any.whl", hash = "sha256:90e2eeb169786c014c429e5058d2ea6bcd02d651f2a24450ba6c9ffc0f8da15a", size = 3617774 }, ] [package.optional-dependencies] @@ -4271,9 +4263,9 @@ en = [ name = "mistune" version = "3.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598 }, ] [[package]] @@ -4283,115 +4275,115 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, - { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, - { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, - { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, - { url = "https://files.pythonhosted.org/packages/d9/a1/4008f14bbc616cfb1ac5b39ea485f9c63031c4634ab3f4cf72e7541f816a/ml_dtypes-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c760d85a2f82e2bed75867079188c9d18dae2ee77c25a54d60e9cc79be1bc48", size = 676888, upload-time = "2025-11-17T22:31:56.907Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b7/dff378afc2b0d5a7d6cd9d3209b60474d9819d1189d347521e1688a60a53/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce756d3a10d0c4067172804c9cc276ba9cc0ff47af9078ad439b075d1abdc29b", size = 5036993, upload-time = "2025-11-17T22:31:58.497Z" }, - { url = "https://files.pythonhosted.org/packages/eb/33/40cd74219417e78b97c47802037cf2d87b91973e18bb968a7da48a96ea44/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d", size = 5010956, upload-time = "2025-11-17T22:31:59.931Z" }, - { url = "https://files.pythonhosted.org/packages/e1/8b/200088c6859d8221454825959df35b5244fa9bdf263fd0249ac5fb75e281/ml_dtypes-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:f21c9219ef48ca5ee78402d5cc831bd58ea27ce89beda894428bc67a52da5328", size = 212224, upload-time = "2025-11-17T22:32:01.349Z" }, - { url = "https://files.pythonhosted.org/packages/8f/75/dfc3775cb36367816e678f69a7843f6f03bd4e2bcd79941e01ea960a068e/ml_dtypes-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:35f29491a3e478407f7047b8a4834e4640a77d2737e0b294d049746507af5175", size = 160798, upload-time = "2025-11-17T22:32:02.864Z" }, - { url = "https://files.pythonhosted.org/packages/4f/74/e9ddb35fd1dd43b1106c20ced3f53c2e8e7fc7598c15638e9f80677f81d4/ml_dtypes-0.5.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:304ad47faa395415b9ccbcc06a0350800bc50eda70f0e45326796e27c62f18b6", size = 702083, upload-time = "2025-11-17T22:32:04.08Z" }, - { url = "https://files.pythonhosted.org/packages/74/f5/667060b0aed1aa63166b22897fdf16dca9eb704e6b4bbf86848d5a181aa7/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a0df4223b514d799b8a1629c65ddc351b3efa833ccf7f8ea0cf654a61d1e35d", size = 5354111, upload-time = "2025-11-17T22:32:05.546Z" }, - { url = "https://files.pythonhosted.org/packages/40/49/0f8c498a28c0efa5f5c95a9e374c83ec1385ca41d0e85e7cf40e5d519a21/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531eff30e4d368cb6255bc2328d070e35836aa4f282a0fb5f3a0cd7260257298", size = 5366453, upload-time = "2025-11-17T22:32:07.115Z" }, - { url = "https://files.pythonhosted.org/packages/8c/27/12607423d0a9c6bbbcc780ad19f1f6baa2b68b18ce4bddcdc122c4c68dc9/ml_dtypes-0.5.4-cp313-cp313t-win_amd64.whl", hash = "sha256:cb73dccfc991691c444acc8c0012bee8f2470da826a92e3a20bb333b1a7894e6", size = 225612, upload-time = "2025-11-17T22:32:08.615Z" }, - { url = "https://files.pythonhosted.org/packages/e5/80/5a5929e92c72936d5b19872c5fb8fc09327c1da67b3b68c6a13139e77e20/ml_dtypes-0.5.4-cp313-cp313t-win_arm64.whl", hash = "sha256:3bbbe120b915090d9dd1375e4684dd17a20a2491ef25d640a908281da85e73f1", size = 164145, upload-time = "2025-11-17T22:32:09.782Z" }, - { url = "https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22", size = 673781, upload-time = "2025-11-17T22:32:11.364Z" }, - { url = "https://files.pythonhosted.org/packages/04/f9/067b84365c7e83bda15bba2b06c6ca250ce27b20630b1128c435fb7a09aa/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:805cef3a38f4eafae3a5bf9ebdcdb741d0bcfd9e1bd90eb54abd24f928cd2465", size = 5036145, upload-time = "2025-11-17T22:32:12.783Z" }, - { url = "https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f", size = 5010230, upload-time = "2025-11-17T22:32:14.38Z" }, - { url = "https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:8c6a2dcebd6f3903e05d51960a8058d6e131fe69f952a5397e5dbabc841b6d56", size = 221032, upload-time = "2025-11-17T22:32:15.763Z" }, - { url = "https://files.pythonhosted.org/packages/76/a3/9c912fe6ea747bb10fe2f8f54d027eb265db05dfb0c6335e3e063e74e6e8/ml_dtypes-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:5a0f68ca8fd8d16583dfa7793973feb86f2fbb56ce3966daf9c9f748f52a2049", size = 163353, upload-time = "2025-11-17T22:32:16.932Z" }, - { url = "https://files.pythonhosted.org/packages/cd/02/48aa7d84cc30ab4ee37624a2fd98c56c02326785750cd212bc0826c2f15b/ml_dtypes-0.5.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:bfc534409c5d4b0bf945af29e5d0ab075eae9eecbb549ff8a29280db822f34f9", size = 702085, upload-time = "2025-11-17T22:32:18.175Z" }, - { url = "https://files.pythonhosted.org/packages/5a/e7/85cb99fe80a7a5513253ec7faa88a65306be071163485e9a626fce1b6e84/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7", size = 5355358, upload-time = "2025-11-17T22:32:19.7Z" }, - { url = "https://files.pythonhosted.org/packages/79/2b/a826ba18d2179a56e144aef69e57fb2ab7c464ef0b2111940ee8a3a223a2/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf", size = 5366332, upload-time = "2025-11-17T22:32:21.193Z" }, - { url = "https://files.pythonhosted.org/packages/84/44/f4d18446eacb20ea11e82f133ea8f86e2bf2891785b67d9da8d0ab0ef525/ml_dtypes-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4381fe2f2452a2d7589689693d3162e876b3ddb0a832cde7a414f8e1adf7eab1", size = 236612, upload-time = "2025-11-17T22:32:22.579Z" }, - { url = "https://files.pythonhosted.org/packages/ad/3f/3d42e9a78fe5edf792a83c074b13b9b770092a4fbf3462872f4303135f09/ml_dtypes-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:11942cbf2cf92157db91e5022633c0d9474d4dfd813a909383bd23ce828a4b7d", size = 168825, upload-time = "2025-11-17T22:32:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927 }, + { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464 }, + { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002 }, + { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222 }, + { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793 }, + { url = "https://files.pythonhosted.org/packages/d9/a1/4008f14bbc616cfb1ac5b39ea485f9c63031c4634ab3f4cf72e7541f816a/ml_dtypes-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c760d85a2f82e2bed75867079188c9d18dae2ee77c25a54d60e9cc79be1bc48", size = 676888 }, + { url = "https://files.pythonhosted.org/packages/d3/b7/dff378afc2b0d5a7d6cd9d3209b60474d9819d1189d347521e1688a60a53/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce756d3a10d0c4067172804c9cc276ba9cc0ff47af9078ad439b075d1abdc29b", size = 5036993 }, + { url = "https://files.pythonhosted.org/packages/eb/33/40cd74219417e78b97c47802037cf2d87b91973e18bb968a7da48a96ea44/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d", size = 5010956 }, + { url = "https://files.pythonhosted.org/packages/e1/8b/200088c6859d8221454825959df35b5244fa9bdf263fd0249ac5fb75e281/ml_dtypes-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:f21c9219ef48ca5ee78402d5cc831bd58ea27ce89beda894428bc67a52da5328", size = 212224 }, + { url = "https://files.pythonhosted.org/packages/8f/75/dfc3775cb36367816e678f69a7843f6f03bd4e2bcd79941e01ea960a068e/ml_dtypes-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:35f29491a3e478407f7047b8a4834e4640a77d2737e0b294d049746507af5175", size = 160798 }, + { url = "https://files.pythonhosted.org/packages/4f/74/e9ddb35fd1dd43b1106c20ced3f53c2e8e7fc7598c15638e9f80677f81d4/ml_dtypes-0.5.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:304ad47faa395415b9ccbcc06a0350800bc50eda70f0e45326796e27c62f18b6", size = 702083 }, + { url = "https://files.pythonhosted.org/packages/74/f5/667060b0aed1aa63166b22897fdf16dca9eb704e6b4bbf86848d5a181aa7/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a0df4223b514d799b8a1629c65ddc351b3efa833ccf7f8ea0cf654a61d1e35d", size = 5354111 }, + { url = "https://files.pythonhosted.org/packages/40/49/0f8c498a28c0efa5f5c95a9e374c83ec1385ca41d0e85e7cf40e5d519a21/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531eff30e4d368cb6255bc2328d070e35836aa4f282a0fb5f3a0cd7260257298", size = 5366453 }, + { url = "https://files.pythonhosted.org/packages/8c/27/12607423d0a9c6bbbcc780ad19f1f6baa2b68b18ce4bddcdc122c4c68dc9/ml_dtypes-0.5.4-cp313-cp313t-win_amd64.whl", hash = "sha256:cb73dccfc991691c444acc8c0012bee8f2470da826a92e3a20bb333b1a7894e6", size = 225612 }, + { url = "https://files.pythonhosted.org/packages/e5/80/5a5929e92c72936d5b19872c5fb8fc09327c1da67b3b68c6a13139e77e20/ml_dtypes-0.5.4-cp313-cp313t-win_arm64.whl", hash = "sha256:3bbbe120b915090d9dd1375e4684dd17a20a2491ef25d640a908281da85e73f1", size = 164145 }, + { url = "https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22", size = 673781 }, + { url = "https://files.pythonhosted.org/packages/04/f9/067b84365c7e83bda15bba2b06c6ca250ce27b20630b1128c435fb7a09aa/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:805cef3a38f4eafae3a5bf9ebdcdb741d0bcfd9e1bd90eb54abd24f928cd2465", size = 5036145 }, + { url = "https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f", size = 5010230 }, + { url = "https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:8c6a2dcebd6f3903e05d51960a8058d6e131fe69f952a5397e5dbabc841b6d56", size = 221032 }, + { url = "https://files.pythonhosted.org/packages/76/a3/9c912fe6ea747bb10fe2f8f54d027eb265db05dfb0c6335e3e063e74e6e8/ml_dtypes-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:5a0f68ca8fd8d16583dfa7793973feb86f2fbb56ce3966daf9c9f748f52a2049", size = 163353 }, + { url = "https://files.pythonhosted.org/packages/cd/02/48aa7d84cc30ab4ee37624a2fd98c56c02326785750cd212bc0826c2f15b/ml_dtypes-0.5.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:bfc534409c5d4b0bf945af29e5d0ab075eae9eecbb549ff8a29280db822f34f9", size = 702085 }, + { url = "https://files.pythonhosted.org/packages/5a/e7/85cb99fe80a7a5513253ec7faa88a65306be071163485e9a626fce1b6e84/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7", size = 5355358 }, + { url = "https://files.pythonhosted.org/packages/79/2b/a826ba18d2179a56e144aef69e57fb2ab7c464ef0b2111940ee8a3a223a2/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf", size = 5366332 }, + { url = "https://files.pythonhosted.org/packages/84/44/f4d18446eacb20ea11e82f133ea8f86e2bf2891785b67d9da8d0ab0ef525/ml_dtypes-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4381fe2f2452a2d7589689693d3162e876b3ddb0a832cde7a414f8e1adf7eab1", size = 236612 }, + { url = "https://files.pythonhosted.org/packages/ad/3f/3d42e9a78fe5edf792a83c074b13b9b770092a4fbf3462872f4303135f09/ml_dtypes-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:11942cbf2cf92157db91e5022633c0d9474d4dfd813a909383bd23ce828a4b7d", size = 168825 }, ] [[package]] name = "mmh3" version = "5.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/1a/edb23803a168f070ded7a3014c6d706f63b90c84ccc024f89d794a3b7a6d/mmh3-5.2.1.tar.gz", hash = "sha256:bbea5b775f0ac84945191fb83f845a6fd9a21a03ea7f2e187defac7e401616ad", size = 33775, upload-time = "2026-03-05T15:55:57.716Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/1a/edb23803a168f070ded7a3014c6d706f63b90c84ccc024f89d794a3b7a6d/mmh3-5.2.1.tar.gz", hash = "sha256:bbea5b775f0ac84945191fb83f845a6fd9a21a03ea7f2e187defac7e401616ad", size = 33775 } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/94/bc5c3b573b40a328c4d141c20e399039ada95e5e2a661df3425c5165fd84/mmh3-5.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cc21533878e5586b80d74c281d7f8da7932bc8ace50b8d5f6dbf7e3935f63f1", size = 56087, upload-time = "2026-03-05T15:54:21.92Z" }, - { url = "https://files.pythonhosted.org/packages/f6/80/64a02cc3e95c3af0aaa2590849d9ed24a9f14bb93537addde688e039b7c3/mmh3-5.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4eda76074cfca2787c8cf1bec603eaebdddd8b061ad5502f85cddae998d54f00", size = 40500, upload-time = "2026-03-05T15:54:22.953Z" }, - { url = "https://files.pythonhosted.org/packages/8b/72/e6d6602ce18adf4ddcd0e48f2e13590cc92a536199e52109f46f259d3c46/mmh3-5.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eee884572b06bbe8a2b54f424dbd996139442cf83c76478e1ec162512e0dd2c7", size = 40034, upload-time = "2026-03-05T15:54:23.943Z" }, - { url = "https://files.pythonhosted.org/packages/59/c2/bf4537a8e58e21886ef16477041238cab5095c836496e19fafc34b7445d2/mmh3-5.2.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d0b7e803191db5f714d264044e06189c8ccd3219e936cc184f07106bd17fd7b", size = 97292, upload-time = "2026-03-05T15:54:25.335Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e2/51ed62063b44d10b06d975ac87af287729eeb5e3ed9772f7584a17983e90/mmh3-5.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e6c219e375f6341d0959af814296372d265a8ca1af63825f65e2e87c618f006", size = 103274, upload-time = "2026-03-05T15:54:26.44Z" }, - { url = "https://files.pythonhosted.org/packages/75/ce/12a7524dca59eec92e5b31fdb13ede1e98eda277cf2b786cf73bfbc24e81/mmh3-5.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26fb5b9c3946bf7f1daed7b37e0c03898a6f062149127570f8ede346390a0825", size = 106158, upload-time = "2026-03-05T15:54:28.578Z" }, - { url = "https://files.pythonhosted.org/packages/86/1f/d3ba6dd322d01ab5d44c46c8f0c38ab6bbbf9b5e20e666dfc05bf4a23604/mmh3-5.2.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3c38d142c706201db5b2345166eeef1e7740e3e2422b470b8ba5c8727a9b4c7a", size = 113005, upload-time = "2026-03-05T15:54:29.767Z" }, - { url = "https://files.pythonhosted.org/packages/b6/a9/15d6b6f913294ea41b44d901741298e3718e1cb89ee626b3694625826a43/mmh3-5.2.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50885073e2909251d4718634a191c49ae5f527e5e1736d738e365c3e8be8f22b", size = 120744, upload-time = "2026-03-05T15:54:30.931Z" }, - { url = "https://files.pythonhosted.org/packages/76/b3/70b73923fd0284c439860ff5c871b20210dfdbe9a6b9dd0ee6496d77f174/mmh3-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3f99e1756fc48ad507b95e5d86f2fb21b3d495012ff13e6592ebac14033f166", size = 99111, upload-time = "2026-03-05T15:54:32.353Z" }, - { url = "https://files.pythonhosted.org/packages/dd/38/99f7f75cd27d10d8b899a1caafb9d531f3903e4d54d572220e3d8ac35e89/mmh3-5.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62815d2c67f2dd1be76a253d88af4e1da19aeaa1820146dec52cf8bee2958b16", size = 98623, upload-time = "2026-03-05T15:54:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/fd/68/6e292c0853e204c44d2f03ea5f090be3317a0e2d9417ecb62c9eb27687df/mmh3-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8f767ba0911602ddef289404e33835a61168314ebd3c729833db2ed685824211", size = 106437, upload-time = "2026-03-05T15:54:35.177Z" }, - { url = "https://files.pythonhosted.org/packages/dd/c6/fedd7284c459cfb58721d461fcf5607a4c1f5d9ab195d113d51d10164d16/mmh3-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:67e41a497bac88cc1de96eeba56eeb933c39d54bc227352f8455aa87c4ca4000", size = 110002, upload-time = "2026-03-05T15:54:36.673Z" }, - { url = "https://files.pythonhosted.org/packages/3b/ac/ca8e0c19a34f5b71390171d2ff0b9f7f187550d66801a731bb68925126a4/mmh3-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d74a03fb57757ece25aa4b3c1c60157a1cece37a020542785f942e2f827eed5", size = 97507, upload-time = "2026-03-05T15:54:37.804Z" }, - { url = "https://files.pythonhosted.org/packages/df/94/6ebb9094cfc7ac5e7950776b9d13a66bb4a34f83814f32ba2abc9494fc68/mmh3-5.2.1-cp312-cp312-win32.whl", hash = "sha256:7374d6e3ef72afe49697ecd683f3da12f4fc06af2d75433d0580c6746d2fa025", size = 40773, upload-time = "2026-03-05T15:54:40.077Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/cd3527198cf159495966551c84a5f36805a10ac17b294f41f67b83f6a4d6/mmh3-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9fed49c6ce4ed7e73f13182760c65c816da006debe67f37635580dfb0fae00", size = 41560, upload-time = "2026-03-05T15:54:41.148Z" }, - { url = "https://files.pythonhosted.org/packages/15/96/6fe5ebd0f970a076e3ed5512871ce7569447b962e96c125528a2f9724470/mmh3-5.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfcb95d9a744e6e2827dfc66ad10e1020e0cac255eb7f85652832d5a264c2fc", size = 39313, upload-time = "2026-03-05T15:54:42.171Z" }, - { url = "https://files.pythonhosted.org/packages/25/a5/9daa0508a1569a54130f6198d5462a92deda870043624aa3ea72721aa765/mmh3-5.2.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:723b2681ed4cc07d3401bbea9c201ad4f2a4ca6ba8cddaff6789f715dd2b391e", size = 40832, upload-time = "2026-03-05T15:54:43.212Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6b/3230c6d80c1f4b766dedf280a92c2241e99f87c1504ff74205ec8cebe451/mmh3-5.2.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:3619473a0e0d329fd4aec8075628f8f616be2da41605300696206d6f36920c3d", size = 41964, upload-time = "2026-03-05T15:54:44.204Z" }, - { url = "https://files.pythonhosted.org/packages/62/fb/648bfddb74a872004b6ee751551bfdda783fe6d70d2e9723bad84dbe5311/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e48d4dbe0f88e53081da605ae68644e5182752803bbc2beb228cca7f1c4454d6", size = 39114, upload-time = "2026-03-05T15:54:45.205Z" }, - { url = "https://files.pythonhosted.org/packages/95/c2/ab7901f87af438468b496728d11264cb397b3574d41506e71b92128e0373/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a482ac121de6973897c92c2f31defc6bafb11c83825109275cffce54bb64933f", size = 39819, upload-time = "2026-03-05T15:54:46.509Z" }, - { url = "https://files.pythonhosted.org/packages/2f/ed/6f88dda0df67de1612f2e130ffea34cf84aaee5bff5b0aff4dbff2babe34/mmh3-5.2.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:17fbb47f0885ace8327ce1235d0416dc86a211dcd8cc1e703f41523be32cfec8", size = 40330, upload-time = "2026-03-05T15:54:47.864Z" }, - { url = "https://files.pythonhosted.org/packages/3d/66/7516d23f53cdf90f43fce24ab80c28f45e6851d78b46bef8c02084edf583/mmh3-5.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d51fde50a77f81330523562e3c2734ffdca9c4c9e9d355478117905e1cfe16c6", size = 56078, upload-time = "2026-03-05T15:54:48.9Z" }, - { url = "https://files.pythonhosted.org/packages/bc/34/4d152fdf4a91a132cb226b671f11c6b796eada9ab78080fb5ce1e95adaab/mmh3-5.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:19bbd3b841174ae6ed588536ab5e1b1fe83d046e668602c20266547298d939a9", size = 40498, upload-time = "2026-03-05T15:54:49.942Z" }, - { url = "https://files.pythonhosted.org/packages/d4/4c/8e3af1b6d85a299767ec97bd923f12b06267089c1472c27c1696870d1175/mmh3-5.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be77c402d5e882b6fbacfd90823f13da8e0a69658405a39a569c6b58fdb17b03", size = 40033, upload-time = "2026-03-05T15:54:50.994Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f2/966ea560e32578d453c9e9db53d602cbb1d0da27317e232afa7c38ceba11/mmh3-5.2.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fd96476f04db5ceba1cfa0f21228f67c1f7402296f0e73fee3513aa680ad237b", size = 97320, upload-time = "2026-03-05T15:54:52.072Z" }, - { url = "https://files.pythonhosted.org/packages/bb/0d/2c5f9893b38aeb6b034d1a44ecd55a010148054f6a516abe53b5e4057297/mmh3-5.2.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:707151644085dd0f20fe4f4b573d28e5130c4aaa5f587e95b60989c5926653b5", size = 103299, upload-time = "2026-03-05T15:54:53.569Z" }, - { url = "https://files.pythonhosted.org/packages/1c/fc/2ebaef4a4d4376f89761274dc274035ffd96006ab496b4ee5af9b08f21a9/mmh3-5.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3737303ca9ea0f7cb83028781148fcda4f1dac7821db0c47672971dabcf63593", size = 106222, upload-time = "2026-03-05T15:54:55.092Z" }, - { url = "https://files.pythonhosted.org/packages/57/09/ea7ffe126d0ba0406622602a2d05e1e1a6841cc92fc322eb576c95b27fad/mmh3-5.2.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2778fed822d7db23ac5008b181441af0c869455b2e7d001f4019636ac31b6fe4", size = 113048, upload-time = "2026-03-05T15:54:56.305Z" }, - { url = "https://files.pythonhosted.org/packages/85/57/9447032edf93a64aa9bef4d9aa596400b1756f40411890f77a284f6293ca/mmh3-5.2.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d57dea657357230cc780e13920d7fa7db059d58fe721c80020f94476da4ca0a1", size = 120742, upload-time = "2026-03-05T15:54:57.453Z" }, - { url = "https://files.pythonhosted.org/packages/53/82/a86cc87cc88c92e9e1a598fee509f0409435b57879a6129bf3b3e40513c7/mmh3-5.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:169e0d178cb59314456ab30772429a802b25d13227088085b0d49b9fe1533104", size = 99132, upload-time = "2026-03-05T15:54:58.583Z" }, - { url = "https://files.pythonhosted.org/packages/54/f7/6b16eb1b40ee89bb740698735574536bc20d6cdafc65ae702ea235578e05/mmh3-5.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7e4e1f580033335c6f76d1e0d6b56baf009d1a64d6a4816347e4271ba951f46d", size = 98686, upload-time = "2026-03-05T15:55:00.078Z" }, - { url = "https://files.pythonhosted.org/packages/e8/88/a601e9f32ad1410f438a6d0544298ea621f989bd34a0731a7190f7dec799/mmh3-5.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2bd9f19f7f1fcebd74e830f4af0f28adad4975d40d80620be19ffb2b2af56c9f", size = 106479, upload-time = "2026-03-05T15:55:01.532Z" }, - { url = "https://files.pythonhosted.org/packages/d6/5c/ce29ae3dfc4feec4007a437a1b7435fb9507532a25147602cd5b52be86db/mmh3-5.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c88653877aeb514c089d1b3d473451677b8b9a6d1497dbddf1ae7934518b06d2", size = 110030, upload-time = "2026-03-05T15:55:02.934Z" }, - { url = "https://files.pythonhosted.org/packages/13/30/ae444ef2ff87c805d525da4fa63d27cda4fe8a48e77003a036b8461cfd5c/mmh3-5.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fceef7fe67c81e1585198215e42ad3fdba3a25644beda8fbdaf85f4d7b93175a", size = 97536, upload-time = "2026-03-05T15:55:04.135Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f9/dc3787ee5c813cc27fe79f45ad4500d9b5437f23a7402435cc34e07c7718/mmh3-5.2.1-cp313-cp313-win32.whl", hash = "sha256:54b64fb2433bc71488e7a449603bf8bd31fbcf9cb56fbe1eb6d459e90b86c37b", size = 40769, upload-time = "2026-03-05T15:55:05.277Z" }, - { url = "https://files.pythonhosted.org/packages/43/67/850e0b5a1e97799822ebfc4ca0e8c6ece3ed8baf7dcdf64de817dfdda2ca/mmh3-5.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:cae6383181f1e345317742d2ddd88f9e7d2682fa4c9432e3a74e47d92dce0229", size = 41563, upload-time = "2026-03-05T15:55:06.283Z" }, - { url = "https://files.pythonhosted.org/packages/c0/cc/98c90b28e1da5458e19fbfaf4adb5289208d3bfccd45dd14eab216a2f0bb/mmh3-5.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:022aa1a528604e6c83d0a7705fdef0b5355d897a9e0fa3a8d26709ceaa06965d", size = 39310, upload-time = "2026-03-05T15:55:07.323Z" }, - { url = "https://files.pythonhosted.org/packages/63/b4/65bc1fb2bb7f83e91c30865023b1847cf89a5f237165575e8c83aa536584/mmh3-5.2.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:d771f085fcdf4035786adfb1d8db026df1eb4b41dac1c3d070d1e49512843227", size = 40794, upload-time = "2026-03-05T15:55:09.773Z" }, - { url = "https://files.pythonhosted.org/packages/c4/86/7168b3d83be8eb553897b1fac9da8bbb06568e5cfe555ffc329ebb46f59d/mmh3-5.2.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:7f196cd7910d71e9d9860da0ff7a77f64d22c1ad931f1dd18559a06e03109fc0", size = 41923, upload-time = "2026-03-05T15:55:10.924Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9b/b653ab611c9060ce8ff0ba25c0226757755725e789292f3ca138a58082cd/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b1f12bd684887a0a5d55e6363ca87056f361e45451105012d329b86ec19dbe0b", size = 39131, upload-time = "2026-03-05T15:55:11.961Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b4/5a2e0d34ab4d33543f01121e832395ea510132ea8e52cdf63926d9d81754/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d106493a60dcb4aef35a0fac85105e150a11cf8bc2b0d388f5a33272d756c966", size = 39825, upload-time = "2026-03-05T15:55:13.013Z" }, - { url = "https://files.pythonhosted.org/packages/bd/69/81699a8f39a3f8d368bec6443435c0c392df0d200ad915bf0d222b588e03/mmh3-5.2.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:44983e45310ee5b9f73397350251cdf6e63a466406a105f1d16cb5baa659270b", size = 40344, upload-time = "2026-03-05T15:55:14.026Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b3/71c8c775807606e8fd8acc5c69016e1caf3200d50b50b6dd4b40ce10b76c/mmh3-5.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:368625fb01666655985391dbad3860dc0ba7c0d6b9125819f3121ee7292b4ac8", size = 56291, upload-time = "2026-03-05T15:55:15.137Z" }, - { url = "https://files.pythonhosted.org/packages/6f/75/2c24517d4b2ce9e4917362d24f274d3d541346af764430249ddcc4cb3a08/mmh3-5.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:72d1cc63bcc91e14933f77d51b3df899d6a07d184ec515ea7f56bff659e124d7", size = 40575, upload-time = "2026-03-05T15:55:16.518Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b9/e4a360164365ac9f07a25f0f7928e3a66eb9ecc989384060747aa170e6aa/mmh3-5.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e8b4b5580280b9265af3e0409974fb79c64cf7523632d03fbf11df18f8b0181e", size = 40052, upload-time = "2026-03-05T15:55:17.735Z" }, - { url = "https://files.pythonhosted.org/packages/97/ca/120d92223a7546131bbbc31c9174168ee7a73b1366f5463ffe69d9e691fe/mmh3-5.2.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4cbbde66f1183db040daede83dd86c06d663c5bb2af6de1142b7c8c37923dd74", size = 97311, upload-time = "2026-03-05T15:55:18.959Z" }, - { url = "https://files.pythonhosted.org/packages/b6/71/c1a60c1652b8813ef9de6d289784847355417ee0f2980bca002fe87f4ae5/mmh3-5.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8ff038d52ef6aa0f309feeba00c5095c9118d0abf787e8e8454d6048db2037fc", size = 103279, upload-time = "2026-03-05T15:55:20.448Z" }, - { url = "https://files.pythonhosted.org/packages/48/29/ad97f4be1509cdcb28ae32c15593ce7c415db47ace37f8fad35b493faa9a/mmh3-5.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4130d0b9ce5fad6af07421b1aecc7e079519f70d6c05729ab871794eded8617", size = 106290, upload-time = "2026-03-05T15:55:21.6Z" }, - { url = "https://files.pythonhosted.org/packages/77/29/1f86d22e281bd8827ba373600a4a8b0c0eae5ca6aa55b9a8c26d2a34decc/mmh3-5.2.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e0bfe77d238308839699944164b96a2eeccaf55f2af400f54dc20669d8d5f2", size = 113116, upload-time = "2026-03-05T15:55:22.826Z" }, - { url = "https://files.pythonhosted.org/packages/a7/7c/339971ea7ed4c12d98f421f13db3ea576a9114082ccb59d2d1a0f00ccac1/mmh3-5.2.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f963eafc0a77a6c0562397da004f5876a9bcf7265a7bcc3205e29636bc4a1312", size = 120740, upload-time = "2026-03-05T15:55:24.3Z" }, - { url = "https://files.pythonhosted.org/packages/e4/92/3c7c4bdb8e926bb3c972d1e2907d77960c1c4b250b41e8366cf20c6e4373/mmh3-5.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:92883836caf50d5255be03d988d75bc93e3f86ba247b7ca137347c323f731deb", size = 99143, upload-time = "2026-03-05T15:55:25.456Z" }, - { url = "https://files.pythonhosted.org/packages/df/0a/33dd8706e732458c8375eae63c981292de07a406bad4ec03e5269654aa2c/mmh3-5.2.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:57b52603e89355ff318025dd55158f6e71396c0f1f609d548e9ea9c94cc6ce0a", size = 98703, upload-time = "2026-03-05T15:55:26.723Z" }, - { url = "https://files.pythonhosted.org/packages/51/04/76bbce05df76cbc3d396f13b2ea5b1578ef02b6a5187e132c6c33f99d596/mmh3-5.2.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f40a95186a72fa0b67d15fef0f157bfcda00b4f59c8a07cbe5530d41ac35d105", size = 106484, upload-time = "2026-03-05T15:55:28.214Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8f/c6e204a2c70b719c1f62ffd9da27aef2dddcba875ea9c31ca0e87b975a46/mmh3-5.2.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:58370d05d033ee97224c81263af123dea3d931025030fd34b61227a768a8858a", size = 110012, upload-time = "2026-03-05T15:55:29.532Z" }, - { url = "https://files.pythonhosted.org/packages/e3/37/7181efd8e39db386c1ebc3e6b7d1f702a09d7c1197a6f2742ed6b5c16597/mmh3-5.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7be6dfb49e48fd0a7d91ff758a2b51336f1cd21f9d44b20f6801f072bd080cdd", size = 97508, upload-time = "2026-03-05T15:55:31.01Z" }, - { url = "https://files.pythonhosted.org/packages/42/0f/afa7ca2615fd85e1469474bb860e381443d0b868c083b62b41cb1d7ca32f/mmh3-5.2.1-cp314-cp314-win32.whl", hash = "sha256:54fe8518abe06a4c3852754bfd498b30cc58e667f376c513eac89a244ce781a4", size = 41387, upload-time = "2026-03-05T15:55:32.403Z" }, - { url = "https://files.pythonhosted.org/packages/71/0d/46d42a260ee1357db3d486e6c7a692e303c017968e14865e00efa10d09fc/mmh3-5.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:3f796b535008708846044c43302719c6956f39ca2d93f2edda5319e79a29efbb", size = 42101, upload-time = "2026-03-05T15:55:33.646Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7b/848a8378059d96501a41159fca90d6a99e89736b0afbe8e8edffeac8c74b/mmh3-5.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:cd471ede0d802dd936b6fab28188302b2d497f68436025857ca72cd3810423fe", size = 39836, upload-time = "2026-03-05T15:55:35.026Z" }, - { url = "https://files.pythonhosted.org/packages/27/61/1dabea76c011ba8547c25d30c91c0ec22544487a8750997a27a0c9e1180b/mmh3-5.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5174a697ce042fa77c407e05efe41e03aa56dae9ec67388055820fb48cf4c3ba", size = 57727, upload-time = "2026-03-05T15:55:36.162Z" }, - { url = "https://files.pythonhosted.org/packages/b7/32/731185950d1cf2d5e28979cc8593016ba1619a295faba10dda664a4931b5/mmh3-5.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0a3984146e414684a6be2862d84fcb1035f4984851cb81b26d933bab6119bf00", size = 41308, upload-time = "2026-03-05T15:55:37.254Z" }, - { url = "https://files.pythonhosted.org/packages/76/aa/66c76801c24b8c9418b4edde9b5e57c75e72c94e29c48f707e3962534f18/mmh3-5.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bd6e7d363aa93bd3421b30b6af97064daf47bc96005bddba67c5ffbc6df426b8", size = 40758, upload-time = "2026-03-05T15:55:38.61Z" }, - { url = "https://files.pythonhosted.org/packages/9e/bb/79a1f638a02f0ae389f706d13891e2fbf7d8c0a22ecde67ba828951bb60a/mmh3-5.2.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:113f78e7463a36dbbcea05bfe688efd7fa759d0f0c56e73c974d60dcfec3dfcc", size = 109670, upload-time = "2026-03-05T15:55:40.13Z" }, - { url = "https://files.pythonhosted.org/packages/26/94/8cd0e187a288985bcfc79bf5144d1d712df9dee74365f59d26e3a1865be6/mmh3-5.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e8ec5f606e0809426d2440e0683509fb605a8820a21ebd120dcdba61b74ef7f", size = 117399, upload-time = "2026-03-05T15:55:42.076Z" }, - { url = "https://files.pythonhosted.org/packages/42/94/dfea6059bd5c5beda565f58a4096e43f4858fb6d2862806b8bbd12cbb284/mmh3-5.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22b0f9971ec4e07e8223f2beebe96a6cfc779d940b6f27d26604040dd74d3a44", size = 120386, upload-time = "2026-03-05T15:55:43.481Z" }, - { url = "https://files.pythonhosted.org/packages/47/cb/f9c45e62aaa67220179f487772461d891bb582bb2f9783c944832c60efd9/mmh3-5.2.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:85ffc9920ffc39c5eee1e3ac9100c913a0973996fbad5111f939bbda49204bb7", size = 125924, upload-time = "2026-03-05T15:55:44.638Z" }, - { url = "https://files.pythonhosted.org/packages/a5/83/fe54a4a7c11bc9f623dfc1707decd034245602b076dfc1dcc771a4163170/mmh3-5.2.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aec798c2b01aaa65a55f1124f3405804184373abb318a3091325aece235f67c", size = 135280, upload-time = "2026-03-05T15:55:45.866Z" }, - { url = "https://files.pythonhosted.org/packages/97/67/fe7e9e9c143daddd210cd22aef89cbc425d58ecf238d2b7d9eb0da974105/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55dbbd8ffbc40d1697d5e2d0375b08599dae8746b0b08dea05eee4ce81648fac", size = 110050, upload-time = "2026-03-05T15:55:47.074Z" }, - { url = "https://files.pythonhosted.org/packages/43/c4/6d4b09fcbef80794de447c9378e39eefc047156b290fa3dd2d5257ca8227/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6c85c38a279ca9295a69b9b088a2e48aa49737bb1b34e6a9dc6297c110e8d912", size = 111158, upload-time = "2026-03-05T15:55:48.239Z" }, - { url = "https://files.pythonhosted.org/packages/81/a6/ca51c864bdb30524beb055a6d8826db3906af0834ec8c41d097a6e8573d5/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:6290289fa5fb4c70fd7f72016e03633d60388185483ff3b162912c81205ae2cf", size = 116890, upload-time = "2026-03-05T15:55:49.405Z" }, - { url = "https://files.pythonhosted.org/packages/cc/04/5a1fe2e2ad843d03e89af25238cbc4f6840a8bb6c4329a98ab694c71deda/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4fc6cd65dc4d2fdb2625e288939a3566e36127a84811a4913f02f3d5931da52d", size = 123121, upload-time = "2026-03-05T15:55:50.61Z" }, - { url = "https://files.pythonhosted.org/packages/af/4d/3c820c6f4897afd25905270a9f2330a23f77a207ea7356f7aadace7273c0/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:623f938f6a039536cc02b7582a07a080f13fdfd48f87e63201d92d7e34d09a18", size = 110187, upload-time = "2026-03-05T15:55:52.143Z" }, - { url = "https://files.pythonhosted.org/packages/21/54/1d71cd143752361c0aebef16ad3f55926a6faf7b112d355745c1f8a25f7f/mmh3-5.2.1-cp314-cp314t-win32.whl", hash = "sha256:29bc3973676ae334412efdd367fcd11d036b7be3efc1ce2407ef8676dabfeb82", size = 41934, upload-time = "2026-03-05T15:55:53.564Z" }, - { url = "https://files.pythonhosted.org/packages/9d/e4/63a2a88f31d93dea03947cccc2a076946857e799ea4f7acdecbf43b324aa/mmh3-5.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:28cfab66577000b9505a0d068c731aee7ca85cd26d4d63881fab17857e0fe1fb", size = 43036, upload-time = "2026-03-05T15:55:55.252Z" }, - { url = "https://files.pythonhosted.org/packages/a0/0f/59204bf136d1201f8d7884cfbaf7498c5b4674e87a4c693f9bde63741ce1/mmh3-5.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dfd51b4c56b673dfbc43d7d27ef857dd91124801e2806c69bb45585ce0fa019b", size = 40391, upload-time = "2026-03-05T15:55:56.697Z" }, + { url = "https://files.pythonhosted.org/packages/92/94/bc5c3b573b40a328c4d141c20e399039ada95e5e2a661df3425c5165fd84/mmh3-5.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cc21533878e5586b80d74c281d7f8da7932bc8ace50b8d5f6dbf7e3935f63f1", size = 56087 }, + { url = "https://files.pythonhosted.org/packages/f6/80/64a02cc3e95c3af0aaa2590849d9ed24a9f14bb93537addde688e039b7c3/mmh3-5.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4eda76074cfca2787c8cf1bec603eaebdddd8b061ad5502f85cddae998d54f00", size = 40500 }, + { url = "https://files.pythonhosted.org/packages/8b/72/e6d6602ce18adf4ddcd0e48f2e13590cc92a536199e52109f46f259d3c46/mmh3-5.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eee884572b06bbe8a2b54f424dbd996139442cf83c76478e1ec162512e0dd2c7", size = 40034 }, + { url = "https://files.pythonhosted.org/packages/59/c2/bf4537a8e58e21886ef16477041238cab5095c836496e19fafc34b7445d2/mmh3-5.2.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d0b7e803191db5f714d264044e06189c8ccd3219e936cc184f07106bd17fd7b", size = 97292 }, + { url = "https://files.pythonhosted.org/packages/e5/e2/51ed62063b44d10b06d975ac87af287729eeb5e3ed9772f7584a17983e90/mmh3-5.2.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e6c219e375f6341d0959af814296372d265a8ca1af63825f65e2e87c618f006", size = 103274 }, + { url = "https://files.pythonhosted.org/packages/75/ce/12a7524dca59eec92e5b31fdb13ede1e98eda277cf2b786cf73bfbc24e81/mmh3-5.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26fb5b9c3946bf7f1daed7b37e0c03898a6f062149127570f8ede346390a0825", size = 106158 }, + { url = "https://files.pythonhosted.org/packages/86/1f/d3ba6dd322d01ab5d44c46c8f0c38ab6bbbf9b5e20e666dfc05bf4a23604/mmh3-5.2.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3c38d142c706201db5b2345166eeef1e7740e3e2422b470b8ba5c8727a9b4c7a", size = 113005 }, + { url = "https://files.pythonhosted.org/packages/b6/a9/15d6b6f913294ea41b44d901741298e3718e1cb89ee626b3694625826a43/mmh3-5.2.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50885073e2909251d4718634a191c49ae5f527e5e1736d738e365c3e8be8f22b", size = 120744 }, + { url = "https://files.pythonhosted.org/packages/76/b3/70b73923fd0284c439860ff5c871b20210dfdbe9a6b9dd0ee6496d77f174/mmh3-5.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3f99e1756fc48ad507b95e5d86f2fb21b3d495012ff13e6592ebac14033f166", size = 99111 }, + { url = "https://files.pythonhosted.org/packages/dd/38/99f7f75cd27d10d8b899a1caafb9d531f3903e4d54d572220e3d8ac35e89/mmh3-5.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62815d2c67f2dd1be76a253d88af4e1da19aeaa1820146dec52cf8bee2958b16", size = 98623 }, + { url = "https://files.pythonhosted.org/packages/fd/68/6e292c0853e204c44d2f03ea5f090be3317a0e2d9417ecb62c9eb27687df/mmh3-5.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8f767ba0911602ddef289404e33835a61168314ebd3c729833db2ed685824211", size = 106437 }, + { url = "https://files.pythonhosted.org/packages/dd/c6/fedd7284c459cfb58721d461fcf5607a4c1f5d9ab195d113d51d10164d16/mmh3-5.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:67e41a497bac88cc1de96eeba56eeb933c39d54bc227352f8455aa87c4ca4000", size = 110002 }, + { url = "https://files.pythonhosted.org/packages/3b/ac/ca8e0c19a34f5b71390171d2ff0b9f7f187550d66801a731bb68925126a4/mmh3-5.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d74a03fb57757ece25aa4b3c1c60157a1cece37a020542785f942e2f827eed5", size = 97507 }, + { url = "https://files.pythonhosted.org/packages/df/94/6ebb9094cfc7ac5e7950776b9d13a66bb4a34f83814f32ba2abc9494fc68/mmh3-5.2.1-cp312-cp312-win32.whl", hash = "sha256:7374d6e3ef72afe49697ecd683f3da12f4fc06af2d75433d0580c6746d2fa025", size = 40773 }, + { url = "https://files.pythonhosted.org/packages/5b/3c/cd3527198cf159495966551c84a5f36805a10ac17b294f41f67b83f6a4d6/mmh3-5.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9fed49c6ce4ed7e73f13182760c65c816da006debe67f37635580dfb0fae00", size = 41560 }, + { url = "https://files.pythonhosted.org/packages/15/96/6fe5ebd0f970a076e3ed5512871ce7569447b962e96c125528a2f9724470/mmh3-5.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:bbfcb95d9a744e6e2827dfc66ad10e1020e0cac255eb7f85652832d5a264c2fc", size = 39313 }, + { url = "https://files.pythonhosted.org/packages/25/a5/9daa0508a1569a54130f6198d5462a92deda870043624aa3ea72721aa765/mmh3-5.2.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:723b2681ed4cc07d3401bbea9c201ad4f2a4ca6ba8cddaff6789f715dd2b391e", size = 40832 }, + { url = "https://files.pythonhosted.org/packages/0a/6b/3230c6d80c1f4b766dedf280a92c2241e99f87c1504ff74205ec8cebe451/mmh3-5.2.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:3619473a0e0d329fd4aec8075628f8f616be2da41605300696206d6f36920c3d", size = 41964 }, + { url = "https://files.pythonhosted.org/packages/62/fb/648bfddb74a872004b6ee751551bfdda783fe6d70d2e9723bad84dbe5311/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e48d4dbe0f88e53081da605ae68644e5182752803bbc2beb228cca7f1c4454d6", size = 39114 }, + { url = "https://files.pythonhosted.org/packages/95/c2/ab7901f87af438468b496728d11264cb397b3574d41506e71b92128e0373/mmh3-5.2.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a482ac121de6973897c92c2f31defc6bafb11c83825109275cffce54bb64933f", size = 39819 }, + { url = "https://files.pythonhosted.org/packages/2f/ed/6f88dda0df67de1612f2e130ffea34cf84aaee5bff5b0aff4dbff2babe34/mmh3-5.2.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:17fbb47f0885ace8327ce1235d0416dc86a211dcd8cc1e703f41523be32cfec8", size = 40330 }, + { url = "https://files.pythonhosted.org/packages/3d/66/7516d23f53cdf90f43fce24ab80c28f45e6851d78b46bef8c02084edf583/mmh3-5.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d51fde50a77f81330523562e3c2734ffdca9c4c9e9d355478117905e1cfe16c6", size = 56078 }, + { url = "https://files.pythonhosted.org/packages/bc/34/4d152fdf4a91a132cb226b671f11c6b796eada9ab78080fb5ce1e95adaab/mmh3-5.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:19bbd3b841174ae6ed588536ab5e1b1fe83d046e668602c20266547298d939a9", size = 40498 }, + { url = "https://files.pythonhosted.org/packages/d4/4c/8e3af1b6d85a299767ec97bd923f12b06267089c1472c27c1696870d1175/mmh3-5.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be77c402d5e882b6fbacfd90823f13da8e0a69658405a39a569c6b58fdb17b03", size = 40033 }, + { url = "https://files.pythonhosted.org/packages/8b/f2/966ea560e32578d453c9e9db53d602cbb1d0da27317e232afa7c38ceba11/mmh3-5.2.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fd96476f04db5ceba1cfa0f21228f67c1f7402296f0e73fee3513aa680ad237b", size = 97320 }, + { url = "https://files.pythonhosted.org/packages/bb/0d/2c5f9893b38aeb6b034d1a44ecd55a010148054f6a516abe53b5e4057297/mmh3-5.2.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:707151644085dd0f20fe4f4b573d28e5130c4aaa5f587e95b60989c5926653b5", size = 103299 }, + { url = "https://files.pythonhosted.org/packages/1c/fc/2ebaef4a4d4376f89761274dc274035ffd96006ab496b4ee5af9b08f21a9/mmh3-5.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3737303ca9ea0f7cb83028781148fcda4f1dac7821db0c47672971dabcf63593", size = 106222 }, + { url = "https://files.pythonhosted.org/packages/57/09/ea7ffe126d0ba0406622602a2d05e1e1a6841cc92fc322eb576c95b27fad/mmh3-5.2.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2778fed822d7db23ac5008b181441af0c869455b2e7d001f4019636ac31b6fe4", size = 113048 }, + { url = "https://files.pythonhosted.org/packages/85/57/9447032edf93a64aa9bef4d9aa596400b1756f40411890f77a284f6293ca/mmh3-5.2.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d57dea657357230cc780e13920d7fa7db059d58fe721c80020f94476da4ca0a1", size = 120742 }, + { url = "https://files.pythonhosted.org/packages/53/82/a86cc87cc88c92e9e1a598fee509f0409435b57879a6129bf3b3e40513c7/mmh3-5.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:169e0d178cb59314456ab30772429a802b25d13227088085b0d49b9fe1533104", size = 99132 }, + { url = "https://files.pythonhosted.org/packages/54/f7/6b16eb1b40ee89bb740698735574536bc20d6cdafc65ae702ea235578e05/mmh3-5.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7e4e1f580033335c6f76d1e0d6b56baf009d1a64d6a4816347e4271ba951f46d", size = 98686 }, + { url = "https://files.pythonhosted.org/packages/e8/88/a601e9f32ad1410f438a6d0544298ea621f989bd34a0731a7190f7dec799/mmh3-5.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2bd9f19f7f1fcebd74e830f4af0f28adad4975d40d80620be19ffb2b2af56c9f", size = 106479 }, + { url = "https://files.pythonhosted.org/packages/d6/5c/ce29ae3dfc4feec4007a437a1b7435fb9507532a25147602cd5b52be86db/mmh3-5.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c88653877aeb514c089d1b3d473451677b8b9a6d1497dbddf1ae7934518b06d2", size = 110030 }, + { url = "https://files.pythonhosted.org/packages/13/30/ae444ef2ff87c805d525da4fa63d27cda4fe8a48e77003a036b8461cfd5c/mmh3-5.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fceef7fe67c81e1585198215e42ad3fdba3a25644beda8fbdaf85f4d7b93175a", size = 97536 }, + { url = "https://files.pythonhosted.org/packages/4b/f9/dc3787ee5c813cc27fe79f45ad4500d9b5437f23a7402435cc34e07c7718/mmh3-5.2.1-cp313-cp313-win32.whl", hash = "sha256:54b64fb2433bc71488e7a449603bf8bd31fbcf9cb56fbe1eb6d459e90b86c37b", size = 40769 }, + { url = "https://files.pythonhosted.org/packages/43/67/850e0b5a1e97799822ebfc4ca0e8c6ece3ed8baf7dcdf64de817dfdda2ca/mmh3-5.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:cae6383181f1e345317742d2ddd88f9e7d2682fa4c9432e3a74e47d92dce0229", size = 41563 }, + { url = "https://files.pythonhosted.org/packages/c0/cc/98c90b28e1da5458e19fbfaf4adb5289208d3bfccd45dd14eab216a2f0bb/mmh3-5.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:022aa1a528604e6c83d0a7705fdef0b5355d897a9e0fa3a8d26709ceaa06965d", size = 39310 }, + { url = "https://files.pythonhosted.org/packages/63/b4/65bc1fb2bb7f83e91c30865023b1847cf89a5f237165575e8c83aa536584/mmh3-5.2.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:d771f085fcdf4035786adfb1d8db026df1eb4b41dac1c3d070d1e49512843227", size = 40794 }, + { url = "https://files.pythonhosted.org/packages/c4/86/7168b3d83be8eb553897b1fac9da8bbb06568e5cfe555ffc329ebb46f59d/mmh3-5.2.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:7f196cd7910d71e9d9860da0ff7a77f64d22c1ad931f1dd18559a06e03109fc0", size = 41923 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/b653ab611c9060ce8ff0ba25c0226757755725e789292f3ca138a58082cd/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b1f12bd684887a0a5d55e6363ca87056f361e45451105012d329b86ec19dbe0b", size = 39131 }, + { url = "https://files.pythonhosted.org/packages/9b/b4/5a2e0d34ab4d33543f01121e832395ea510132ea8e52cdf63926d9d81754/mmh3-5.2.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d106493a60dcb4aef35a0fac85105e150a11cf8bc2b0d388f5a33272d756c966", size = 39825 }, + { url = "https://files.pythonhosted.org/packages/bd/69/81699a8f39a3f8d368bec6443435c0c392df0d200ad915bf0d222b588e03/mmh3-5.2.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:44983e45310ee5b9f73397350251cdf6e63a466406a105f1d16cb5baa659270b", size = 40344 }, + { url = "https://files.pythonhosted.org/packages/0c/b3/71c8c775807606e8fd8acc5c69016e1caf3200d50b50b6dd4b40ce10b76c/mmh3-5.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:368625fb01666655985391dbad3860dc0ba7c0d6b9125819f3121ee7292b4ac8", size = 56291 }, + { url = "https://files.pythonhosted.org/packages/6f/75/2c24517d4b2ce9e4917362d24f274d3d541346af764430249ddcc4cb3a08/mmh3-5.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:72d1cc63bcc91e14933f77d51b3df899d6a07d184ec515ea7f56bff659e124d7", size = 40575 }, + { url = "https://files.pythonhosted.org/packages/bf/b9/e4a360164365ac9f07a25f0f7928e3a66eb9ecc989384060747aa170e6aa/mmh3-5.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e8b4b5580280b9265af3e0409974fb79c64cf7523632d03fbf11df18f8b0181e", size = 40052 }, + { url = "https://files.pythonhosted.org/packages/97/ca/120d92223a7546131bbbc31c9174168ee7a73b1366f5463ffe69d9e691fe/mmh3-5.2.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4cbbde66f1183db040daede83dd86c06d663c5bb2af6de1142b7c8c37923dd74", size = 97311 }, + { url = "https://files.pythonhosted.org/packages/b6/71/c1a60c1652b8813ef9de6d289784847355417ee0f2980bca002fe87f4ae5/mmh3-5.2.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8ff038d52ef6aa0f309feeba00c5095c9118d0abf787e8e8454d6048db2037fc", size = 103279 }, + { url = "https://files.pythonhosted.org/packages/48/29/ad97f4be1509cdcb28ae32c15593ce7c415db47ace37f8fad35b493faa9a/mmh3-5.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4130d0b9ce5fad6af07421b1aecc7e079519f70d6c05729ab871794eded8617", size = 106290 }, + { url = "https://files.pythonhosted.org/packages/77/29/1f86d22e281bd8827ba373600a4a8b0c0eae5ca6aa55b9a8c26d2a34decc/mmh3-5.2.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e0bfe77d238308839699944164b96a2eeccaf55f2af400f54dc20669d8d5f2", size = 113116 }, + { url = "https://files.pythonhosted.org/packages/a7/7c/339971ea7ed4c12d98f421f13db3ea576a9114082ccb59d2d1a0f00ccac1/mmh3-5.2.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f963eafc0a77a6c0562397da004f5876a9bcf7265a7bcc3205e29636bc4a1312", size = 120740 }, + { url = "https://files.pythonhosted.org/packages/e4/92/3c7c4bdb8e926bb3c972d1e2907d77960c1c4b250b41e8366cf20c6e4373/mmh3-5.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:92883836caf50d5255be03d988d75bc93e3f86ba247b7ca137347c323f731deb", size = 99143 }, + { url = "https://files.pythonhosted.org/packages/df/0a/33dd8706e732458c8375eae63c981292de07a406bad4ec03e5269654aa2c/mmh3-5.2.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:57b52603e89355ff318025dd55158f6e71396c0f1f609d548e9ea9c94cc6ce0a", size = 98703 }, + { url = "https://files.pythonhosted.org/packages/51/04/76bbce05df76cbc3d396f13b2ea5b1578ef02b6a5187e132c6c33f99d596/mmh3-5.2.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f40a95186a72fa0b67d15fef0f157bfcda00b4f59c8a07cbe5530d41ac35d105", size = 106484 }, + { url = "https://files.pythonhosted.org/packages/d3/8f/c6e204a2c70b719c1f62ffd9da27aef2dddcba875ea9c31ca0e87b975a46/mmh3-5.2.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:58370d05d033ee97224c81263af123dea3d931025030fd34b61227a768a8858a", size = 110012 }, + { url = "https://files.pythonhosted.org/packages/e3/37/7181efd8e39db386c1ebc3e6b7d1f702a09d7c1197a6f2742ed6b5c16597/mmh3-5.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7be6dfb49e48fd0a7d91ff758a2b51336f1cd21f9d44b20f6801f072bd080cdd", size = 97508 }, + { url = "https://files.pythonhosted.org/packages/42/0f/afa7ca2615fd85e1469474bb860e381443d0b868c083b62b41cb1d7ca32f/mmh3-5.2.1-cp314-cp314-win32.whl", hash = "sha256:54fe8518abe06a4c3852754bfd498b30cc58e667f376c513eac89a244ce781a4", size = 41387 }, + { url = "https://files.pythonhosted.org/packages/71/0d/46d42a260ee1357db3d486e6c7a692e303c017968e14865e00efa10d09fc/mmh3-5.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:3f796b535008708846044c43302719c6956f39ca2d93f2edda5319e79a29efbb", size = 42101 }, + { url = "https://files.pythonhosted.org/packages/a4/7b/848a8378059d96501a41159fca90d6a99e89736b0afbe8e8edffeac8c74b/mmh3-5.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:cd471ede0d802dd936b6fab28188302b2d497f68436025857ca72cd3810423fe", size = 39836 }, + { url = "https://files.pythonhosted.org/packages/27/61/1dabea76c011ba8547c25d30c91c0ec22544487a8750997a27a0c9e1180b/mmh3-5.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5174a697ce042fa77c407e05efe41e03aa56dae9ec67388055820fb48cf4c3ba", size = 57727 }, + { url = "https://files.pythonhosted.org/packages/b7/32/731185950d1cf2d5e28979cc8593016ba1619a295faba10dda664a4931b5/mmh3-5.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0a3984146e414684a6be2862d84fcb1035f4984851cb81b26d933bab6119bf00", size = 41308 }, + { url = "https://files.pythonhosted.org/packages/76/aa/66c76801c24b8c9418b4edde9b5e57c75e72c94e29c48f707e3962534f18/mmh3-5.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bd6e7d363aa93bd3421b30b6af97064daf47bc96005bddba67c5ffbc6df426b8", size = 40758 }, + { url = "https://files.pythonhosted.org/packages/9e/bb/79a1f638a02f0ae389f706d13891e2fbf7d8c0a22ecde67ba828951bb60a/mmh3-5.2.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:113f78e7463a36dbbcea05bfe688efd7fa759d0f0c56e73c974d60dcfec3dfcc", size = 109670 }, + { url = "https://files.pythonhosted.org/packages/26/94/8cd0e187a288985bcfc79bf5144d1d712df9dee74365f59d26e3a1865be6/mmh3-5.2.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e8ec5f606e0809426d2440e0683509fb605a8820a21ebd120dcdba61b74ef7f", size = 117399 }, + { url = "https://files.pythonhosted.org/packages/42/94/dfea6059bd5c5beda565f58a4096e43f4858fb6d2862806b8bbd12cbb284/mmh3-5.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22b0f9971ec4e07e8223f2beebe96a6cfc779d940b6f27d26604040dd74d3a44", size = 120386 }, + { url = "https://files.pythonhosted.org/packages/47/cb/f9c45e62aaa67220179f487772461d891bb582bb2f9783c944832c60efd9/mmh3-5.2.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:85ffc9920ffc39c5eee1e3ac9100c913a0973996fbad5111f939bbda49204bb7", size = 125924 }, + { url = "https://files.pythonhosted.org/packages/a5/83/fe54a4a7c11bc9f623dfc1707decd034245602b076dfc1dcc771a4163170/mmh3-5.2.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aec798c2b01aaa65a55f1124f3405804184373abb318a3091325aece235f67c", size = 135280 }, + { url = "https://files.pythonhosted.org/packages/97/67/fe7e9e9c143daddd210cd22aef89cbc425d58ecf238d2b7d9eb0da974105/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55dbbd8ffbc40d1697d5e2d0375b08599dae8746b0b08dea05eee4ce81648fac", size = 110050 }, + { url = "https://files.pythonhosted.org/packages/43/c4/6d4b09fcbef80794de447c9378e39eefc047156b290fa3dd2d5257ca8227/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6c85c38a279ca9295a69b9b088a2e48aa49737bb1b34e6a9dc6297c110e8d912", size = 111158 }, + { url = "https://files.pythonhosted.org/packages/81/a6/ca51c864bdb30524beb055a6d8826db3906af0834ec8c41d097a6e8573d5/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:6290289fa5fb4c70fd7f72016e03633d60388185483ff3b162912c81205ae2cf", size = 116890 }, + { url = "https://files.pythonhosted.org/packages/cc/04/5a1fe2e2ad843d03e89af25238cbc4f6840a8bb6c4329a98ab694c71deda/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4fc6cd65dc4d2fdb2625e288939a3566e36127a84811a4913f02f3d5931da52d", size = 123121 }, + { url = "https://files.pythonhosted.org/packages/af/4d/3c820c6f4897afd25905270a9f2330a23f77a207ea7356f7aadace7273c0/mmh3-5.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:623f938f6a039536cc02b7582a07a080f13fdfd48f87e63201d92d7e34d09a18", size = 110187 }, + { url = "https://files.pythonhosted.org/packages/21/54/1d71cd143752361c0aebef16ad3f55926a6faf7b112d355745c1f8a25f7f/mmh3-5.2.1-cp314-cp314t-win32.whl", hash = "sha256:29bc3973676ae334412efdd367fcd11d036b7be3efc1ce2407ef8676dabfeb82", size = 41934 }, + { url = "https://files.pythonhosted.org/packages/9d/e4/63a2a88f31d93dea03947cccc2a076946857e799ea4f7acdecbf43b324aa/mmh3-5.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:28cfab66577000b9505a0d068c731aee7ca85cd26d4d63881fab17857e0fe1fb", size = 43036 }, + { url = "https://files.pythonhosted.org/packages/a0/0f/59204bf136d1201f8d7884cfbaf7498c5b4674e87a4c693f9bde63741ce1/mmh3-5.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dfd51b4c56b673dfbc43d7d27ef857dd91124801e2806c69bb45585ce0fa019b", size = 40391 }, ] [[package]] @@ -4408,18 +4400,18 @@ dependencies = [ { name = "tokenizers" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/26/d2/0da831d4f7930839340ec9cab3d9c82205cf65b96bdc29cab6796b795b68/model2vec-0.8.1.tar.gz", hash = "sha256:9a35d35f6a444e4cec19f2027ee106c54965cd26b7fd4a4f002b5f3e2b6777f4", size = 4535650, upload-time = "2026-03-27T13:38:41.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/d2/0da831d4f7930839340ec9cab3d9c82205cf65b96bdc29cab6796b795b68/model2vec-0.8.1.tar.gz", hash = "sha256:9a35d35f6a444e4cec19f2027ee106c54965cd26b7fd4a4f002b5f3e2b6777f4", size = 4535650 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/5d/fa38e1f68d123726639377f0ba494b366905a2fa451d2c0f365cf0cc6ecb/model2vec-0.8.1-py3-none-any.whl", hash = "sha256:24eee3a5b060c705cceddc9091e0d23fa908fb967e48c0e68b7990211b10927d", size = 49938, upload-time = "2026-03-27T13:38:39.81Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5d/fa38e1f68d123726639377f0ba494b366905a2fa451d2c0f365cf0cc6ecb/model2vec-0.8.1-py3-none-any.whl", hash = "sha256:24eee3a5b060c705cceddc9091e0d23fa908fb967e48c0e68b7990211b10927d", size = 49938 }, ] [[package]] name = "more-itertools" version = "10.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667 }, ] [[package]] @@ -4431,9 +4423,9 @@ dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/93/80ac75c20ce54c785648b4ed363c88f148bf22637e10c9863db4fbe73e74/mpire-2.10.2.tar.gz", hash = "sha256:f66a321e93fadff34585a4bfa05e95bd946cf714b442f51c529038eb45773d97", size = 271270, upload-time = "2024-05-07T14:00:31.815Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/93/80ac75c20ce54c785648b4ed363c88f148bf22637e10c9863db4fbe73e74/mpire-2.10.2.tar.gz", hash = "sha256:f66a321e93fadff34585a4bfa05e95bd946cf714b442f51c529038eb45773d97", size = 271270 } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/14/1db1729ad6db4999c3a16c47937d601fcb909aaa4224f5eca5a2f145a605/mpire-2.10.2-py3-none-any.whl", hash = "sha256:d627707f7a8d02aa4c7f7d59de399dec5290945ddf7fbd36cbb1d6ebb37a51fb", size = 272756, upload-time = "2024-05-07T14:00:29.633Z" }, + { url = "https://files.pythonhosted.org/packages/20/14/1db1729ad6db4999c3a16c47937d601fcb909aaa4224f5eca5a2f145a605/mpire-2.10.2-py3-none-any.whl", hash = "sha256:d627707f7a8d02aa4c7f7d59de399dec5290945ddf7fbd36cbb1d6ebb37a51fb", size = 272756 }, ] [package.optional-dependencies] @@ -4445,9 +4437,9 @@ dill = [ name = "mpmath" version = "1.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 }, ] [[package]] @@ -4459,9 +4451,9 @@ dependencies = [ { name = "pyjwt", extra = ["crypto"] }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/aa/5a646093ac218e4a329391d5a31e5092a89db7d2ef1637a90b82cd0b6f94/msal-1.35.1.tar.gz", hash = "sha256:70cac18ab80a053bff86219ba64cfe3da1f307c74b009e2da57ef040eb1b5656", size = 165658, upload-time = "2026-03-04T23:38:51.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/aa/5a646093ac218e4a329391d5a31e5092a89db7d2ef1637a90b82cd0b6f94/msal-1.35.1.tar.gz", hash = "sha256:70cac18ab80a053bff86219ba64cfe3da1f307c74b009e2da57ef040eb1b5656", size = 165658 } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/86/16815fddf056ca998853c6dc525397edf0b43559bb4073a80d2bc7fe8009/msal-1.35.1-py3-none-any.whl", hash = "sha256:8f4e82f34b10c19e326ec69f44dc6b30171f2f7098f3720ea8a9f0c11832caa3", size = 119909, upload-time = "2026-03-04T23:38:50.452Z" }, + { url = "https://files.pythonhosted.org/packages/96/86/16815fddf056ca998853c6dc525397edf0b43559bb4073a80d2bc7fe8009/msal-1.35.1-py3-none-any.whl", hash = "sha256:8f4e82f34b10c19e326ec69f44dc6b30171f2f7098f3720ea8a9f0c11832caa3", size = 119909 }, ] [[package]] @@ -4471,9 +4463,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "msal" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, + { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583 }, ] [[package]] @@ -4484,108 +4476,108 @@ dependencies = [ { name = "cryptography" }, { name = "olefile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/34/6250bdddaeaae24098e45449ea362fb3555a65fba30cad0ad5630ea48d1a/msoffcrypto_tool-6.0.0.tar.gz", hash = "sha256:9a5ebc4c0096b42e5d7ebc2350afdc92dc511061e935ca188468094fdd032bbe", size = 40593, upload-time = "2026-01-12T08:59:56.73Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/34/6250bdddaeaae24098e45449ea362fb3555a65fba30cad0ad5630ea48d1a/msoffcrypto_tool-6.0.0.tar.gz", hash = "sha256:9a5ebc4c0096b42e5d7ebc2350afdc92dc511061e935ca188468094fdd032bbe", size = 40593 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/85/9e359fa9279e1d6861faaf9b6f037a3226374deb20a054c3937be6992013/msoffcrypto_tool-6.0.0-py3-none-any.whl", hash = "sha256:46c394ed5d9641e802fc79bf3fb0666a53748b23fa8c4aa634ae9d30d46fe397", size = 48791, upload-time = "2026-01-12T08:59:55.394Z" }, + { url = "https://files.pythonhosted.org/packages/3c/85/9e359fa9279e1d6861faaf9b6f037a3226374deb20a054c3937be6992013/msoffcrypto_tool-6.0.0-py3-none-any.whl", hash = "sha256:46c394ed5d9641e802fc79bf3fb0666a53748b23fa8c4aa634ae9d30d46fe397", size = 48791 }, ] [[package]] name = "multidict" version = "6.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, - { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, - { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, - { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, - { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, - { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, - { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, - { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, - { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, - { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, - { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, - { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, - { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, - { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, - { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, - { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, - { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, - { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, - { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, - { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, - { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, - { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, - { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, - { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, - { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, - { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, - { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, - { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, - { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, - { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, - { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, - { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, - { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, - { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, - { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, - { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, - { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, - { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, - { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, - { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, - { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, - { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, - { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, - { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, - { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, - { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, - { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, - { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, - { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, - { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, - { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, - { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, - { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, - { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, - { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, - { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, - { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, - { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, - { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, - { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, - { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, - { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, - { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, - { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, - { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, - { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, - { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, - { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, - { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, - { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, - { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, - { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, - { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, - { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, - { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, - { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, - { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, - { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, - { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, - { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, - { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, - { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, - { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893 }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456 }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872 }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018 }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883 }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413 }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404 }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456 }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322 }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955 }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254 }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059 }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588 }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642 }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377 }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887 }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053 }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307 }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174 }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116 }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524 }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368 }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952 }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317 }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132 }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140 }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277 }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291 }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156 }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742 }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221 }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664 }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490 }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695 }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884 }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122 }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175 }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460 }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930 }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582 }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031 }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596 }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492 }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899 }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970 }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060 }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888 }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554 }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341 }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391 }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422 }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770 }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109 }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573 }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190 }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486 }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219 }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132 }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420 }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510 }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094 }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786 }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483 }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403 }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315 }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528 }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784 }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980 }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602 }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930 }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074 }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471 }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401 }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143 }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507 }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358 }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884 }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878 }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542 }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403 }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889 }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982 }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415 }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337 }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788 }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842 }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237 }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008 }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542 }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719 }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319 }, ] [[package]] @@ -4595,122 +4587,122 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dill" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1", size = 1772603, upload-time = "2024-01-28T18:52:34.85Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/ae/04f39c5d0d0def03247c2893d6f2b83c136bf3320a2154d7b8858f2ba72d/multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1", size = 1772603 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/f7/7ec7fddc92e50714ea3745631f79bd9c96424cb2702632521028e57d3a36/multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02", size = 134824, upload-time = "2024-01-28T18:52:26.062Z" }, - { url = "https://files.pythonhosted.org/packages/50/15/b56e50e8debaf439f44befec5b2af11db85f6e0f344c3113ae0be0593a91/multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a", size = 143519, upload-time = "2024-01-28T18:52:28.115Z" }, - { url = "https://files.pythonhosted.org/packages/0a/7d/a988f258104dcd2ccf1ed40fdc97e26c4ac351eeaf81d76e266c52d84e2f/multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e", size = 146741, upload-time = "2024-01-28T18:52:29.395Z" }, - { url = "https://files.pythonhosted.org/packages/ea/89/38df130f2c799090c978b366cfdf5b96d08de5b29a4a293df7f7429fa50b/multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435", size = 132628, upload-time = "2024-01-28T18:52:30.853Z" }, - { url = "https://files.pythonhosted.org/packages/da/d9/f7f9379981e39b8c2511c9e0326d212accacb82f12fbfdc1aa2ce2a7b2b6/multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3", size = 133351, upload-time = "2024-01-28T18:52:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f7/7ec7fddc92e50714ea3745631f79bd9c96424cb2702632521028e57d3a36/multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02", size = 134824 }, + { url = "https://files.pythonhosted.org/packages/50/15/b56e50e8debaf439f44befec5b2af11db85f6e0f344c3113ae0be0593a91/multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a", size = 143519 }, + { url = "https://files.pythonhosted.org/packages/0a/7d/a988f258104dcd2ccf1ed40fdc97e26c4ac351eeaf81d76e266c52d84e2f/multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e", size = 146741 }, + { url = "https://files.pythonhosted.org/packages/ea/89/38df130f2c799090c978b366cfdf5b96d08de5b29a4a293df7f7429fa50b/multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435", size = 132628 }, + { url = "https://files.pythonhosted.org/packages/da/d9/f7f9379981e39b8c2511c9e0326d212accacb82f12fbfdc1aa2ce2a7b2b6/multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3", size = 133351 }, ] [[package]] name = "murmurhash" version = "1.0.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/2e/88c147931ea9725d634840d538622e94122bceaf346233349b7b5c62964b/murmurhash-1.0.15.tar.gz", hash = "sha256:58e2b27b7847f9e2a6edf10b47a8c8dd70a4705f45dccb7bf76aeadacf56ba01", size = 13291, upload-time = "2025-11-14T09:51:15.272Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/2e/88c147931ea9725d634840d538622e94122bceaf346233349b7b5c62964b/murmurhash-1.0.15.tar.gz", hash = "sha256:58e2b27b7847f9e2a6edf10b47a8c8dd70a4705f45dccb7bf76aeadacf56ba01", size = 13291 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/46/be8522d3456fdccf1b8b049c6d82e7a3c1114c4fc2cfe14b04cba4b3e701/murmurhash-1.0.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d37e3ae44746bca80b1a917c2ea625cf216913564ed43f69d2888e5df97db0cb", size = 27884, upload-time = "2025-11-14T09:50:13.133Z" }, - { url = "https://files.pythonhosted.org/packages/ed/cc/630449bf4f6178d7daf948ce46ad00b25d279065fc30abd8d706be3d87e0/murmurhash-1.0.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0861cb11039409eaf46878456b7d985ef17b6b484103a6fc367b2ecec846891d", size = 27855, upload-time = "2025-11-14T09:50:14.859Z" }, - { url = "https://files.pythonhosted.org/packages/ff/30/ea8f601a9bf44db99468696efd59eb9cff1157cd55cb586d67116697583f/murmurhash-1.0.15-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5a301decfaccfec70fe55cb01dde2a012c3014a874542eaa7cc73477bb749616", size = 134088, upload-time = "2025-11-14T09:50:15.958Z" }, - { url = "https://files.pythonhosted.org/packages/c9/de/c40ce8c0877d406691e735b8d6e9c815f36a82b499d358313db5dbe219d7/murmurhash-1.0.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32c6fde7bd7e9407003370a07b5f4addacabe1556ad3dc2cac246b7a2bba3400", size = 133978, upload-time = "2025-11-14T09:50:17.572Z" }, - { url = "https://files.pythonhosted.org/packages/47/84/bd49963ecd84ebab2fe66595e2d1ed41d5e8b5153af5dc930f0bd827007c/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d8b43a7011540dc3c7ce66f2134df9732e2bc3bbb4a35f6458bc755e48bde26", size = 132956, upload-time = "2025-11-14T09:50:18.742Z" }, - { url = "https://files.pythonhosted.org/packages/4f/7c/2530769c545074417c862583f05f4245644599f1e9ff619b3dfe2969aafc/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43bf4541892ecd95963fcd307bf1c575fc0fee1682f41c93007adee71ca2bb40", size = 134184, upload-time = "2025-11-14T09:50:19.941Z" }, - { url = "https://files.pythonhosted.org/packages/84/a4/b249b042f5afe34d14ada2dc4afc777e883c15863296756179652e081c44/murmurhash-1.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:f4ac15a2089dc42e6eb0966622d42d2521590a12c92480aafecf34c085302cca", size = 25647, upload-time = "2025-11-14T09:50:21.049Z" }, - { url = "https://files.pythonhosted.org/packages/13/bf/028179259aebc18fd4ba5cae2601d1d47517427a537ab44336446431a215/murmurhash-1.0.15-cp312-cp312-win_arm64.whl", hash = "sha256:4a70ca4ae19e600d9be3da64d00710e79dde388a4d162f22078d64844d0ebdda", size = 23338, upload-time = "2025-11-14T09:50:22.359Z" }, - { url = "https://files.pythonhosted.org/packages/29/2f/ba300b5f04dae0409202d6285668b8a9d3ade43a846abee3ef611cb388d5/murmurhash-1.0.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fe50dc70e52786759358fd1471e309b94dddfffb9320d9dfea233c7684c894ba", size = 27861, upload-time = "2025-11-14T09:50:23.804Z" }, - { url = "https://files.pythonhosted.org/packages/34/02/29c19d268e6f4ea1ed2a462c901eed1ed35b454e2cbc57da592fad663ac6/murmurhash-1.0.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1349a7c23f6092e7998ddc5bd28546cc31a595afc61e9fdb3afc423feec3d7ad", size = 27840, upload-time = "2025-11-14T09:50:25.146Z" }, - { url = "https://files.pythonhosted.org/packages/e2/63/58e2de2b5232cd294c64092688c422196e74f9fa8b3958bdf02d33df24b9/murmurhash-1.0.15-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ba6d05de2613535b5a9227d4ad8ef40a540465f64660d4a8800634ae10e04f", size = 133080, upload-time = "2025-11-14T09:50:26.566Z" }, - { url = "https://files.pythonhosted.org/packages/aa/9a/d13e2e9f8ba1ced06840921a50f7cece0a475453284158a3018b72679761/murmurhash-1.0.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fa1b70b3cc2801ab44179c65827bbd12009c68b34e9d9ce7125b6a0bd35af63c", size = 132648, upload-time = "2025-11-14T09:50:27.788Z" }, - { url = "https://files.pythonhosted.org/packages/b2/e1/47994f1813fa205c84977b0ff51ae6709f8539af052c7491a5f863d82bdc/murmurhash-1.0.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:213d710fb6f4ef3bc11abbfad0fa94a75ffb675b7dc158c123471e5de869f9af", size = 131502, upload-time = "2025-11-14T09:50:29.339Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ea/90c1fd00b4aeb704fb5e84cd666b33ffd7f245155048071ffbb51d2bb57d/murmurhash-1.0.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b65a5c4e7f5d71f7ccac2d2b60bdf7092d7976270878cfec59d5a66a533db823", size = 132736, upload-time = "2025-11-14T09:50:30.545Z" }, - { url = "https://files.pythonhosted.org/packages/00/db/da73462dbfa77f6433b128d2120ba7ba300f8c06dc4f4e022c38d240a5f5/murmurhash-1.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:9aba94c5d841e1904cd110e94ceb7f49cfb60a874bbfb27e0373622998fb7c7c", size = 25682, upload-time = "2025-11-14T09:50:31.624Z" }, - { url = "https://files.pythonhosted.org/packages/bb/83/032729ef14971b938fbef41ee125fc8800020ee229bd35178b6ede8ee934/murmurhash-1.0.15-cp313-cp313-win_arm64.whl", hash = "sha256:263807eca40d08c7b702413e45cca75ecb5883aa337237dc5addb660f1483378", size = 23370, upload-time = "2025-11-14T09:50:33.264Z" }, - { url = "https://files.pythonhosted.org/packages/10/83/7547d9205e9bd2f8e5dfd0b682cc9277594f98909f228eb359489baec1df/murmurhash-1.0.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:694fd42a74b7ce257169d14c24aa616aa6cd4ccf8abe50eca0557e08da99d055", size = 29955, upload-time = "2025-11-14T09:50:34.488Z" }, - { url = "https://files.pythonhosted.org/packages/b7/c7/3afd5de7a5b3ae07fe2d3a3271b327ee1489c58ba2b2f2159bd31a25edb9/murmurhash-1.0.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a2ea4546ba426390beff3cd10db8f0152fdc9072c4f2583ec7d8aa9f3e4ac070", size = 30108, upload-time = "2025-11-14T09:50:35.53Z" }, - { url = "https://files.pythonhosted.org/packages/02/69/d6637ee67d78ebb2538c00411f28ea5c154886bbe1db16c49435a8a4ab16/murmurhash-1.0.15-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:34e5a91139c40b10f98d0b297907f5d5267b4b1b2e5dd2eb74a021824f751b98", size = 164054, upload-time = "2025-11-14T09:50:36.591Z" }, - { url = "https://files.pythonhosted.org/packages/ab/4c/89e590165b4c7da6bf941441212a721a270195332d3aacfdfdf527d466ca/murmurhash-1.0.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:dc35606868a5961cf42e79314ca0bddf5a400ce377b14d83192057928d6252ec", size = 168153, upload-time = "2025-11-14T09:50:37.856Z" }, - { url = "https://files.pythonhosted.org/packages/07/7a/95c42df0c21d2e413b9fcd17317a7587351daeb264dc29c6aec1fdbd26f8/murmurhash-1.0.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:43cc6ac3b91ca0f7a5ae9c063ba4d6c26972c97fd7c25280ecc666413e4c5535", size = 164345, upload-time = "2025-11-14T09:50:39.346Z" }, - { url = "https://files.pythonhosted.org/packages/d0/22/9d02c880a88b83bb3ce7d6a38fb727373ab78d82e5f3d8d9fc5612219f90/murmurhash-1.0.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:847d712136cb462f0e4bd6229ee2d9eb996d8854eb8312dff3d20c8f5181fda5", size = 161990, upload-time = "2025-11-14T09:50:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/750232524e0dc262e8dcede6536dafc766faadd9a52f1d23746b02948ad8/murmurhash-1.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:2680851af6901dbe66cc4aa7ef8e263de47e6e1b425ae324caa571bdf18f8d58", size = 28812, upload-time = "2025-11-14T09:50:41.971Z" }, - { url = "https://files.pythonhosted.org/packages/ff/89/4ad9d215ef6ade89f27a72dc4e86b98ef1a43534cc3e6a6900a362a0bf0a/murmurhash-1.0.15-cp313-cp313t-win_arm64.whl", hash = "sha256:189a8de4d657b5da9efd66601b0636330b08262b3a55431f2379097c986995d0", size = 25398, upload-time = "2025-11-14T09:50:43.023Z" }, - { url = "https://files.pythonhosted.org/packages/1c/69/726df275edf07688146966e15eaaa23168100b933a2e1a29b37eb56c6db8/murmurhash-1.0.15-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c4280136b738e85ff76b4bdc4341d0b867ee753e73fd8b6994288080c040d0b", size = 28029, upload-time = "2025-11-14T09:50:44.124Z" }, - { url = "https://files.pythonhosted.org/packages/59/8f/24ecf9061bc2b20933df8aba47c73e904274ea8811c8300cab92f6f82372/murmurhash-1.0.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d4d681f474830489e2ec1d912095cfff027fbaf2baa5414c7e9d25b89f0fab68", size = 27912, upload-time = "2025-11-14T09:50:45.266Z" }, - { url = "https://files.pythonhosted.org/packages/ba/26/fff3caba25aa3c0622114e03c69fb66c839b22335b04d7cce91a3a126d44/murmurhash-1.0.15-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d7e47c5746785db6a43b65fac47b9e63dd71dfbd89a8c92693425b9715e68c6e", size = 131847, upload-time = "2025-11-14T09:50:46.819Z" }, - { url = "https://files.pythonhosted.org/packages/df/e4/0f2b9fc533467a27afb4e906c33f32d5f637477de87dd94690e0c44335a6/murmurhash-1.0.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e8e674f02a99828c8a671ba99cd03299381b2f0744e6f25c29cadfc6151dc724", size = 132267, upload-time = "2025-11-14T09:50:48.298Z" }, - { url = "https://files.pythonhosted.org/packages/da/bf/9d1c107989728ec46e25773d503aa54070b32822a18cfa7f9d5f41bc17a5/murmurhash-1.0.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:26fd7c7855ac4850ad8737991d7b0e3e501df93ebaf0cf45aa5954303085fdba", size = 131894, upload-time = "2025-11-14T09:50:49.485Z" }, - { url = "https://files.pythonhosted.org/packages/0d/81/dcf27c71445c0e993b10e33169a098ca60ee702c5c58fcbde205fa6332a6/murmurhash-1.0.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb8ebafae60d5f892acff533cc599a359954d8c016a829514cb3f6e9ee10f322", size = 132054, upload-time = "2025-11-14T09:50:50.747Z" }, - { url = "https://files.pythonhosted.org/packages/bc/32/e874a14b2d2246bd2d16f80f49fad393a3865d4ee7d66d2cae939a67a29a/murmurhash-1.0.15-cp314-cp314-win_amd64.whl", hash = "sha256:898a629bf111f1aeba4437e533b5b836c0a9d2dd12d6880a9c75f6ca13e30e22", size = 26579, upload-time = "2025-11-14T09:50:52.278Z" }, - { url = "https://files.pythonhosted.org/packages/af/8e/4fca051ed8ae4d23a15aaf0a82b18cb368e8cf84f1e3b474d5749ec46069/murmurhash-1.0.15-cp314-cp314-win_arm64.whl", hash = "sha256:88dc1dd53b7b37c0df1b8b6bce190c12763014492f0269ff7620dc6027f470f4", size = 24341, upload-time = "2025-11-14T09:50:53.295Z" }, - { url = "https://files.pythonhosted.org/packages/38/9c/c72c2a4edd86aac829337ab9f83cf04cdb15e5d503e4c9a3a243f30a261c/murmurhash-1.0.15-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6cb4e962ec4f928b30c271b2d84e6707eff6d942552765b663743cfa618b294b", size = 30146, upload-time = "2025-11-14T09:50:54.705Z" }, - { url = "https://files.pythonhosted.org/packages/ac/d7/72b47ebc86436cd0aa1fd4c6e8779521ec389397ac11389990278d0f7a47/murmurhash-1.0.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5678a3ea4fbf0cbaaca2bed9b445f556f294d5f799c67185d05ffcb221a77faf", size = 30141, upload-time = "2025-11-14T09:50:55.829Z" }, - { url = "https://files.pythonhosted.org/packages/64/bb/6d2f09135079c34dc2d26e961c52742d558b320c61503f273eab6ba743d9/murmurhash-1.0.15-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ef19f38c6b858eef83caf710773db98c8f7eb2193b4c324650c74f3d8ba299e0", size = 163898, upload-time = "2025-11-14T09:50:56.946Z" }, - { url = "https://files.pythonhosted.org/packages/b9/e2/9c1b462e33f9cb2d632056f07c90b502fc20bd7da50a15d0557343bd2fed/murmurhash-1.0.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22aa3ceaedd2e57078b491ed08852d512b84ff4ff9bb2ff3f9bf0eec7f214c9e", size = 168040, upload-time = "2025-11-14T09:50:58.234Z" }, - { url = "https://files.pythonhosted.org/packages/e8/73/8694db1408fcdfa73589f7df6c445437ea146986fa1e393ec60d26d6e30c/murmurhash-1.0.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bba0e0262c0d08682b028cb963ac477bd9839029486fa1333fc5c01fb6072749", size = 164239, upload-time = "2025-11-14T09:50:59.95Z" }, - { url = "https://files.pythonhosted.org/packages/2d/f9/8e360bdfc3c44e267e7e046f0e0b9922766da92da26959a6963f597e6bb5/murmurhash-1.0.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fd8189ee293a09f30f4931408f40c28ccd42d9de4f66595f8814879339378bc", size = 161811, upload-time = "2025-11-14T09:51:01.289Z" }, - { url = "https://files.pythonhosted.org/packages/f9/31/97649680595b1096803d877ababb9a67c07f4378f177ec885eea28b9db6d/murmurhash-1.0.15-cp314-cp314t-win_amd64.whl", hash = "sha256:66395b1388f7daa5103db92debe06842ae3be4c0749ef6db68b444518666cdcc", size = 29817, upload-time = "2025-11-14T09:51:02.493Z" }, - { url = "https://files.pythonhosted.org/packages/76/66/4fce8755f25d77324401886c00017c556be7ca3039575b94037aff905385/murmurhash-1.0.15-cp314-cp314t-win_arm64.whl", hash = "sha256:c22e56c6a0b70598a66e456de5272f76088bc623688da84ef403148a6d41851d", size = 26219, upload-time = "2025-11-14T09:51:03.563Z" }, + { url = "https://files.pythonhosted.org/packages/b6/46/be8522d3456fdccf1b8b049c6d82e7a3c1114c4fc2cfe14b04cba4b3e701/murmurhash-1.0.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d37e3ae44746bca80b1a917c2ea625cf216913564ed43f69d2888e5df97db0cb", size = 27884 }, + { url = "https://files.pythonhosted.org/packages/ed/cc/630449bf4f6178d7daf948ce46ad00b25d279065fc30abd8d706be3d87e0/murmurhash-1.0.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0861cb11039409eaf46878456b7d985ef17b6b484103a6fc367b2ecec846891d", size = 27855 }, + { url = "https://files.pythonhosted.org/packages/ff/30/ea8f601a9bf44db99468696efd59eb9cff1157cd55cb586d67116697583f/murmurhash-1.0.15-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5a301decfaccfec70fe55cb01dde2a012c3014a874542eaa7cc73477bb749616", size = 134088 }, + { url = "https://files.pythonhosted.org/packages/c9/de/c40ce8c0877d406691e735b8d6e9c815f36a82b499d358313db5dbe219d7/murmurhash-1.0.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32c6fde7bd7e9407003370a07b5f4addacabe1556ad3dc2cac246b7a2bba3400", size = 133978 }, + { url = "https://files.pythonhosted.org/packages/47/84/bd49963ecd84ebab2fe66595e2d1ed41d5e8b5153af5dc930f0bd827007c/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d8b43a7011540dc3c7ce66f2134df9732e2bc3bbb4a35f6458bc755e48bde26", size = 132956 }, + { url = "https://files.pythonhosted.org/packages/4f/7c/2530769c545074417c862583f05f4245644599f1e9ff619b3dfe2969aafc/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43bf4541892ecd95963fcd307bf1c575fc0fee1682f41c93007adee71ca2bb40", size = 134184 }, + { url = "https://files.pythonhosted.org/packages/84/a4/b249b042f5afe34d14ada2dc4afc777e883c15863296756179652e081c44/murmurhash-1.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:f4ac15a2089dc42e6eb0966622d42d2521590a12c92480aafecf34c085302cca", size = 25647 }, + { url = "https://files.pythonhosted.org/packages/13/bf/028179259aebc18fd4ba5cae2601d1d47517427a537ab44336446431a215/murmurhash-1.0.15-cp312-cp312-win_arm64.whl", hash = "sha256:4a70ca4ae19e600d9be3da64d00710e79dde388a4d162f22078d64844d0ebdda", size = 23338 }, + { url = "https://files.pythonhosted.org/packages/29/2f/ba300b5f04dae0409202d6285668b8a9d3ade43a846abee3ef611cb388d5/murmurhash-1.0.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fe50dc70e52786759358fd1471e309b94dddfffb9320d9dfea233c7684c894ba", size = 27861 }, + { url = "https://files.pythonhosted.org/packages/34/02/29c19d268e6f4ea1ed2a462c901eed1ed35b454e2cbc57da592fad663ac6/murmurhash-1.0.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1349a7c23f6092e7998ddc5bd28546cc31a595afc61e9fdb3afc423feec3d7ad", size = 27840 }, + { url = "https://files.pythonhosted.org/packages/e2/63/58e2de2b5232cd294c64092688c422196e74f9fa8b3958bdf02d33df24b9/murmurhash-1.0.15-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ba6d05de2613535b5a9227d4ad8ef40a540465f64660d4a8800634ae10e04f", size = 133080 }, + { url = "https://files.pythonhosted.org/packages/aa/9a/d13e2e9f8ba1ced06840921a50f7cece0a475453284158a3018b72679761/murmurhash-1.0.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fa1b70b3cc2801ab44179c65827bbd12009c68b34e9d9ce7125b6a0bd35af63c", size = 132648 }, + { url = "https://files.pythonhosted.org/packages/b2/e1/47994f1813fa205c84977b0ff51ae6709f8539af052c7491a5f863d82bdc/murmurhash-1.0.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:213d710fb6f4ef3bc11abbfad0fa94a75ffb675b7dc158c123471e5de869f9af", size = 131502 }, + { url = "https://files.pythonhosted.org/packages/b9/ea/90c1fd00b4aeb704fb5e84cd666b33ffd7f245155048071ffbb51d2bb57d/murmurhash-1.0.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b65a5c4e7f5d71f7ccac2d2b60bdf7092d7976270878cfec59d5a66a533db823", size = 132736 }, + { url = "https://files.pythonhosted.org/packages/00/db/da73462dbfa77f6433b128d2120ba7ba300f8c06dc4f4e022c38d240a5f5/murmurhash-1.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:9aba94c5d841e1904cd110e94ceb7f49cfb60a874bbfb27e0373622998fb7c7c", size = 25682 }, + { url = "https://files.pythonhosted.org/packages/bb/83/032729ef14971b938fbef41ee125fc8800020ee229bd35178b6ede8ee934/murmurhash-1.0.15-cp313-cp313-win_arm64.whl", hash = "sha256:263807eca40d08c7b702413e45cca75ecb5883aa337237dc5addb660f1483378", size = 23370 }, + { url = "https://files.pythonhosted.org/packages/10/83/7547d9205e9bd2f8e5dfd0b682cc9277594f98909f228eb359489baec1df/murmurhash-1.0.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:694fd42a74b7ce257169d14c24aa616aa6cd4ccf8abe50eca0557e08da99d055", size = 29955 }, + { url = "https://files.pythonhosted.org/packages/b7/c7/3afd5de7a5b3ae07fe2d3a3271b327ee1489c58ba2b2f2159bd31a25edb9/murmurhash-1.0.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a2ea4546ba426390beff3cd10db8f0152fdc9072c4f2583ec7d8aa9f3e4ac070", size = 30108 }, + { url = "https://files.pythonhosted.org/packages/02/69/d6637ee67d78ebb2538c00411f28ea5c154886bbe1db16c49435a8a4ab16/murmurhash-1.0.15-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:34e5a91139c40b10f98d0b297907f5d5267b4b1b2e5dd2eb74a021824f751b98", size = 164054 }, + { url = "https://files.pythonhosted.org/packages/ab/4c/89e590165b4c7da6bf941441212a721a270195332d3aacfdfdf527d466ca/murmurhash-1.0.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:dc35606868a5961cf42e79314ca0bddf5a400ce377b14d83192057928d6252ec", size = 168153 }, + { url = "https://files.pythonhosted.org/packages/07/7a/95c42df0c21d2e413b9fcd17317a7587351daeb264dc29c6aec1fdbd26f8/murmurhash-1.0.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:43cc6ac3b91ca0f7a5ae9c063ba4d6c26972c97fd7c25280ecc666413e4c5535", size = 164345 }, + { url = "https://files.pythonhosted.org/packages/d0/22/9d02c880a88b83bb3ce7d6a38fb727373ab78d82e5f3d8d9fc5612219f90/murmurhash-1.0.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:847d712136cb462f0e4bd6229ee2d9eb996d8854eb8312dff3d20c8f5181fda5", size = 161990 }, + { url = "https://files.pythonhosted.org/packages/9a/e3/750232524e0dc262e8dcede6536dafc766faadd9a52f1d23746b02948ad8/murmurhash-1.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:2680851af6901dbe66cc4aa7ef8e263de47e6e1b425ae324caa571bdf18f8d58", size = 28812 }, + { url = "https://files.pythonhosted.org/packages/ff/89/4ad9d215ef6ade89f27a72dc4e86b98ef1a43534cc3e6a6900a362a0bf0a/murmurhash-1.0.15-cp313-cp313t-win_arm64.whl", hash = "sha256:189a8de4d657b5da9efd66601b0636330b08262b3a55431f2379097c986995d0", size = 25398 }, + { url = "https://files.pythonhosted.org/packages/1c/69/726df275edf07688146966e15eaaa23168100b933a2e1a29b37eb56c6db8/murmurhash-1.0.15-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c4280136b738e85ff76b4bdc4341d0b867ee753e73fd8b6994288080c040d0b", size = 28029 }, + { url = "https://files.pythonhosted.org/packages/59/8f/24ecf9061bc2b20933df8aba47c73e904274ea8811c8300cab92f6f82372/murmurhash-1.0.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d4d681f474830489e2ec1d912095cfff027fbaf2baa5414c7e9d25b89f0fab68", size = 27912 }, + { url = "https://files.pythonhosted.org/packages/ba/26/fff3caba25aa3c0622114e03c69fb66c839b22335b04d7cce91a3a126d44/murmurhash-1.0.15-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d7e47c5746785db6a43b65fac47b9e63dd71dfbd89a8c92693425b9715e68c6e", size = 131847 }, + { url = "https://files.pythonhosted.org/packages/df/e4/0f2b9fc533467a27afb4e906c33f32d5f637477de87dd94690e0c44335a6/murmurhash-1.0.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e8e674f02a99828c8a671ba99cd03299381b2f0744e6f25c29cadfc6151dc724", size = 132267 }, + { url = "https://files.pythonhosted.org/packages/da/bf/9d1c107989728ec46e25773d503aa54070b32822a18cfa7f9d5f41bc17a5/murmurhash-1.0.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:26fd7c7855ac4850ad8737991d7b0e3e501df93ebaf0cf45aa5954303085fdba", size = 131894 }, + { url = "https://files.pythonhosted.org/packages/0d/81/dcf27c71445c0e993b10e33169a098ca60ee702c5c58fcbde205fa6332a6/murmurhash-1.0.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb8ebafae60d5f892acff533cc599a359954d8c016a829514cb3f6e9ee10f322", size = 132054 }, + { url = "https://files.pythonhosted.org/packages/bc/32/e874a14b2d2246bd2d16f80f49fad393a3865d4ee7d66d2cae939a67a29a/murmurhash-1.0.15-cp314-cp314-win_amd64.whl", hash = "sha256:898a629bf111f1aeba4437e533b5b836c0a9d2dd12d6880a9c75f6ca13e30e22", size = 26579 }, + { url = "https://files.pythonhosted.org/packages/af/8e/4fca051ed8ae4d23a15aaf0a82b18cb368e8cf84f1e3b474d5749ec46069/murmurhash-1.0.15-cp314-cp314-win_arm64.whl", hash = "sha256:88dc1dd53b7b37c0df1b8b6bce190c12763014492f0269ff7620dc6027f470f4", size = 24341 }, + { url = "https://files.pythonhosted.org/packages/38/9c/c72c2a4edd86aac829337ab9f83cf04cdb15e5d503e4c9a3a243f30a261c/murmurhash-1.0.15-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:6cb4e962ec4f928b30c271b2d84e6707eff6d942552765b663743cfa618b294b", size = 30146 }, + { url = "https://files.pythonhosted.org/packages/ac/d7/72b47ebc86436cd0aa1fd4c6e8779521ec389397ac11389990278d0f7a47/murmurhash-1.0.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5678a3ea4fbf0cbaaca2bed9b445f556f294d5f799c67185d05ffcb221a77faf", size = 30141 }, + { url = "https://files.pythonhosted.org/packages/64/bb/6d2f09135079c34dc2d26e961c52742d558b320c61503f273eab6ba743d9/murmurhash-1.0.15-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ef19f38c6b858eef83caf710773db98c8f7eb2193b4c324650c74f3d8ba299e0", size = 163898 }, + { url = "https://files.pythonhosted.org/packages/b9/e2/9c1b462e33f9cb2d632056f07c90b502fc20bd7da50a15d0557343bd2fed/murmurhash-1.0.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22aa3ceaedd2e57078b491ed08852d512b84ff4ff9bb2ff3f9bf0eec7f214c9e", size = 168040 }, + { url = "https://files.pythonhosted.org/packages/e8/73/8694db1408fcdfa73589f7df6c445437ea146986fa1e393ec60d26d6e30c/murmurhash-1.0.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bba0e0262c0d08682b028cb963ac477bd9839029486fa1333fc5c01fb6072749", size = 164239 }, + { url = "https://files.pythonhosted.org/packages/2d/f9/8e360bdfc3c44e267e7e046f0e0b9922766da92da26959a6963f597e6bb5/murmurhash-1.0.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fd8189ee293a09f30f4931408f40c28ccd42d9de4f66595f8814879339378bc", size = 161811 }, + { url = "https://files.pythonhosted.org/packages/f9/31/97649680595b1096803d877ababb9a67c07f4378f177ec885eea28b9db6d/murmurhash-1.0.15-cp314-cp314t-win_amd64.whl", hash = "sha256:66395b1388f7daa5103db92debe06842ae3be4c0749ef6db68b444518666cdcc", size = 29817 }, + { url = "https://files.pythonhosted.org/packages/76/66/4fce8755f25d77324401886c00017c556be7ca3039575b94037aff905385/murmurhash-1.0.15-cp314-cp314t-win_arm64.whl", hash = "sha256:c22e56c6a0b70598a66e456de5272f76088bc623688da84ef403148a6d41851d", size = 26219 }, ] [[package]] name = "mypy-extensions" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, ] [[package]] name = "nest-asyncio" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, ] [[package]] name = "networkx" version = "3.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504 }, ] [[package]] name = "nh3" version = "0.3.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/86/f8d3a7c9bd1bbaa181f6312c757e0b74d25f71ecf84ea3c0dc5e0f01840d/nh3-0.3.4.tar.gz", hash = "sha256:96709a379997c1b28c8974146ca660b0dcd3794f4f6d50c1ea549bab39ac6ade", size = 19520, upload-time = "2026-03-25T10:57:30.789Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/86/f8d3a7c9bd1bbaa181f6312c757e0b74d25f71ecf84ea3c0dc5e0f01840d/nh3-0.3.4.tar.gz", hash = "sha256:96709a379997c1b28c8974146ca660b0dcd3794f4f6d50c1ea549bab39ac6ade", size = 19520 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/5e/c400663d14be2216bc084ed2befc871b7b12563f85d40904f2a4bf0dd2b7/nh3-0.3.4-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8b61058f34c2105d44d2a4d4241bacf603a1ef5c143b08766bbd0cf23830118f", size = 1417991, upload-time = "2026-03-25T10:56:59.13Z" }, - { url = "https://files.pythonhosted.org/packages/36/f5/109526f5002ec41322ac8cafd50f0f154bae0c26b9607c0fcb708bdca8ec/nh3-0.3.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:554cc2bab281758e94d770c3fb0bf2d8be5fb403ef6b2e8841dd7c1615df7a0f", size = 790566, upload-time = "2026-03-25T10:57:00.445Z" }, - { url = "https://files.pythonhosted.org/packages/7b/66/38950f2b4b316ffd82ee51ed8f9143d1f56fdd620312cacc91613b77b3e7/nh3-0.3.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dbe76feaa44e2ef9436f345016012a591550e77818876a8de5c8bc2a248e08df", size = 837538, upload-time = "2026-03-25T10:57:01.848Z" }, - { url = "https://files.pythonhosted.org/packages/d8/9f/9d6da970e9524fe360ea02a2082856390c2c8ba540409d1be6e5851887b3/nh3-0.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:87dac8d611b4a478400e0821a13b35770e88c266582f065e7249d6a37b0f86e8", size = 1012154, upload-time = "2026-03-25T10:57:03.592Z" }, - { url = "https://files.pythonhosted.org/packages/54/92/7c85c33c241e9dd51dda115bd3f765e940446588cdaaca62ef8edffe675f/nh3-0.3.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8d697e19f2995b337f648204848ac3a528eaafffc39e7ce4ac6b7a2fbe6c84af", size = 1092516, upload-time = "2026-03-25T10:57:04.726Z" }, - { url = "https://files.pythonhosted.org/packages/16/0f/597842bdb2890999a3faa2f3fcb02db8aa6ad09320d3d843ff6d0a1f737b/nh3-0.3.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:7cae217f031809321db962cd7e092bda8d4e95a87f78c0226628fa6c2ea8ebc5", size = 1053793, upload-time = "2026-03-25T10:57:06.171Z" }, - { url = "https://files.pythonhosted.org/packages/7d/32/669da65147bc10746d2e1d7a8a3dbfbffe0315f419e74b559e2ee3471a01/nh3-0.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:07999b998bf89692738f15c0eac76a416382932f855709e0b7488b595c30ec89", size = 1035975, upload-time = "2026-03-25T10:57:07.292Z" }, - { url = "https://files.pythonhosted.org/packages/a1/7e/9e97a8b3c5161c79b4bf21cc54e9334860a52cc54ede15bf2239ef494b73/nh3-0.3.4-cp314-cp314t-win32.whl", hash = "sha256:ca90397c8d36c1535bf1988b2bed006597337843a164c7ec269dc8813f37536b", size = 600419, upload-time = "2026-03-25T10:57:08.342Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c7/6849d8d4295d3997d148eacb2d4b1c9faada4895ee3c1b1e12e72f4611e2/nh3-0.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:41e46b3499918ab6128b6421677b316e79869d0c140da24069d220a94f4e72d1", size = 613342, upload-time = "2026-03-25T10:57:09.593Z" }, - { url = "https://files.pythonhosted.org/packages/8b/0e/14a3f510f36c20b922c123a2730f071f938d006fb513aacfd46d6cbc03a7/nh3-0.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:80b955d802bf365bd42e09f6c3d64567dce777d20e97968d94b3e9d9e99b265e", size = 607025, upload-time = "2026-03-25T10:57:10.959Z" }, - { url = "https://files.pythonhosted.org/packages/4a/57/a97955bc95960cfb1f0517043d60a121f4ba93fde252d4d9ffd3c2a9eead/nh3-0.3.4-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d8bebcb20ab4b91858385cd98fe58046ec4a624275b45ef9b976475604f45b49", size = 1439519, upload-time = "2026-03-25T10:57:12.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/60/c9a33361da8cde7c7760f091cd10467bc470634e4eea31c8bb70935b00a4/nh3-0.3.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d825722a1e8cbc87d7ca1e47ffb1d2a6cf343ad4c1b8465becf7cadcabcdfd0", size = 833798, upload-time = "2026-03-25T10:57:13.264Z" }, - { url = "https://files.pythonhosted.org/packages/6b/19/9487790780b8c94eacca37866c1270b747a4af8e244d43b3b550fddbbf62/nh3-0.3.4-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4aa8b43e68c26b68069a3b6cef09de166d1d7fa140cf8d77e409a46cbf742e44", size = 820414, upload-time = "2026-03-25T10:57:14.236Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b4/c6a340dd321d20b1e4a663307032741da045685c87403926c43656f6f5ec/nh3-0.3.4-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f5f214618ad5eff4f2a6b13a8d4da4d9e7f37c569d90a13fb9f0caaf7d04fe21", size = 1061531, upload-time = "2026-03-25T10:57:15.384Z" }, - { url = "https://files.pythonhosted.org/packages/c4/49/f6b4b474e0032e4bcbb7174b44e4cf6915670e09c62421deb06ccfcb88b8/nh3-0.3.4-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3390e4333883673a684ce16c1716b481e91782d6f56dec5c85fed9feedb23382", size = 1021889, upload-time = "2026-03-25T10:57:16.454Z" }, - { url = "https://files.pythonhosted.org/packages/43/da/e52a6941746d1f974752af3fc8591f1dbcdcf7fd8c726c7d99f444ba820e/nh3-0.3.4-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a2e44ccb29cbb45071b8f3f2dab9ebfb41a6516f328f91f1f1fd18196239a4", size = 912965, upload-time = "2026-03-25T10:57:17.624Z" }, - { url = "https://files.pythonhosted.org/packages/d6/b7/ec1cbc6b297a808c513f59f501656389623fc09ad6a58c640851289c7854/nh3-0.3.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0961a27dc2057c38d0364cb05880e1997ae1c80220cbc847db63213720b8f304", size = 804975, upload-time = "2026-03-25T10:57:18.994Z" }, - { url = "https://files.pythonhosted.org/packages/a9/56/b1275aa2c6510191eed76178da4626b0900402439cb9f27d6b9bf7c6d5e9/nh3-0.3.4-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:9337517edb7c10228252cce2898e20fb3d77e32ffaccbb3c66897927d74215a0", size = 833400, upload-time = "2026-03-25T10:57:20.086Z" }, - { url = "https://files.pythonhosted.org/packages/7c/a5/5d574ffa3c6e49a5364d1b25ebad165501c055340056671493beb467a15e/nh3-0.3.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d866701affe67a5171b916b5c076e767a74c6a9efb7fb2006eb8d3c5f9a293d5", size = 854277, upload-time = "2026-03-25T10:57:21.433Z" }, - { url = "https://files.pythonhosted.org/packages/79/36/8aeb2ab21517cefa212db109e41024e02650716cb42bf293d0a88437a92d/nh3-0.3.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:47d749d99ae005ab19517224140b280dd56e77b33afb82f9b600e106d0458003", size = 1022021, upload-time = "2026-03-25T10:57:22.433Z" }, - { url = "https://files.pythonhosted.org/packages/9c/95/9fd860997685e64abe2d5a995ca2eb5004c0fb6d6585429612a7871548b9/nh3-0.3.4-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f987cb56458323405e8e5ea827e1befcf141ffa0c0ac797d6d02e6b646056d9a", size = 1103526, upload-time = "2026-03-25T10:57:23.487Z" }, - { url = "https://files.pythonhosted.org/packages/7d/0d/df545070614c1007f0109bb004230226c9000e7857c9785583ec25cda9d7/nh3-0.3.4-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:883d5a6d6ee8078c4afc8e96e022fe579c4c265775ff6ee21e39b8c542cabab3", size = 1068050, upload-time = "2026-03-25T10:57:24.624Z" }, - { url = "https://files.pythonhosted.org/packages/94/d5/17b016df52df052f714c53be71df26a1943551d9931e9383b92c998b88f8/nh3-0.3.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:75643c22f5092d8e209f766ee8108c400bc1e44760fc94d2d638eb138d18f853", size = 1046037, upload-time = "2026-03-25T10:57:25.799Z" }, - { url = "https://files.pythonhosted.org/packages/51/39/49f737907e6ab2b4ca71855d3bd63dd7958862e9c8b94fb4e5b18ccf6988/nh3-0.3.4-cp38-abi3-win32.whl", hash = "sha256:72e4e9ca1c4bd41b4a28b0190edc2e21e3f71496acd36a0162858e1a28db3d7e", size = 609542, upload-time = "2026-03-25T10:57:27.112Z" }, - { url = "https://files.pythonhosted.org/packages/73/4f/af8e9071d7464575a7316831938237ffc9d92d27f163dbdd964b1309cd9b/nh3-0.3.4-cp38-abi3-win_amd64.whl", hash = "sha256:c10b1f0c741e257a5cb2978d6bac86e7c784ab20572724b20c6402c2e24bce75", size = 624244, upload-time = "2026-03-25T10:57:28.302Z" }, - { url = "https://files.pythonhosted.org/packages/44/0c/37695d6b0168f6714b5c492331636a9e6123d6ec22d25876c68d06eab1b8/nh3-0.3.4-cp38-abi3-win_arm64.whl", hash = "sha256:43ad4eedee7e049b9069bc015b7b095d320ed6d167ecec111f877de1540656e9", size = 616649, upload-time = "2026-03-25T10:57:29.623Z" }, + { url = "https://files.pythonhosted.org/packages/fd/5e/c400663d14be2216bc084ed2befc871b7b12563f85d40904f2a4bf0dd2b7/nh3-0.3.4-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:8b61058f34c2105d44d2a4d4241bacf603a1ef5c143b08766bbd0cf23830118f", size = 1417991 }, + { url = "https://files.pythonhosted.org/packages/36/f5/109526f5002ec41322ac8cafd50f0f154bae0c26b9607c0fcb708bdca8ec/nh3-0.3.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:554cc2bab281758e94d770c3fb0bf2d8be5fb403ef6b2e8841dd7c1615df7a0f", size = 790566 }, + { url = "https://files.pythonhosted.org/packages/7b/66/38950f2b4b316ffd82ee51ed8f9143d1f56fdd620312cacc91613b77b3e7/nh3-0.3.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dbe76feaa44e2ef9436f345016012a591550e77818876a8de5c8bc2a248e08df", size = 837538 }, + { url = "https://files.pythonhosted.org/packages/d8/9f/9d6da970e9524fe360ea02a2082856390c2c8ba540409d1be6e5851887b3/nh3-0.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:87dac8d611b4a478400e0821a13b35770e88c266582f065e7249d6a37b0f86e8", size = 1012154 }, + { url = "https://files.pythonhosted.org/packages/54/92/7c85c33c241e9dd51dda115bd3f765e940446588cdaaca62ef8edffe675f/nh3-0.3.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8d697e19f2995b337f648204848ac3a528eaafffc39e7ce4ac6b7a2fbe6c84af", size = 1092516 }, + { url = "https://files.pythonhosted.org/packages/16/0f/597842bdb2890999a3faa2f3fcb02db8aa6ad09320d3d843ff6d0a1f737b/nh3-0.3.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:7cae217f031809321db962cd7e092bda8d4e95a87f78c0226628fa6c2ea8ebc5", size = 1053793 }, + { url = "https://files.pythonhosted.org/packages/7d/32/669da65147bc10746d2e1d7a8a3dbfbffe0315f419e74b559e2ee3471a01/nh3-0.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:07999b998bf89692738f15c0eac76a416382932f855709e0b7488b595c30ec89", size = 1035975 }, + { url = "https://files.pythonhosted.org/packages/a1/7e/9e97a8b3c5161c79b4bf21cc54e9334860a52cc54ede15bf2239ef494b73/nh3-0.3.4-cp314-cp314t-win32.whl", hash = "sha256:ca90397c8d36c1535bf1988b2bed006597337843a164c7ec269dc8813f37536b", size = 600419 }, + { url = "https://files.pythonhosted.org/packages/e0/c7/6849d8d4295d3997d148eacb2d4b1c9faada4895ee3c1b1e12e72f4611e2/nh3-0.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:41e46b3499918ab6128b6421677b316e79869d0c140da24069d220a94f4e72d1", size = 613342 }, + { url = "https://files.pythonhosted.org/packages/8b/0e/14a3f510f36c20b922c123a2730f071f938d006fb513aacfd46d6cbc03a7/nh3-0.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:80b955d802bf365bd42e09f6c3d64567dce777d20e97968d94b3e9d9e99b265e", size = 607025 }, + { url = "https://files.pythonhosted.org/packages/4a/57/a97955bc95960cfb1f0517043d60a121f4ba93fde252d4d9ffd3c2a9eead/nh3-0.3.4-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d8bebcb20ab4b91858385cd98fe58046ec4a624275b45ef9b976475604f45b49", size = 1439519 }, + { url = "https://files.pythonhosted.org/packages/2b/60/c9a33361da8cde7c7760f091cd10467bc470634e4eea31c8bb70935b00a4/nh3-0.3.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d825722a1e8cbc87d7ca1e47ffb1d2a6cf343ad4c1b8465becf7cadcabcdfd0", size = 833798 }, + { url = "https://files.pythonhosted.org/packages/6b/19/9487790780b8c94eacca37866c1270b747a4af8e244d43b3b550fddbbf62/nh3-0.3.4-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4aa8b43e68c26b68069a3b6cef09de166d1d7fa140cf8d77e409a46cbf742e44", size = 820414 }, + { url = "https://files.pythonhosted.org/packages/6b/b4/c6a340dd321d20b1e4a663307032741da045685c87403926c43656f6f5ec/nh3-0.3.4-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f5f214618ad5eff4f2a6b13a8d4da4d9e7f37c569d90a13fb9f0caaf7d04fe21", size = 1061531 }, + { url = "https://files.pythonhosted.org/packages/c4/49/f6b4b474e0032e4bcbb7174b44e4cf6915670e09c62421deb06ccfcb88b8/nh3-0.3.4-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3390e4333883673a684ce16c1716b481e91782d6f56dec5c85fed9feedb23382", size = 1021889 }, + { url = "https://files.pythonhosted.org/packages/43/da/e52a6941746d1f974752af3fc8591f1dbcdcf7fd8c726c7d99f444ba820e/nh3-0.3.4-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a2e44ccb29cbb45071b8f3f2dab9ebfb41a6516f328f91f1f1fd18196239a4", size = 912965 }, + { url = "https://files.pythonhosted.org/packages/d6/b7/ec1cbc6b297a808c513f59f501656389623fc09ad6a58c640851289c7854/nh3-0.3.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0961a27dc2057c38d0364cb05880e1997ae1c80220cbc847db63213720b8f304", size = 804975 }, + { url = "https://files.pythonhosted.org/packages/a9/56/b1275aa2c6510191eed76178da4626b0900402439cb9f27d6b9bf7c6d5e9/nh3-0.3.4-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:9337517edb7c10228252cce2898e20fb3d77e32ffaccbb3c66897927d74215a0", size = 833400 }, + { url = "https://files.pythonhosted.org/packages/7c/a5/5d574ffa3c6e49a5364d1b25ebad165501c055340056671493beb467a15e/nh3-0.3.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d866701affe67a5171b916b5c076e767a74c6a9efb7fb2006eb8d3c5f9a293d5", size = 854277 }, + { url = "https://files.pythonhosted.org/packages/79/36/8aeb2ab21517cefa212db109e41024e02650716cb42bf293d0a88437a92d/nh3-0.3.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:47d749d99ae005ab19517224140b280dd56e77b33afb82f9b600e106d0458003", size = 1022021 }, + { url = "https://files.pythonhosted.org/packages/9c/95/9fd860997685e64abe2d5a995ca2eb5004c0fb6d6585429612a7871548b9/nh3-0.3.4-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f987cb56458323405e8e5ea827e1befcf141ffa0c0ac797d6d02e6b646056d9a", size = 1103526 }, + { url = "https://files.pythonhosted.org/packages/7d/0d/df545070614c1007f0109bb004230226c9000e7857c9785583ec25cda9d7/nh3-0.3.4-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:883d5a6d6ee8078c4afc8e96e022fe579c4c265775ff6ee21e39b8c542cabab3", size = 1068050 }, + { url = "https://files.pythonhosted.org/packages/94/d5/17b016df52df052f714c53be71df26a1943551d9931e9383b92c998b88f8/nh3-0.3.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:75643c22f5092d8e209f766ee8108c400bc1e44760fc94d2d638eb138d18f853", size = 1046037 }, + { url = "https://files.pythonhosted.org/packages/51/39/49f737907e6ab2b4ca71855d3bd63dd7958862e9c8b94fb4e5b18ccf6988/nh3-0.3.4-cp38-abi3-win32.whl", hash = "sha256:72e4e9ca1c4bd41b4a28b0190edc2e21e3f71496acd36a0162858e1a28db3d7e", size = 609542 }, + { url = "https://files.pythonhosted.org/packages/73/4f/af8e9071d7464575a7316831938237ffc9d92d27f163dbdd964b1309cd9b/nh3-0.3.4-cp38-abi3-win_amd64.whl", hash = "sha256:c10b1f0c741e257a5cb2978d6bac86e7c784ab20572724b20c6402c2e24bce75", size = 624244 }, + { url = "https://files.pythonhosted.org/packages/44/0c/37695d6b0168f6714b5c492331636a9e6123d6ec22d25876c68d06eab1b8/nh3-0.3.4-cp38-abi3-win_arm64.whl", hash = "sha256:43ad4eedee7e049b9069bc015b7b095d320ed6d167ecec111f877de1540656e9", size = 616649 }, ] [[package]] @@ -4723,9 +4715,9 @@ dependencies = [ { name = "regex" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/74/a1/b3b4adf15585a5bc4c357adde150c01ebeeb642173ded4d871e89468767c/nltk-3.9.4.tar.gz", hash = "sha256:ed03bc098a40481310320808b2db712d95d13ca65b27372f8a403949c8b523d0", size = 2946864, upload-time = "2026-03-24T06:13:40.641Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/a1/b3b4adf15585a5bc4c357adde150c01ebeeb642173ded4d871e89468767c/nltk-3.9.4.tar.gz", hash = "sha256:ed03bc098a40481310320808b2db712d95d13ca65b27372f8a403949c8b523d0", size = 2946864 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/91/04e965f8e717ba0ab4bdca5c112deeab11c9e750d94c4d4602f050295d39/nltk-3.9.4-py3-none-any.whl", hash = "sha256:f2fa301c3a12718ce4a0e9305c5675299da5ad9e26068218b69d692fda84828f", size = 1552087, upload-time = "2026-03-24T06:13:38.47Z" }, + { url = "https://files.pythonhosted.org/packages/9d/91/04e965f8e717ba0ab4bdca5c112deeab11c9e750d94c4d4602f050295d39/nltk-3.9.4-py3-none-any.whl", hash = "sha256:f2fa301c3a12718ce4a0e9305c5675299da5ad9e26068218b69d692fda84828f", size = 1552087 }, ] [[package]] @@ -4735,9 +4727,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/39/60afcbc0148c3dafaaefe851ae3f058077db49d66288dfb218a11a57b997/notion_client-3.0.0.tar.gz", hash = "sha256:05c4d2b4fa3491dc0de21c9c826277202ea8b8714077ee7f51a6e1a09ab23d0f", size = 31357, upload-time = "2026-02-16T11:15:48.024Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/39/60afcbc0148c3dafaaefe851ae3f058077db49d66288dfb218a11a57b997/notion_client-3.0.0.tar.gz", hash = "sha256:05c4d2b4fa3491dc0de21c9c826277202ea8b8714077ee7f51a6e1a09ab23d0f", size = 31357 } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/ce/6b03f9aedd2edfcc28e23ced5c2582d543f6ddbb2be5c570533f02890b27/notion_client-3.0.0-py2.py3-none-any.whl", hash = "sha256:177fc3d2ace7e8ef69cf96f46269e8a66071c2c7c526194bf06ce7925853e759", size = 18746, upload-time = "2026-02-16T11:15:46.602Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ce/6b03f9aedd2edfcc28e23ced5c2582d543f6ddbb2be5c570533f02890b27/notion_client-3.0.0-py2.py3-none-any.whl", hash = "sha256:177fc3d2ace7e8ef69cf96f46269e8a66071c2c7c526194bf06ce7925853e759", size = 18746 }, ] [[package]] @@ -4748,9 +4740,9 @@ dependencies = [ { name = "mistune" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/c1/7614cd3ce5df401209051808e3c59c4959b52641a2e6c86c64184d754a49/notion_markdown-0.7.0.tar.gz", hash = "sha256:941b3a5b04eee991d971114a9edc09b0935c6ab79f987a6f4999774d643fe362", size = 42558, upload-time = "2026-02-13T05:48:21.974Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/c1/7614cd3ce5df401209051808e3c59c4959b52641a2e6c86c64184d754a49/notion_markdown-0.7.0.tar.gz", hash = "sha256:941b3a5b04eee991d971114a9edc09b0935c6ab79f987a6f4999774d643fe362", size = 42558 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/85/6e0f302211bd615b16bd8431d2855225b9b2901b84b6d6785c730a9a4a91/notion_markdown-0.7.0-py3-none-any.whl", hash = "sha256:ae0c38ec73e114f8b6a367bd81eca0640b2af366ac2ea117028080c713ea6ea9", size = 23355, upload-time = "2026-02-13T05:48:20.369Z" }, + { url = "https://files.pythonhosted.org/packages/5d/85/6e0f302211bd615b16bd8431d2855225b9b2901b84b6d6785c730a9a4a91/notion_markdown-0.7.0-py3-none-any.whl", hash = "sha256:ae0c38ec73e114f8b6a367bd81eca0640b2af366ac2ea117028080c713ea6ea9", size = 23355 }, ] [[package]] @@ -4760,9 +4752,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docopt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f6/58/ad645bd38b4b648eb2fc2ba1b909398e54eb0cbb6a7dbd2b4953e38c9621/num2words-0.5.14.tar.gz", hash = "sha256:b066ec18e56b6616a3b38086b5747daafbaa8868b226a36127e0451c0cf379c6", size = 218213, upload-time = "2024-12-17T20:17:10.191Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/58/ad645bd38b4b648eb2fc2ba1b909398e54eb0cbb6a7dbd2b4953e38c9621/num2words-0.5.14.tar.gz", hash = "sha256:b066ec18e56b6616a3b38086b5747daafbaa8868b226a36127e0451c0cf379c6", size = 218213 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/5b/545e9267a1cc080c8a1be2746113a063e34bcdd0f5173fd665a5c13cb234/num2words-0.5.14-py3-none-any.whl", hash = "sha256:1c8e5b00142fc2966fd8d685001e36c4a9911e070d1b120e1beb721fa1edb33d", size = 163525, upload-time = "2024-12-17T20:17:06.074Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5b/545e9267a1cc080c8a1be2746113a063e34bcdd0f5173fd665a5c13cb234/num2words-0.5.14-py3-none-any.whl", hash = "sha256:1c8e5b00142fc2966fd8d685001e36c4a9911e070d1b120e1beb721fa1edb33d", size = 163525 }, ] [[package]] @@ -4773,81 +4765,81 @@ dependencies = [ { name = "llvmlite" }, { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/c9/a0fb41787d01d621046138da30f6c2100d80857bf34b3390dd68040f27a3/numba-0.64.0.tar.gz", hash = "sha256:95e7300af648baa3308127b1955b52ce6d11889d16e8cfe637b4f85d2fca52b1", size = 2765679, upload-time = "2026-02-18T18:41:20.974Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/c9/a0fb41787d01d621046138da30f6c2100d80857bf34b3390dd68040f27a3/numba-0.64.0.tar.gz", hash = "sha256:95e7300af648baa3308127b1955b52ce6d11889d16e8cfe637b4f85d2fca52b1", size = 2765679 } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/a6/9fc52cb4f0d5e6d8b5f4d81615bc01012e3cf24e1052a60f17a68deb8092/numba-0.64.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69440a8e8bc1a81028446f06b363e28635aa67bd51b1e498023f03b812e0ce68", size = 2683418, upload-time = "2026-02-18T18:40:59.886Z" }, - { url = "https://files.pythonhosted.org/packages/9b/89/1a74ea99b180b7a5587b0301ed1b183a2937c4b4b67f7994689b5d36fc34/numba-0.64.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13721011f693ba558b8dd4e4db7f2640462bba1b855bdc804be45bbeb55031a", size = 3804087, upload-time = "2026-02-18T18:41:01.699Z" }, - { url = "https://files.pythonhosted.org/packages/91/e1/583c647404b15f807410510fec1eb9b80cb8474165940b7749f026f21cbc/numba-0.64.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0b180b1133f2b5d8b3f09d96b6d7a9e51a7da5dda3c09e998b5bcfac85d222c", size = 3504309, upload-time = "2026-02-18T18:41:03.252Z" }, - { url = "https://files.pythonhosted.org/packages/85/23/0fce5789b8a5035e7ace21216a468143f3144e02013252116616c58339aa/numba-0.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:e63dc94023b47894849b8b106db28ccb98b49d5498b98878fac1a38f83ac007a", size = 2752740, upload-time = "2026-02-18T18:41:05.097Z" }, - { url = "https://files.pythonhosted.org/packages/52/80/2734de90f9300a6e2503b35ee50d9599926b90cbb7ac54f9e40074cd07f1/numba-0.64.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3bab2c872194dcd985f1153b70782ec0fbbe348fffef340264eacd3a76d59fd6", size = 2683392, upload-time = "2026-02-18T18:41:06.563Z" }, - { url = "https://files.pythonhosted.org/packages/42/e8/14b5853ebefd5b37723ef365c5318a30ce0702d39057eaa8d7d76392859d/numba-0.64.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:703a246c60832cad231d2e73c1182f25bf3cc8b699759ec8fe58a2dbc689a70c", size = 3812245, upload-time = "2026-02-18T18:41:07.963Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a2/f60dc6c96d19b7185144265a5fbf01c14993d37ff4cd324b09d0212aa7ce/numba-0.64.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e2e49a7900ee971d32af7609adc0cfe6aa7477c6f6cccdf6d8138538cf7756f", size = 3511328, upload-time = "2026-02-18T18:41:09.504Z" }, - { url = "https://files.pythonhosted.org/packages/9c/2a/fe7003ea7e7237ee7014f8eaeeb7b0d228a2db22572ca85bab2648cf52cb/numba-0.64.0-cp313-cp313-win_amd64.whl", hash = "sha256:396f43c3f77e78d7ec84cdfc6b04969c78f8f169351b3c4db814b97e7acf4245", size = 2752668, upload-time = "2026-02-18T18:41:11.455Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8a/77d26afe0988c592dd97cb8d4e80bfb3dfc7dbdacfca7d74a7c5c81dd8c2/numba-0.64.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f565d55eaeff382cbc86c63c8c610347453af3d1e7afb2b6569aac1c9b5c93ce", size = 2683590, upload-time = "2026-02-18T18:41:12.897Z" }, - { url = "https://files.pythonhosted.org/packages/8e/4b/600b8b7cdbc7f9cebee9ea3d13bb70052a79baf28944024ffcb59f0712e3/numba-0.64.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9b55169b18892c783f85e9ad9e6f5297a6d12967e4414e6b71361086025ff0bb", size = 3781163, upload-time = "2026-02-18T18:41:15.377Z" }, - { url = "https://files.pythonhosted.org/packages/ff/73/53f2d32bfa45b7175e9944f6b816d8c32840178c3eee9325033db5bf838e/numba-0.64.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:196bcafa02c9dd1707e068434f6d5cedde0feb787e3432f7f1f0e993cc336c4c", size = 3481172, upload-time = "2026-02-18T18:41:17.281Z" }, - { url = "https://files.pythonhosted.org/packages/b5/00/aebd2f7f1e11e38814bb96e95a27580817a7b340608d3ac085fdbab83174/numba-0.64.0-cp314-cp314-win_amd64.whl", hash = "sha256:213e9acbe7f1c05090592e79020315c1749dd52517b90e94c517dca3f014d4a1", size = 2754700, upload-time = "2026-02-18T18:41:19.277Z" }, + { url = "https://files.pythonhosted.org/packages/70/a6/9fc52cb4f0d5e6d8b5f4d81615bc01012e3cf24e1052a60f17a68deb8092/numba-0.64.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69440a8e8bc1a81028446f06b363e28635aa67bd51b1e498023f03b812e0ce68", size = 2683418 }, + { url = "https://files.pythonhosted.org/packages/9b/89/1a74ea99b180b7a5587b0301ed1b183a2937c4b4b67f7994689b5d36fc34/numba-0.64.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13721011f693ba558b8dd4e4db7f2640462bba1b855bdc804be45bbeb55031a", size = 3804087 }, + { url = "https://files.pythonhosted.org/packages/91/e1/583c647404b15f807410510fec1eb9b80cb8474165940b7749f026f21cbc/numba-0.64.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0b180b1133f2b5d8b3f09d96b6d7a9e51a7da5dda3c09e998b5bcfac85d222c", size = 3504309 }, + { url = "https://files.pythonhosted.org/packages/85/23/0fce5789b8a5035e7ace21216a468143f3144e02013252116616c58339aa/numba-0.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:e63dc94023b47894849b8b106db28ccb98b49d5498b98878fac1a38f83ac007a", size = 2752740 }, + { url = "https://files.pythonhosted.org/packages/52/80/2734de90f9300a6e2503b35ee50d9599926b90cbb7ac54f9e40074cd07f1/numba-0.64.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3bab2c872194dcd985f1153b70782ec0fbbe348fffef340264eacd3a76d59fd6", size = 2683392 }, + { url = "https://files.pythonhosted.org/packages/42/e8/14b5853ebefd5b37723ef365c5318a30ce0702d39057eaa8d7d76392859d/numba-0.64.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:703a246c60832cad231d2e73c1182f25bf3cc8b699759ec8fe58a2dbc689a70c", size = 3812245 }, + { url = "https://files.pythonhosted.org/packages/8a/a2/f60dc6c96d19b7185144265a5fbf01c14993d37ff4cd324b09d0212aa7ce/numba-0.64.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e2e49a7900ee971d32af7609adc0cfe6aa7477c6f6cccdf6d8138538cf7756f", size = 3511328 }, + { url = "https://files.pythonhosted.org/packages/9c/2a/fe7003ea7e7237ee7014f8eaeeb7b0d228a2db22572ca85bab2648cf52cb/numba-0.64.0-cp313-cp313-win_amd64.whl", hash = "sha256:396f43c3f77e78d7ec84cdfc6b04969c78f8f169351b3c4db814b97e7acf4245", size = 2752668 }, + { url = "https://files.pythonhosted.org/packages/3d/8a/77d26afe0988c592dd97cb8d4e80bfb3dfc7dbdacfca7d74a7c5c81dd8c2/numba-0.64.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f565d55eaeff382cbc86c63c8c610347453af3d1e7afb2b6569aac1c9b5c93ce", size = 2683590 }, + { url = "https://files.pythonhosted.org/packages/8e/4b/600b8b7cdbc7f9cebee9ea3d13bb70052a79baf28944024ffcb59f0712e3/numba-0.64.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9b55169b18892c783f85e9ad9e6f5297a6d12967e4414e6b71361086025ff0bb", size = 3781163 }, + { url = "https://files.pythonhosted.org/packages/ff/73/53f2d32bfa45b7175e9944f6b816d8c32840178c3eee9325033db5bf838e/numba-0.64.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:196bcafa02c9dd1707e068434f6d5cedde0feb787e3432f7f1f0e993cc336c4c", size = 3481172 }, + { url = "https://files.pythonhosted.org/packages/b5/00/aebd2f7f1e11e38814bb96e95a27580817a7b340608d3ac085fdbab83174/numba-0.64.0-cp314-cp314-win_amd64.whl", hash = "sha256:213e9acbe7f1c05090592e79020315c1749dd52517b90e94c517dca3f014d4a1", size = 2754700 }, ] [[package]] name = "numpy" version = "2.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628, upload-time = "2026-03-09T07:56:24.252Z" }, - { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872, upload-time = "2026-03-09T07:56:26.991Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489, upload-time = "2026-03-09T07:56:29.414Z" }, - { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814, upload-time = "2026-03-09T07:56:32.225Z" }, - { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601, upload-time = "2026-03-09T07:56:34.461Z" }, - { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358, upload-time = "2026-03-09T07:56:36.852Z" }, - { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135, upload-time = "2026-03-09T07:56:39.337Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816, upload-time = "2026-03-09T07:56:42.31Z" }, - { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132, upload-time = "2026-03-09T07:56:44.851Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144, upload-time = "2026-03-09T07:56:47.057Z" }, - { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364, upload-time = "2026-03-09T07:56:49.849Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" }, - { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" }, - { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" }, - { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" }, - { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" }, - { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" }, - { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" }, - { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" }, - { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" }, - { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" }, - { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" }, - { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" }, - { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" }, - { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" }, - { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" }, - { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" }, - { url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563, upload-time = "2026-03-09T07:57:43.817Z" }, - { url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161, upload-time = "2026-03-09T07:57:46.169Z" }, - { url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738, upload-time = "2026-03-09T07:57:48.506Z" }, - { url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618, upload-time = "2026-03-09T07:57:50.591Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676, upload-time = "2026-03-09T07:57:52.34Z" }, - { url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492, upload-time = "2026-03-09T07:57:54.91Z" }, - { url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789, upload-time = "2026-03-09T07:57:57.641Z" }, - { url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941, upload-time = "2026-03-09T07:58:00.577Z" }, - { url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503, upload-time = "2026-03-09T07:58:03.331Z" }, - { url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915, upload-time = "2026-03-09T07:58:06.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875, upload-time = "2026-03-09T07:58:08.734Z" }, - { url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225, upload-time = "2026-03-09T07:58:11.034Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769, upload-time = "2026-03-09T07:58:13.67Z" }, - { url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461, upload-time = "2026-03-09T07:58:15.912Z" }, - { url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809, upload-time = "2026-03-09T07:58:17.787Z" }, - { url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242, upload-time = "2026-03-09T07:58:20.476Z" }, - { url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660, upload-time = "2026-03-09T07:58:23.067Z" }, - { url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384, upload-time = "2026-03-09T07:58:25.839Z" }, - { url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547, upload-time = "2026-03-09T07:58:28.289Z" }, - { url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645, upload-time = "2026-03-09T07:58:30.384Z" }, - { url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454, upload-time = "2026-03-09T07:58:33.031Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628 }, + { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872 }, + { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489 }, + { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814 }, + { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601 }, + { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358 }, + { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135 }, + { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816 }, + { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132 }, + { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144 }, + { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364 }, + { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297 }, + { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853 }, + { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435 }, + { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347 }, + { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626 }, + { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916 }, + { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824 }, + { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581 }, + { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618 }, + { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824 }, + { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218 }, + { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570 }, + { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113 }, + { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370 }, + { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499 }, + { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164 }, + { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544 }, + { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290 }, + { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814 }, + { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673 }, + { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907 }, + { url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563 }, + { url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161 }, + { url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738 }, + { url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618 }, + { url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676 }, + { url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492 }, + { url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789 }, + { url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941 }, + { url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503 }, + { url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915 }, + { url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875 }, + { url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225 }, + { url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769 }, + { url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461 }, + { url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809 }, + { url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242 }, + { url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660 }, + { url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384 }, + { url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547 }, + { url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645 }, + { url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454 }, ] [[package]] @@ -4855,8 +4847,8 @@ name = "nvidia-cublas" version = "13.1.0.3" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226, upload-time = "2025-10-09T08:59:04.818Z" }, - { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236, upload-time = "2025-10-09T08:59:32.536Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226 }, + { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236 }, ] [[package]] @@ -4864,8 +4856,8 @@ name = "nvidia-cuda-cupti" version = "13.0.85" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, - { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827 }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597 }, ] [[package]] @@ -4873,8 +4865,8 @@ name = "nvidia-cuda-nvrtc" version = "13.0.88" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, - { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200 }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449 }, ] [[package]] @@ -4882,8 +4874,8 @@ name = "nvidia-cuda-runtime" version = "13.0.96" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, - { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060 }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632 }, ] [[package]] @@ -4894,8 +4886,8 @@ dependencies = [ { name = "nvidia-cublas", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201, upload-time = "2026-02-03T20:40:53.805Z" }, - { url = "https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf", size = 366066321, upload-time = "2026-02-03T20:44:52.837Z" }, + { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201 }, + { url = "https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf", size = 366066321 }, ] [[package]] @@ -4906,8 +4898,8 @@ dependencies = [ { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554 }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489 }, ] [[package]] @@ -4915,8 +4907,8 @@ name = "nvidia-cufile" version = "1.15.1.6" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, - { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672 }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992 }, ] [[package]] @@ -4924,8 +4916,8 @@ name = "nvidia-curand" version = "10.4.0.35" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, - { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106 }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258 }, ] [[package]] @@ -4938,8 +4930,8 @@ dependencies = [ { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, - { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760 }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980 }, ] [[package]] @@ -4950,8 +4942,8 @@ dependencies = [ { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, - { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568 }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937 }, ] [[package]] @@ -4959,8 +4951,8 @@ name = "nvidia-cusparselt-cu13" version = "0.8.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/10/8dcd1175260706a2fc92a16a52e306b71d4c1ea0b0cc4a9484183399818a/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c", size = 220791277, upload-time = "2025-08-13T19:22:40.982Z" }, - { url = "https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd", size = 169884119, upload-time = "2025-08-13T19:23:41.967Z" }, + { url = "https://files.pythonhosted.org/packages/46/10/8dcd1175260706a2fc92a16a52e306b71d4c1ea0b0cc4a9484183399818a/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c", size = 220791277 }, + { url = "https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd", size = 169884119 }, ] [[package]] @@ -4968,8 +4960,8 @@ name = "nvidia-nccl-cu13" version = "2.28.9" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/55/1920646a2e43ffd4fc958536b276197ed740e9e0c54105b4bb3521591fc7/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643", size = 196561677, upload-time = "2025-11-18T05:49:03.45Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42", size = 196493177, upload-time = "2025-11-18T05:49:17.677Z" }, + { url = "https://files.pythonhosted.org/packages/39/55/1920646a2e43ffd4fc958536b276197ed740e9e0c54105b4bb3521591fc7/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643", size = 196561677 }, + { url = "https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42", size = 196493177 }, ] [[package]] @@ -4977,8 +4969,8 @@ name = "nvidia-nvjitlink" version = "13.0.88" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933, upload-time = "2025-09-04T08:35:43.553Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748, upload-time = "2025-09-04T08:35:20.008Z" }, + { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933 }, + { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748 }, ] [[package]] @@ -4986,8 +4978,8 @@ name = "nvidia-nvshmem-cu13" version = "3.4.5" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, - { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947 }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546 }, ] [[package]] @@ -4995,17 +4987,17 @@ name = "nvidia-nvtx" version = "13.0.85" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, - { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047 }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878 }, ] [[package]] name = "oauthlib" version = "3.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918 } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065 }, ] [[package]] @@ -5015,46 +5007,46 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/8c/9ec984edd0f3b72226adfaa19b1c61b15823b35b52f311ca4af36d009d15/obstore-0.8.2.tar.gz", hash = "sha256:a467bc4e97169e2ba749981b4fd0936015428d9b8f3fb83a5528536b1b6f377f", size = 168852, upload-time = "2025-09-16T15:34:55.786Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/8c/9ec984edd0f3b72226adfaa19b1c61b15823b35b52f311ca4af36d009d15/obstore-0.8.2.tar.gz", hash = "sha256:a467bc4e97169e2ba749981b4fd0936015428d9b8f3fb83a5528536b1b6f377f", size = 168852 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/dc/60fefbb5736e69eab56657bca04ca64dc07fdeccb3814164a31b62ad066b/obstore-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bb70ce297a47392b1d9a3e310f18d59cd5ebbb9453428210fef02ed60e4d75d1", size = 3612955, upload-time = "2025-09-16T15:33:29.527Z" }, - { url = "https://files.pythonhosted.org/packages/d2/8b/844e8f382e5a12b8a3796a05d76a03e12c7aedc13d6900419e39207d7868/obstore-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1619bf618428abf1f607e0b219b2e230a966dcf697b717deccfa0983dd91f646", size = 3346564, upload-time = "2025-09-16T15:33:30.698Z" }, - { url = "https://files.pythonhosted.org/packages/89/73/8537f99e09a38a54a6a15ede907aa25d4da089f767a808f0b2edd9c03cec/obstore-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4605c3ed7c9515aeb4c619b5f7f2c9986ed4a79fe6045e536b5e59b804b1476", size = 3460809, upload-time = "2025-09-16T15:33:31.837Z" }, - { url = "https://files.pythonhosted.org/packages/b4/99/7714dec721e43f521d6325a82303a002cddad089437640f92542b84e9cc8/obstore-0.8.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce42670417876dd8668cbb8659e860e9725e5f26bbc86449fd259970e2dd9d18", size = 3692081, upload-time = "2025-09-16T15:33:33.028Z" }, - { url = "https://files.pythonhosted.org/packages/ec/bd/4ac4175fe95a24c220a96021c25c432bcc0c0212f618be0737184eebbaad/obstore-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a3e893b2a06585f651c541c1972fe1e3bf999ae2a5fda052ee55eb7e6516f5", size = 3957466, upload-time = "2025-09-16T15:33:34.528Z" }, - { url = "https://files.pythonhosted.org/packages/4e/04/caa288fb735484fc5cb019bdf3d896eaccfae0ac4622e520d05692c46790/obstore-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08462b32f95a9948ed56ed63e88406e2e5a4cae1fde198f9682e0fb8487100ed", size = 3951293, upload-time = "2025-09-16T15:33:35.733Z" }, - { url = "https://files.pythonhosted.org/packages/44/2f/d380239da2d6a1fda82e17df5dae600a404e8a93a065784518ff8325d5f6/obstore-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a0bf7763292a8fc47d01cd66e6f19002c5c6ad4b3ed4e6b2729f5e190fa8a0d", size = 3766199, upload-time = "2025-09-16T15:33:36.904Z" }, - { url = "https://files.pythonhosted.org/packages/28/41/d391be069d3da82969b54266948b2582aeca5dd735abeda4d63dba36e07b/obstore-0.8.2-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:bcd47f8126cb192cbe86942b8f73b1c45a651ce7e14c9a82c5641dfbf8be7603", size = 3529678, upload-time = "2025-09-16T15:33:38.221Z" }, - { url = "https://files.pythonhosted.org/packages/b9/4c/4862fdd1a3abde459ee8eea699b1797df638a460af235b18ca82c8fffb72/obstore-0.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:57eda9fd8c757c3b4fe36cf3918d7e589cc1286591295cc10b34122fa36dd3fd", size = 3698079, upload-time = "2025-09-16T15:33:39.696Z" }, - { url = "https://files.pythonhosted.org/packages/68/ca/014e747bc53b570059c27e3565b2316fbe5c107d4134551f4cd3e24aa667/obstore-0.8.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ea44442aad8992166baa69f5069750979e4c5d9ffce772e61565945eea5774b9", size = 3687154, upload-time = "2025-09-16T15:33:40.92Z" }, - { url = "https://files.pythonhosted.org/packages/6f/89/6db5f8edd93028e5b8bfbeee15e6bd3e56f72106107d31cb208b57659de4/obstore-0.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:41496a3ab8527402db4142aaaf0d42df9d7d354b13ba10d9c33e0e48dd49dd96", size = 3773444, upload-time = "2025-09-16T15:33:42.123Z" }, - { url = "https://files.pythonhosted.org/packages/26/e5/c9e2cc540689c873beb61246e1615d6e38301e6a34dec424f5a5c63c1afd/obstore-0.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43da209803f052df96c7c3cbec512d310982efd2407e4a435632841a51143170", size = 3939315, upload-time = "2025-09-16T15:33:43.252Z" }, - { url = "https://files.pythonhosted.org/packages/4d/c9/bb53280ca50103c1ffda373cdc9b0f835431060039c2897cbc87ddd92e42/obstore-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:1836f5dcd49f9f2950c75889ab5c51fb290d3ea93cdc39a514541e0be3af016e", size = 3978234, upload-time = "2025-09-16T15:33:44.393Z" }, - { url = "https://files.pythonhosted.org/packages/f0/5d/8c3316cc958d386d5e6ab03e9db9ddc27f8e2141cee4a6777ae5b92f3aac/obstore-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:212f033e53fe6e53d64957923c5c88949a400e9027f7038c705ec2e9038be563", size = 3612027, upload-time = "2025-09-16T15:33:45.6Z" }, - { url = "https://files.pythonhosted.org/packages/ea/4d/699359774ce6330130536d008bfc32827fab0c25a00238d015a5974a3d1d/obstore-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bee21fa4ba148d08fa90e47a96df11161661ed31e09c056a373cb2154b0f2852", size = 3344686, upload-time = "2025-09-16T15:33:47.185Z" }, - { url = "https://files.pythonhosted.org/packages/82/37/55437341f10512906e02fd9fa69a8a95ad3f2f6a916d3233fda01763d110/obstore-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4c66594b59832ff1ced4c72575d9beb8b5f9b4e404ac1150a42bfb226617fd50", size = 3459860, upload-time = "2025-09-16T15:33:48.382Z" }, - { url = "https://files.pythonhosted.org/packages/7a/51/4245a616c94ee4851965e33f7a563ab4090cc81f52cc73227ff9ceca2e46/obstore-0.8.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:089f33af5c2fe132d00214a0c1f40601b28f23a38e24ef9f79fb0576f2730b74", size = 3691648, upload-time = "2025-09-16T15:33:49.524Z" }, - { url = "https://files.pythonhosted.org/packages/4e/f1/4e2fb24171e3ca3641a4653f006be826e7e17634b11688a5190553b00b83/obstore-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d87f658dfd340d5d9ea2d86a7c90d44da77a0db9e00c034367dca335735110cf", size = 3956867, upload-time = "2025-09-16T15:33:51.082Z" }, - { url = "https://files.pythonhosted.org/packages/42/f5/b703115361c798c9c1744e1e700d5908d904a8c2e2bd38bec759c9ffb469/obstore-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e2e4fa92828c4fbc2d487f3da2d3588701a1b67d9f6ca3c97cc2afc912e9c63", size = 3950599, upload-time = "2025-09-16T15:33:52.173Z" }, - { url = "https://files.pythonhosted.org/packages/53/20/08c6dc0f20c1394e2324b9344838e4e7af770cdcb52c30757a475f50daeb/obstore-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab440e89c5c37a8ec230857dd65147d4b923e0cada33297135d05e0f937d696a", size = 3765865, upload-time = "2025-09-16T15:33:53.291Z" }, - { url = "https://files.pythonhosted.org/packages/77/20/77907765e29b2eba6bd8821872284d91170d7084f670855b2dfcb249ea14/obstore-0.8.2-cp313-cp313-manylinux_2_24_aarch64.whl", hash = "sha256:b9beed107c5c9cd995d4a73263861fcfbc414d58773ed65c14f80eb18258a932", size = 3529807, upload-time = "2025-09-16T15:33:54.535Z" }, - { url = "https://files.pythonhosted.org/packages/a5/f5/f629d39cc30d050f52b1bf927e4d65c1cc7d7ffbb8a635cd546b5c5219a0/obstore-0.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b75b4e7746292c785e31edcd5aadc8b758238372a19d4c5e394db5c305d7d175", size = 3693629, upload-time = "2025-09-16T15:33:56.016Z" }, - { url = "https://files.pythonhosted.org/packages/30/ff/106763fd10f2a1cb47f2ef1162293c78ad52f4e73223d8d43fc6b755445d/obstore-0.8.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f33e6c366869d05ab0b7f12efe63269e631c5450d95d6b4ba4c5faf63f69de70", size = 3686176, upload-time = "2025-09-16T15:33:57.247Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0c/d2ccb6f32feeca906d5a7c4255340df5262af8838441ca06c9e4e37b67d5/obstore-0.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:12c885a9ce5ceb09d13cc186586c0c10b62597eff21b985f6ce8ff9dab963ad3", size = 3773081, upload-time = "2025-09-16T15:33:58.475Z" }, - { url = "https://files.pythonhosted.org/packages/fa/79/40d1cc504cefc89c9b3dd8874287f3fddc7d963a8748d6dffc5880222013/obstore-0.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4accc883b93349a81c9931e15dd318cc703b02bbef2805d964724c73d006d00e", size = 3938589, upload-time = "2025-09-16T15:33:59.734Z" }, - { url = "https://files.pythonhosted.org/packages/14/dd/916c6777222db3271e9fb3cf9a97ed92b3a9b3e465bdeec96de9ab809d53/obstore-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ec850adf9980e5788a826ccfd5819989724e2a2f712bfa3258e85966c8d9981e", size = 3977768, upload-time = "2025-09-16T15:34:01.25Z" }, - { url = "https://files.pythonhosted.org/packages/f1/61/66f8dc98bbf5613bbfe5bf21747b4c8091442977f4bd897945895ab7325c/obstore-0.8.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1431e40e9bb4773a261e51b192ea6489d0799b9d4d7dbdf175cdf813eb8c0503", size = 3623364, upload-time = "2025-09-16T15:34:02.957Z" }, - { url = "https://files.pythonhosted.org/packages/1a/66/6d527b3027e42f625c8fc816ac7d19b0d6228f95bfe7666e4d6b081d2348/obstore-0.8.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ddb39d4da303f50b959da000aa42734f6da7ac0cc0be2d5a7838b62c97055bb9", size = 3347764, upload-time = "2025-09-16T15:34:04.236Z" }, - { url = "https://files.pythonhosted.org/packages/0d/79/c00103302b620192ea447a948921ad3fed031ce3d19e989f038e1183f607/obstore-0.8.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e01f4e13783db453e17e005a4a3ceff09c41c262e44649ba169d253098c775e8", size = 3460981, upload-time = "2025-09-16T15:34:05.595Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d9/bfe4ed4b1aebc45b56644dd5b943cf8e1673505cccb352e66878a457e807/obstore-0.8.2-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df0fc2d0bc17caff9b538564ddc26d7616f7e8b7c65b1a3c90b5048a8ad2e797", size = 3692711, upload-time = "2025-09-16T15:34:06.796Z" }, - { url = "https://files.pythonhosted.org/packages/13/47/cd6c2cbb18e1f40c77e7957a4a03d2d83f1859a2e876a408f1ece81cad4c/obstore-0.8.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e439d06c99a140348f046c9f598ee349cc2dcd9105c15540a4b231f9cc48bbae", size = 3958362, upload-time = "2025-09-16T15:34:08.277Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ea/5ee82bf23abd71c7d6a3f2d008197ae8f8f569d41314c26a8f75318245be/obstore-0.8.2-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e37d9046669fcc59522d0faf1d105fcbfd09c84cccaaa1e809227d8e030f32c", size = 3957082, upload-time = "2025-09-16T15:34:09.477Z" }, - { url = "https://files.pythonhosted.org/packages/cb/ee/46650405e50fdaa8d95f30375491f9c91fac9517980e8a28a4a6af66927f/obstore-0.8.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2646fdcc4bbe92dc2bb5bcdff15574da1211f5806c002b66d514cee2a23c7cb8", size = 3775539, upload-time = "2025-09-16T15:34:10.726Z" }, - { url = "https://files.pythonhosted.org/packages/35/d6/348a7ebebe2ca3d94dfc75344ea19675ae45472823e372c1852844078307/obstore-0.8.2-cp314-cp314-manylinux_2_24_aarch64.whl", hash = "sha256:e31a7d37675056d93dfc244605089dee67f5bba30f37c88436623c8c5ad9ba9d", size = 3535048, upload-time = "2025-09-16T15:34:12.076Z" }, - { url = "https://files.pythonhosted.org/packages/41/07/b7a16cc0da91a4b902d47880ad24016abfe7880c63f7cdafda45d89a2f91/obstore-0.8.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:656313dd8170dde0f0cd471433283337a63912e8e790a121f7cc7639c83e3816", size = 3699035, upload-time = "2025-09-16T15:34:13.331Z" }, - { url = "https://files.pythonhosted.org/packages/7f/74/3269a3a58347e0b019742d888612c4b765293c9c75efa44e144b1e884c0d/obstore-0.8.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329038c9645d6d1741e77fe1a53e28a14b1a5c1461cfe4086082ad39ebabf981", size = 3687307, upload-time = "2025-09-16T15:34:14.501Z" }, - { url = "https://files.pythonhosted.org/packages/01/f9/4fd4819ad6a49d2f462a45be453561f4caebded0dc40112deeffc34b89b1/obstore-0.8.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1e4df99b369790c97c752d126b286dc86484ea49bff5782843a265221406566f", size = 3776076, upload-time = "2025-09-16T15:34:16.207Z" }, - { url = "https://files.pythonhosted.org/packages/14/dd/7c4f958fa0b9fc4778fb3d232e38b37db8c6b260f641022fbba48b049d7e/obstore-0.8.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9e1c65c65e20cc990414a8a9af88209b1bbc0dd9521b5f6b0293c60e19439bb7", size = 3947445, upload-time = "2025-09-16T15:34:17.423Z" }, + { url = "https://files.pythonhosted.org/packages/2b/dc/60fefbb5736e69eab56657bca04ca64dc07fdeccb3814164a31b62ad066b/obstore-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bb70ce297a47392b1d9a3e310f18d59cd5ebbb9453428210fef02ed60e4d75d1", size = 3612955 }, + { url = "https://files.pythonhosted.org/packages/d2/8b/844e8f382e5a12b8a3796a05d76a03e12c7aedc13d6900419e39207d7868/obstore-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1619bf618428abf1f607e0b219b2e230a966dcf697b717deccfa0983dd91f646", size = 3346564 }, + { url = "https://files.pythonhosted.org/packages/89/73/8537f99e09a38a54a6a15ede907aa25d4da089f767a808f0b2edd9c03cec/obstore-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4605c3ed7c9515aeb4c619b5f7f2c9986ed4a79fe6045e536b5e59b804b1476", size = 3460809 }, + { url = "https://files.pythonhosted.org/packages/b4/99/7714dec721e43f521d6325a82303a002cddad089437640f92542b84e9cc8/obstore-0.8.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce42670417876dd8668cbb8659e860e9725e5f26bbc86449fd259970e2dd9d18", size = 3692081 }, + { url = "https://files.pythonhosted.org/packages/ec/bd/4ac4175fe95a24c220a96021c25c432bcc0c0212f618be0737184eebbaad/obstore-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a3e893b2a06585f651c541c1972fe1e3bf999ae2a5fda052ee55eb7e6516f5", size = 3957466 }, + { url = "https://files.pythonhosted.org/packages/4e/04/caa288fb735484fc5cb019bdf3d896eaccfae0ac4622e520d05692c46790/obstore-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08462b32f95a9948ed56ed63e88406e2e5a4cae1fde198f9682e0fb8487100ed", size = 3951293 }, + { url = "https://files.pythonhosted.org/packages/44/2f/d380239da2d6a1fda82e17df5dae600a404e8a93a065784518ff8325d5f6/obstore-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a0bf7763292a8fc47d01cd66e6f19002c5c6ad4b3ed4e6b2729f5e190fa8a0d", size = 3766199 }, + { url = "https://files.pythonhosted.org/packages/28/41/d391be069d3da82969b54266948b2582aeca5dd735abeda4d63dba36e07b/obstore-0.8.2-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:bcd47f8126cb192cbe86942b8f73b1c45a651ce7e14c9a82c5641dfbf8be7603", size = 3529678 }, + { url = "https://files.pythonhosted.org/packages/b9/4c/4862fdd1a3abde459ee8eea699b1797df638a460af235b18ca82c8fffb72/obstore-0.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:57eda9fd8c757c3b4fe36cf3918d7e589cc1286591295cc10b34122fa36dd3fd", size = 3698079 }, + { url = "https://files.pythonhosted.org/packages/68/ca/014e747bc53b570059c27e3565b2316fbe5c107d4134551f4cd3e24aa667/obstore-0.8.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ea44442aad8992166baa69f5069750979e4c5d9ffce772e61565945eea5774b9", size = 3687154 }, + { url = "https://files.pythonhosted.org/packages/6f/89/6db5f8edd93028e5b8bfbeee15e6bd3e56f72106107d31cb208b57659de4/obstore-0.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:41496a3ab8527402db4142aaaf0d42df9d7d354b13ba10d9c33e0e48dd49dd96", size = 3773444 }, + { url = "https://files.pythonhosted.org/packages/26/e5/c9e2cc540689c873beb61246e1615d6e38301e6a34dec424f5a5c63c1afd/obstore-0.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43da209803f052df96c7c3cbec512d310982efd2407e4a435632841a51143170", size = 3939315 }, + { url = "https://files.pythonhosted.org/packages/4d/c9/bb53280ca50103c1ffda373cdc9b0f835431060039c2897cbc87ddd92e42/obstore-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:1836f5dcd49f9f2950c75889ab5c51fb290d3ea93cdc39a514541e0be3af016e", size = 3978234 }, + { url = "https://files.pythonhosted.org/packages/f0/5d/8c3316cc958d386d5e6ab03e9db9ddc27f8e2141cee4a6777ae5b92f3aac/obstore-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:212f033e53fe6e53d64957923c5c88949a400e9027f7038c705ec2e9038be563", size = 3612027 }, + { url = "https://files.pythonhosted.org/packages/ea/4d/699359774ce6330130536d008bfc32827fab0c25a00238d015a5974a3d1d/obstore-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bee21fa4ba148d08fa90e47a96df11161661ed31e09c056a373cb2154b0f2852", size = 3344686 }, + { url = "https://files.pythonhosted.org/packages/82/37/55437341f10512906e02fd9fa69a8a95ad3f2f6a916d3233fda01763d110/obstore-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4c66594b59832ff1ced4c72575d9beb8b5f9b4e404ac1150a42bfb226617fd50", size = 3459860 }, + { url = "https://files.pythonhosted.org/packages/7a/51/4245a616c94ee4851965e33f7a563ab4090cc81f52cc73227ff9ceca2e46/obstore-0.8.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:089f33af5c2fe132d00214a0c1f40601b28f23a38e24ef9f79fb0576f2730b74", size = 3691648 }, + { url = "https://files.pythonhosted.org/packages/4e/f1/4e2fb24171e3ca3641a4653f006be826e7e17634b11688a5190553b00b83/obstore-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d87f658dfd340d5d9ea2d86a7c90d44da77a0db9e00c034367dca335735110cf", size = 3956867 }, + { url = "https://files.pythonhosted.org/packages/42/f5/b703115361c798c9c1744e1e700d5908d904a8c2e2bd38bec759c9ffb469/obstore-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e2e4fa92828c4fbc2d487f3da2d3588701a1b67d9f6ca3c97cc2afc912e9c63", size = 3950599 }, + { url = "https://files.pythonhosted.org/packages/53/20/08c6dc0f20c1394e2324b9344838e4e7af770cdcb52c30757a475f50daeb/obstore-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab440e89c5c37a8ec230857dd65147d4b923e0cada33297135d05e0f937d696a", size = 3765865 }, + { url = "https://files.pythonhosted.org/packages/77/20/77907765e29b2eba6bd8821872284d91170d7084f670855b2dfcb249ea14/obstore-0.8.2-cp313-cp313-manylinux_2_24_aarch64.whl", hash = "sha256:b9beed107c5c9cd995d4a73263861fcfbc414d58773ed65c14f80eb18258a932", size = 3529807 }, + { url = "https://files.pythonhosted.org/packages/a5/f5/f629d39cc30d050f52b1bf927e4d65c1cc7d7ffbb8a635cd546b5c5219a0/obstore-0.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b75b4e7746292c785e31edcd5aadc8b758238372a19d4c5e394db5c305d7d175", size = 3693629 }, + { url = "https://files.pythonhosted.org/packages/30/ff/106763fd10f2a1cb47f2ef1162293c78ad52f4e73223d8d43fc6b755445d/obstore-0.8.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f33e6c366869d05ab0b7f12efe63269e631c5450d95d6b4ba4c5faf63f69de70", size = 3686176 }, + { url = "https://files.pythonhosted.org/packages/ce/0c/d2ccb6f32feeca906d5a7c4255340df5262af8838441ca06c9e4e37b67d5/obstore-0.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:12c885a9ce5ceb09d13cc186586c0c10b62597eff21b985f6ce8ff9dab963ad3", size = 3773081 }, + { url = "https://files.pythonhosted.org/packages/fa/79/40d1cc504cefc89c9b3dd8874287f3fddc7d963a8748d6dffc5880222013/obstore-0.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4accc883b93349a81c9931e15dd318cc703b02bbef2805d964724c73d006d00e", size = 3938589 }, + { url = "https://files.pythonhosted.org/packages/14/dd/916c6777222db3271e9fb3cf9a97ed92b3a9b3e465bdeec96de9ab809d53/obstore-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ec850adf9980e5788a826ccfd5819989724e2a2f712bfa3258e85966c8d9981e", size = 3977768 }, + { url = "https://files.pythonhosted.org/packages/f1/61/66f8dc98bbf5613bbfe5bf21747b4c8091442977f4bd897945895ab7325c/obstore-0.8.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1431e40e9bb4773a261e51b192ea6489d0799b9d4d7dbdf175cdf813eb8c0503", size = 3623364 }, + { url = "https://files.pythonhosted.org/packages/1a/66/6d527b3027e42f625c8fc816ac7d19b0d6228f95bfe7666e4d6b081d2348/obstore-0.8.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ddb39d4da303f50b959da000aa42734f6da7ac0cc0be2d5a7838b62c97055bb9", size = 3347764 }, + { url = "https://files.pythonhosted.org/packages/0d/79/c00103302b620192ea447a948921ad3fed031ce3d19e989f038e1183f607/obstore-0.8.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e01f4e13783db453e17e005a4a3ceff09c41c262e44649ba169d253098c775e8", size = 3460981 }, + { url = "https://files.pythonhosted.org/packages/3d/d9/bfe4ed4b1aebc45b56644dd5b943cf8e1673505cccb352e66878a457e807/obstore-0.8.2-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df0fc2d0bc17caff9b538564ddc26d7616f7e8b7c65b1a3c90b5048a8ad2e797", size = 3692711 }, + { url = "https://files.pythonhosted.org/packages/13/47/cd6c2cbb18e1f40c77e7957a4a03d2d83f1859a2e876a408f1ece81cad4c/obstore-0.8.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e439d06c99a140348f046c9f598ee349cc2dcd9105c15540a4b231f9cc48bbae", size = 3958362 }, + { url = "https://files.pythonhosted.org/packages/3d/ea/5ee82bf23abd71c7d6a3f2d008197ae8f8f569d41314c26a8f75318245be/obstore-0.8.2-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e37d9046669fcc59522d0faf1d105fcbfd09c84cccaaa1e809227d8e030f32c", size = 3957082 }, + { url = "https://files.pythonhosted.org/packages/cb/ee/46650405e50fdaa8d95f30375491f9c91fac9517980e8a28a4a6af66927f/obstore-0.8.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2646fdcc4bbe92dc2bb5bcdff15574da1211f5806c002b66d514cee2a23c7cb8", size = 3775539 }, + { url = "https://files.pythonhosted.org/packages/35/d6/348a7ebebe2ca3d94dfc75344ea19675ae45472823e372c1852844078307/obstore-0.8.2-cp314-cp314-manylinux_2_24_aarch64.whl", hash = "sha256:e31a7d37675056d93dfc244605089dee67f5bba30f37c88436623c8c5ad9ba9d", size = 3535048 }, + { url = "https://files.pythonhosted.org/packages/41/07/b7a16cc0da91a4b902d47880ad24016abfe7880c63f7cdafda45d89a2f91/obstore-0.8.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:656313dd8170dde0f0cd471433283337a63912e8e790a121f7cc7639c83e3816", size = 3699035 }, + { url = "https://files.pythonhosted.org/packages/7f/74/3269a3a58347e0b019742d888612c4b765293c9c75efa44e144b1e884c0d/obstore-0.8.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329038c9645d6d1741e77fe1a53e28a14b1a5c1461cfe4086082ad39ebabf981", size = 3687307 }, + { url = "https://files.pythonhosted.org/packages/01/f9/4fd4819ad6a49d2f462a45be453561f4caebded0dc40112deeffc34b89b1/obstore-0.8.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1e4df99b369790c97c752d126b286dc86484ea49bff5782843a265221406566f", size = 3776076 }, + { url = "https://files.pythonhosted.org/packages/14/dd/7c4f958fa0b9fc4778fb3d232e38b37db8c6b260f641022fbba48b049d7e/obstore-0.8.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9e1c65c65e20cc990414a8a9af88209b1bbc0dd9521b5f6b0293c60e19439bb7", size = 3947445 }, ] [[package]] @@ -5066,18 +5058,18 @@ dependencies = [ { name = "pillow", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, { name = "pyobjc-framework-vision", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/07/3e15ab404f75875c5e48c47163300eb90b7409044d8711fc3aaf52503f2e/ocrmac-1.0.1.tar.gz", hash = "sha256:507fe5e4cbd67b2d03f6729a52bbc11f9d0b58241134eb958a5daafd4b9d93d9", size = 1454317, upload-time = "2026-01-08T16:44:26.412Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/07/3e15ab404f75875c5e48c47163300eb90b7409044d8711fc3aaf52503f2e/ocrmac-1.0.1.tar.gz", hash = "sha256:507fe5e4cbd67b2d03f6729a52bbc11f9d0b58241134eb958a5daafd4b9d93d9", size = 1454317 } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/15/7cc16507a2aca927abe395f1c545f17ae76b1f8ed44f43ebe4e8670ee203/ocrmac-1.0.1-py3-none-any.whl", hash = "sha256:1cef25426f7ae6bbd57fe3dc5553b25461ae8ad0d2b428a9bbadbf5907349024", size = 9955, upload-time = "2026-01-08T16:44:25.555Z" }, + { url = "https://files.pythonhosted.org/packages/37/15/7cc16507a2aca927abe395f1c545f17ae76b1f8ed44f43ebe4e8670ee203/ocrmac-1.0.1-py3-none-any.whl", hash = "sha256:1cef25426f7ae6bbd57fe3dc5553b25461ae8ad0d2b428a9bbadbf5907349024", size = 9955 }, ] [[package]] name = "olefile" version = "0.47" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/1b/077b508e3e500e1629d366249c3ccb32f95e50258b231705c09e3c7a4366/olefile-0.47.zip", hash = "sha256:599383381a0bf3dfbd932ca0ca6515acd174ed48870cbf7fee123d698c192c1c", size = 112240, upload-time = "2023-12-01T16:22:53.025Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/1b/077b508e3e500e1629d366249c3ccb32f95e50258b231705c09e3c7a4366/olefile-0.47.zip", hash = "sha256:599383381a0bf3dfbd932ca0ca6515acd174ed48870cbf7fee123d698c192c1c", size = 112240 } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/d3/b64c356a907242d719fc668b71befd73324e47ab46c8ebbbede252c154b2/olefile-0.47-py2.py3-none-any.whl", hash = "sha256:543c7da2a7adadf21214938bb79c83ea12b473a4b6ee4ad4bf854e7715e13d1f", size = 114565, upload-time = "2023-12-01T16:22:51.518Z" }, + { url = "https://files.pythonhosted.org/packages/17/d3/b64c356a907242d719fc668b71befd73324e47ab46c8ebbbede252c154b2/olefile-0.47-py2.py3-none-any.whl", hash = "sha256:543c7da2a7adadf21214938bb79c83ea12b473a4b6ee4ad4bf854e7715e13d1f", size = 114565 }, ] [[package]] @@ -5088,9 +5080,9 @@ dependencies = [ { name = "antlr4-python3-runtime" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/48/6388f1bb9da707110532cb70ec4d2822858ddfb44f1cdf1233c20a80ea4b/omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7", size = 3298120, upload-time = "2022-12-08T20:59:22.753Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/48/6388f1bb9da707110532cb70ec4d2822858ddfb44f1cdf1233c20a80ea4b/omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7", size = 3298120 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500, upload-time = "2022-12-08T20:59:19.686Z" }, + { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500 }, ] [[package]] @@ -5103,19 +5095,19 @@ dependencies = [ { name = "protobuf", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, { name = "typing-extensions", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/8a/335c03a8683a88a32f9a6bb98899ea6df241a41df64b37b9696772414794/onnx-1.20.1.tar.gz", hash = "sha256:ded16de1df563d51fbc1ad885f2a426f814039d8b5f4feb77febe09c0295ad67", size = 12048980, upload-time = "2026-01-10T01:40:03.043Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/8a/335c03a8683a88a32f9a6bb98899ea6df241a41df64b37b9696772414794/onnx-1.20.1.tar.gz", hash = "sha256:ded16de1df563d51fbc1ad885f2a426f814039d8b5f4feb77febe09c0295ad67", size = 12048980 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/4c/4b17e82f91ab9aa07ff595771e935ca73547b035030dc5f5a76e63fbfea9/onnx-1.20.1-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:1d923bb4f0ce1b24c6859222a7e6b2f123e7bfe7623683662805f2e7b9e95af2", size = 17903547, upload-time = "2026-01-10T01:39:31.015Z" }, - { url = "https://files.pythonhosted.org/packages/64/5e/1bfa100a9cb3f2d3d5f2f05f52f7e60323b0e20bb0abace1ae64dbc88f25/onnx-1.20.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddc0b7d8b5a94627dc86c533d5e415af94cbfd103019a582669dad1f56d30281", size = 17412021, upload-time = "2026-01-10T01:39:33.885Z" }, - { url = "https://files.pythonhosted.org/packages/fb/71/d3fec0dcf9a7a99e7368112d9c765154e81da70fcba1e3121131a45c245b/onnx-1.20.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9336b6b8e6efcf5c490a845f6afd7e041c89a56199aeda384ed7d58fb953b080", size = 17510450, upload-time = "2026-01-10T01:39:36.589Z" }, - { url = "https://files.pythonhosted.org/packages/74/a7/edce1403e05a46e59b502fae8e3350ceeac5841f8e8f1561e98562ed9b09/onnx-1.20.1-cp312-abi3-win32.whl", hash = "sha256:564c35a94811979808ab5800d9eb4f3f32c12daedba7e33ed0845f7c61ef2431", size = 16238216, upload-time = "2026-01-10T01:39:39.46Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c7/8690c81200ae652ac550c1df52f89d7795e6cc941f3cb38c9ef821419e80/onnx-1.20.1-cp312-abi3-win_amd64.whl", hash = "sha256:9fe7f9a633979d50984b94bda8ceb7807403f59a341d09d19342dc544d0ca1d5", size = 16389207, upload-time = "2026-01-10T01:39:41.955Z" }, - { url = "https://files.pythonhosted.org/packages/01/a0/4fb0e6d36eaf079af366b2c1f68bafe92df6db963e2295da84388af64abc/onnx-1.20.1-cp312-abi3-win_arm64.whl", hash = "sha256:21d747348b1c8207406fa2f3e12b82f53e0d5bb3958bcd0288bd27d3cb6ebb00", size = 16344155, upload-time = "2026-01-10T01:39:45.536Z" }, - { url = "https://files.pythonhosted.org/packages/ea/bb/715fad292b255664f0e603f1b2ef7bf2b386281775f37406beb99fa05957/onnx-1.20.1-cp313-cp313t-macosx_12_0_universal2.whl", hash = "sha256:29197b768f5acdd1568ddeb0a376407a2817844f6ac1ef8c8dd2d974c9ab27c3", size = 17912296, upload-time = "2026-01-10T01:39:48.21Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c3/541af12c3d45e159a94ee701100ba9e94b7bd8b7a8ac5ca6838569f894f8/onnx-1.20.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f0371aa67f51917a09cc829ada0f9a79a58f833449e03d748f7f7f53787c43c", size = 17416925, upload-time = "2026-01-10T01:39:50.82Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3b/d5660a7d2ddf14f531ca66d409239f543bb290277c3f14f4b4b78e32efa3/onnx-1.20.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be1e5522200b203b34327b2cf132ddec20ab063469476e1f5b02bb7bd259a489", size = 17515602, upload-time = "2026-01-10T01:39:54.132Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b4/47225ab2a92562eff87ba9a1a028e3535d659a7157d7cde659003998b8e3/onnx-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:15c815313bbc4b2fdc7e4daeb6e26b6012012adc4d850f4e3b09ed327a7ea92a", size = 16395729, upload-time = "2026-01-10T01:39:57.577Z" }, - { url = "https://files.pythonhosted.org/packages/aa/7d/1bbe626ff6b192c844d3ad34356840cc60fca02e2dea0db95e01645758b1/onnx-1.20.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eb335d7bcf9abac82a0d6a0fda0363531ae0b22cfd0fc6304bff32ee29905def", size = 16348968, upload-time = "2026-01-10T01:40:00.491Z" }, + { url = "https://files.pythonhosted.org/packages/7c/4c/4b17e82f91ab9aa07ff595771e935ca73547b035030dc5f5a76e63fbfea9/onnx-1.20.1-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:1d923bb4f0ce1b24c6859222a7e6b2f123e7bfe7623683662805f2e7b9e95af2", size = 17903547 }, + { url = "https://files.pythonhosted.org/packages/64/5e/1bfa100a9cb3f2d3d5f2f05f52f7e60323b0e20bb0abace1ae64dbc88f25/onnx-1.20.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddc0b7d8b5a94627dc86c533d5e415af94cbfd103019a582669dad1f56d30281", size = 17412021 }, + { url = "https://files.pythonhosted.org/packages/fb/71/d3fec0dcf9a7a99e7368112d9c765154e81da70fcba1e3121131a45c245b/onnx-1.20.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9336b6b8e6efcf5c490a845f6afd7e041c89a56199aeda384ed7d58fb953b080", size = 17510450 }, + { url = "https://files.pythonhosted.org/packages/74/a7/edce1403e05a46e59b502fae8e3350ceeac5841f8e8f1561e98562ed9b09/onnx-1.20.1-cp312-abi3-win32.whl", hash = "sha256:564c35a94811979808ab5800d9eb4f3f32c12daedba7e33ed0845f7c61ef2431", size = 16238216 }, + { url = "https://files.pythonhosted.org/packages/8b/c7/8690c81200ae652ac550c1df52f89d7795e6cc941f3cb38c9ef821419e80/onnx-1.20.1-cp312-abi3-win_amd64.whl", hash = "sha256:9fe7f9a633979d50984b94bda8ceb7807403f59a341d09d19342dc544d0ca1d5", size = 16389207 }, + { url = "https://files.pythonhosted.org/packages/01/a0/4fb0e6d36eaf079af366b2c1f68bafe92df6db963e2295da84388af64abc/onnx-1.20.1-cp312-abi3-win_arm64.whl", hash = "sha256:21d747348b1c8207406fa2f3e12b82f53e0d5bb3958bcd0288bd27d3cb6ebb00", size = 16344155 }, + { url = "https://files.pythonhosted.org/packages/ea/bb/715fad292b255664f0e603f1b2ef7bf2b386281775f37406beb99fa05957/onnx-1.20.1-cp313-cp313t-macosx_12_0_universal2.whl", hash = "sha256:29197b768f5acdd1568ddeb0a376407a2817844f6ac1ef8c8dd2d974c9ab27c3", size = 17912296 }, + { url = "https://files.pythonhosted.org/packages/2d/c3/541af12c3d45e159a94ee701100ba9e94b7bd8b7a8ac5ca6838569f894f8/onnx-1.20.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f0371aa67f51917a09cc829ada0f9a79a58f833449e03d748f7f7f53787c43c", size = 17416925 }, + { url = "https://files.pythonhosted.org/packages/2c/3b/d5660a7d2ddf14f531ca66d409239f543bb290277c3f14f4b4b78e32efa3/onnx-1.20.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be1e5522200b203b34327b2cf132ddec20ab063469476e1f5b02bb7bd259a489", size = 17515602 }, + { url = "https://files.pythonhosted.org/packages/9c/b4/47225ab2a92562eff87ba9a1a028e3535d659a7157d7cde659003998b8e3/onnx-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:15c815313bbc4b2fdc7e4daeb6e26b6012012adc4d850f4e3b09ed327a7ea92a", size = 16395729 }, + { url = "https://files.pythonhosted.org/packages/aa/7d/1bbe626ff6b192c844d3ad34356840cc60fca02e2dea0db95e01645758b1/onnx-1.20.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eb335d7bcf9abac82a0d6a0fda0363531ae0b22cfd0fc6304bff32ee29905def", size = 16348968 }, ] [[package]] @@ -5130,25 +5122,25 @@ dependencies = [ { name = "sympy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/38/31db1b232b4ba960065a90c1506ad7a56995cd8482033184e97fadca17cc/onnxruntime-1.24.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cad1c2b3f455c55678ab2a8caa51fb420c25e6e3cf10f4c23653cdabedc8de78", size = 17341875, upload-time = "2026-03-17T22:05:51.669Z" }, - { url = "https://files.pythonhosted.org/packages/aa/60/c4d1c8043eb42f8a9aa9e931c8c293d289c48ff463267130eca97d13357f/onnxruntime-1.24.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a5c5a544b22f90859c88617ecb30e161ee3349fcc73878854f43d77f00558b5", size = 15172485, upload-time = "2026-03-17T22:03:32.182Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ab/5b68110e0460d73fad814d5bd11c7b1ddcce5c37b10177eb264d6a36e331/onnxruntime-1.24.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d640eb9f3782689b55cfa715094474cd5662f2f137be6a6f847a594b6e9705c", size = 17244912, upload-time = "2026-03-17T22:04:37.251Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f4/6b89e297b93704345f0f3f8c62229bee323ef25682a3f9b4f89a39324950/onnxruntime-1.24.4-cp312-cp312-win_amd64.whl", hash = "sha256:535b29475ca42b593c45fbb2152fbf1cdf3f287315bf650e6a724a0a1d065cdb", size = 12596856, upload-time = "2026-03-17T22:05:41.224Z" }, - { url = "https://files.pythonhosted.org/packages/43/06/8b8ec6e9e6a474fcd5d772453f627ad4549dfe3ab8c0bf70af5afcde551b/onnxruntime-1.24.4-cp312-cp312-win_arm64.whl", hash = "sha256:e6214096e14b7b52e3bee1903dc12dc7ca09cb65e26664668a4620cc5e6f9a90", size = 12270275, upload-time = "2026-03-17T22:05:31.132Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f0/8a21ec0a97e40abb7d8da1e8b20fb9e1af509cc6d191f6faa75f73622fb2/onnxruntime-1.24.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e99a48078baaefa2b50fe5836c319499f71f13f76ed32d0211f39109147a49e0", size = 17341922, upload-time = "2026-03-17T22:03:56.364Z" }, - { url = "https://files.pythonhosted.org/packages/8b/25/d7908de8e08cee9abfa15b8aa82349b79733ae5865162a3609c11598805d/onnxruntime-1.24.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4aaed1e5e1aaacf2343c838a30a7c3ade78f13eeb16817411f929d04040a13", size = 15172290, upload-time = "2026-03-17T22:03:37.124Z" }, - { url = "https://files.pythonhosted.org/packages/7f/72/105ec27a78c5aa0154a7c0cd8c41c19a97799c3b12fc30392928997e3be3/onnxruntime-1.24.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e30c972bc02e072911aabb6891453ec73795386c0af2b761b65444b8a4c4745f", size = 17244738, upload-time = "2026-03-17T22:04:40.625Z" }, - { url = "https://files.pythonhosted.org/packages/05/fb/a592736d968c2f58e12de4d52088dda8e0e724b26ad5c0487263adb45875/onnxruntime-1.24.4-cp313-cp313-win_amd64.whl", hash = "sha256:3b6ba8b0181a3aa88edab00eb01424ffc06f42e71095a91186c2249415fcff93", size = 12597435, upload-time = "2026-03-17T22:05:43.826Z" }, - { url = "https://files.pythonhosted.org/packages/ad/04/ae2479e9841b64bd2eb44f8a64756c62593f896514369a11243b1b86ca5c/onnxruntime-1.24.4-cp313-cp313-win_arm64.whl", hash = "sha256:71d6a5c1821d6e8586a024000ece458db8f2fc0ecd050435d45794827ce81e19", size = 12269852, upload-time = "2026-03-17T22:05:33.353Z" }, - { url = "https://files.pythonhosted.org/packages/b4/af/a479a536c4398ffaf49fbbe755f45d5b8726bdb4335ab31b537f3d7149b8/onnxruntime-1.24.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1700f559c8086d06b2a4d5de51e62cb4ff5e2631822f71a36db8c72383db71ee", size = 15176861, upload-time = "2026-03-17T22:03:40.143Z" }, - { url = "https://files.pythonhosted.org/packages/be/13/19f5da70c346a76037da2c2851ecbf1266e61d7f0dcdb887c667210d4608/onnxruntime-1.24.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c74e268dc808e61e63784d43f9ddcdaf50a776c2819e8bd1d1b11ef64bf7e36", size = 17247454, upload-time = "2026-03-17T22:04:46.643Z" }, - { url = "https://files.pythonhosted.org/packages/89/db/b30dbbd6037847b205ab75d962bc349bf1e46d02a65b30d7047a6893ffd6/onnxruntime-1.24.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:fbff2a248940e3398ae78374c5a839e49a2f39079b488bc64439fa0ec327a3e4", size = 17343300, upload-time = "2026-03-17T22:03:59.223Z" }, - { url = "https://files.pythonhosted.org/packages/61/88/1746c0e7959961475b84c776d35601a21d445f463c93b1433a409ec3e188/onnxruntime-1.24.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2b7969e72d8cb53ffc88ab6d49dd5e75c1c663bda7be7eb0ece192f127343d1", size = 15175936, upload-time = "2026-03-17T22:03:43.671Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ba/4699cde04a52cece66cbebc85bd8335a0d3b9ad485abc9a2e15946a1349d/onnxruntime-1.24.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14ed1f197fab812b695a5eaddb536c635e58a2fbbe50a517c78f082cc6ce9177", size = 17246432, upload-time = "2026-03-17T22:04:49.58Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/4590910841bb28bd3b4b388a9efbedf4e2d2cca99ddf0c863642b4e87814/onnxruntime-1.24.4-cp314-cp314-win_amd64.whl", hash = "sha256:311e309f573bf3c12aa5723e23823077f83d5e412a18499d4485c7eb41040858", size = 12903276, upload-time = "2026-03-17T22:05:46.349Z" }, - { url = "https://files.pythonhosted.org/packages/7f/6f/60e2c0acea1e1ac09b3e794b5a19c166eebf91c0b860b3e6db8e74983fda/onnxruntime-1.24.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f0b910e86b759a4732663ec61fd57ac42ee1b0066f68299de164220b660546d", size = 12594365, upload-time = "2026-03-17T22:05:35.795Z" }, - { url = "https://files.pythonhosted.org/packages/cf/68/0c05d10f8f6c40fe0912ebec0d5a33884aaa2af2053507e864dab0883208/onnxruntime-1.24.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa12ddc54c9c4594073abcaa265cd9681e95fb89dae982a6f508a794ca42e661", size = 15176889, upload-time = "2026-03-17T22:03:48.021Z" }, - { url = "https://files.pythonhosted.org/packages/6c/1d/1666dc64e78d8587d168fec4e3b7922b92eb286a2ddeebcf6acb55c7dc82/onnxruntime-1.24.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1cc6a518255f012134bc791975a6294806be9a3b20c4a54cca25194c90cf731", size = 17247021, upload-time = "2026-03-17T22:04:52.377Z" }, + { url = "https://files.pythonhosted.org/packages/d7/38/31db1b232b4ba960065a90c1506ad7a56995cd8482033184e97fadca17cc/onnxruntime-1.24.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cad1c2b3f455c55678ab2a8caa51fb420c25e6e3cf10f4c23653cdabedc8de78", size = 17341875 }, + { url = "https://files.pythonhosted.org/packages/aa/60/c4d1c8043eb42f8a9aa9e931c8c293d289c48ff463267130eca97d13357f/onnxruntime-1.24.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a5c5a544b22f90859c88617ecb30e161ee3349fcc73878854f43d77f00558b5", size = 15172485 }, + { url = "https://files.pythonhosted.org/packages/6d/ab/5b68110e0460d73fad814d5bd11c7b1ddcce5c37b10177eb264d6a36e331/onnxruntime-1.24.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d640eb9f3782689b55cfa715094474cd5662f2f137be6a6f847a594b6e9705c", size = 17244912 }, + { url = "https://files.pythonhosted.org/packages/8b/f4/6b89e297b93704345f0f3f8c62229bee323ef25682a3f9b4f89a39324950/onnxruntime-1.24.4-cp312-cp312-win_amd64.whl", hash = "sha256:535b29475ca42b593c45fbb2152fbf1cdf3f287315bf650e6a724a0a1d065cdb", size = 12596856 }, + { url = "https://files.pythonhosted.org/packages/43/06/8b8ec6e9e6a474fcd5d772453f627ad4549dfe3ab8c0bf70af5afcde551b/onnxruntime-1.24.4-cp312-cp312-win_arm64.whl", hash = "sha256:e6214096e14b7b52e3bee1903dc12dc7ca09cb65e26664668a4620cc5e6f9a90", size = 12270275 }, + { url = "https://files.pythonhosted.org/packages/e9/f0/8a21ec0a97e40abb7d8da1e8b20fb9e1af509cc6d191f6faa75f73622fb2/onnxruntime-1.24.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e99a48078baaefa2b50fe5836c319499f71f13f76ed32d0211f39109147a49e0", size = 17341922 }, + { url = "https://files.pythonhosted.org/packages/8b/25/d7908de8e08cee9abfa15b8aa82349b79733ae5865162a3609c11598805d/onnxruntime-1.24.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4aaed1e5e1aaacf2343c838a30a7c3ade78f13eeb16817411f929d04040a13", size = 15172290 }, + { url = "https://files.pythonhosted.org/packages/7f/72/105ec27a78c5aa0154a7c0cd8c41c19a97799c3b12fc30392928997e3be3/onnxruntime-1.24.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e30c972bc02e072911aabb6891453ec73795386c0af2b761b65444b8a4c4745f", size = 17244738 }, + { url = "https://files.pythonhosted.org/packages/05/fb/a592736d968c2f58e12de4d52088dda8e0e724b26ad5c0487263adb45875/onnxruntime-1.24.4-cp313-cp313-win_amd64.whl", hash = "sha256:3b6ba8b0181a3aa88edab00eb01424ffc06f42e71095a91186c2249415fcff93", size = 12597435 }, + { url = "https://files.pythonhosted.org/packages/ad/04/ae2479e9841b64bd2eb44f8a64756c62593f896514369a11243b1b86ca5c/onnxruntime-1.24.4-cp313-cp313-win_arm64.whl", hash = "sha256:71d6a5c1821d6e8586a024000ece458db8f2fc0ecd050435d45794827ce81e19", size = 12269852 }, + { url = "https://files.pythonhosted.org/packages/b4/af/a479a536c4398ffaf49fbbe755f45d5b8726bdb4335ab31b537f3d7149b8/onnxruntime-1.24.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1700f559c8086d06b2a4d5de51e62cb4ff5e2631822f71a36db8c72383db71ee", size = 15176861 }, + { url = "https://files.pythonhosted.org/packages/be/13/19f5da70c346a76037da2c2851ecbf1266e61d7f0dcdb887c667210d4608/onnxruntime-1.24.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c74e268dc808e61e63784d43f9ddcdaf50a776c2819e8bd1d1b11ef64bf7e36", size = 17247454 }, + { url = "https://files.pythonhosted.org/packages/89/db/b30dbbd6037847b205ab75d962bc349bf1e46d02a65b30d7047a6893ffd6/onnxruntime-1.24.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:fbff2a248940e3398ae78374c5a839e49a2f39079b488bc64439fa0ec327a3e4", size = 17343300 }, + { url = "https://files.pythonhosted.org/packages/61/88/1746c0e7959961475b84c776d35601a21d445f463c93b1433a409ec3e188/onnxruntime-1.24.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2b7969e72d8cb53ffc88ab6d49dd5e75c1c663bda7be7eb0ece192f127343d1", size = 15175936 }, + { url = "https://files.pythonhosted.org/packages/5f/ba/4699cde04a52cece66cbebc85bd8335a0d3b9ad485abc9a2e15946a1349d/onnxruntime-1.24.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14ed1f197fab812b695a5eaddb536c635e58a2fbbe50a517c78f082cc6ce9177", size = 17246432 }, + { url = "https://files.pythonhosted.org/packages/ef/60/4590910841bb28bd3b4b388a9efbedf4e2d2cca99ddf0c863642b4e87814/onnxruntime-1.24.4-cp314-cp314-win_amd64.whl", hash = "sha256:311e309f573bf3c12aa5723e23823077f83d5e412a18499d4485c7eb41040858", size = 12903276 }, + { url = "https://files.pythonhosted.org/packages/7f/6f/60e2c0acea1e1ac09b3e794b5a19c166eebf91c0b860b3e6db8e74983fda/onnxruntime-1.24.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f0b910e86b759a4732663ec61fd57ac42ee1b0066f68299de164220b660546d", size = 12594365 }, + { url = "https://files.pythonhosted.org/packages/cf/68/0c05d10f8f6c40fe0912ebec0d5a33884aaa2af2053507e864dab0883208/onnxruntime-1.24.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa12ddc54c9c4594073abcaa265cd9681e95fb89dae982a6f508a794ca42e661", size = 15176889 }, + { url = "https://files.pythonhosted.org/packages/6c/1d/1666dc64e78d8587d168fec4e3b7922b92eb286a2ddeebcf6acb55c7dc82/onnxruntime-1.24.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1cc6a518255f012134bc791975a6294806be9a3b20c4a54cca25194c90cf731", size = 17247021 }, ] [[package]] @@ -5165,9 +5157,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/13/17e87641b89b74552ed408a92b231283786523edddc95f3545809fab673c/openai-2.24.0.tar.gz", hash = "sha256:1e5769f540dbd01cb33bc4716a23e67b9d695161a734aff9c5f925e2bf99a673", size = 658717, upload-time = "2026-02-24T20:02:07.958Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/13/17e87641b89b74552ed408a92b231283786523edddc95f3545809fab673c/openai-2.24.0.tar.gz", hash = "sha256:1e5769f540dbd01cb33bc4716a23e67b9d695161a734aff9c5f925e2bf99a673", size = 658717 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/30/844dc675ee6902579b8eef01ed23917cc9319a1c9c0c14ec6e39340c96d0/openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94", size = 1120122, upload-time = "2026-02-24T20:02:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/c9/30/844dc675ee6902579b8eef01ed23917cc9319a1c9c0c14ec6e39340c96d0/openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94", size = 1120122 }, ] [[package]] @@ -5178,14 +5170,14 @@ dependencies = [ { name = "numpy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/6f/5a28fef4c4a382be06afe3938c64cc168223016fa520c5abaf37e8862aa5/opencv_python-4.13.0.92-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:caf60c071ec391ba51ed00a4a920f996d0b64e3e46068aac1f646b5de0326a19", size = 46247052, upload-time = "2026-02-05T07:01:25.046Z" }, - { url = "https://files.pythonhosted.org/packages/08/ac/6c98c44c650b8114a0fb901691351cfb3956d502e8e9b5cd27f4ee7fbf2f/opencv_python-4.13.0.92-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:5868a8c028a0b37561579bfb8ac1875babdc69546d236249fff296a8c010ccf9", size = 32568781, upload-time = "2026-02-05T07:01:41.379Z" }, - { url = "https://files.pythonhosted.org/packages/3e/51/82fed528b45173bf629fa44effb76dff8bc9f4eeaee759038362dfa60237/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bc2596e68f972ca452d80f444bc404e08807d021fbba40df26b61b18e01838a", size = 47685527, upload-time = "2026-02-05T06:59:11.24Z" }, - { url = "https://files.pythonhosted.org/packages/db/07/90b34a8e2cf9c50fe8ed25cac9011cde0676b4d9d9c973751ac7616223a2/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:402033cddf9d294693094de5ef532339f14ce821da3ad7df7c9f6e8316da32cf", size = 70460872, upload-time = "2026-02-05T06:59:19.162Z" }, - { url = "https://files.pythonhosted.org/packages/02/6d/7a9cc719b3eaf4377b9c2e3edeb7ed3a81de41f96421510c0a169ca3cfd4/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bccaabf9eb7f897ca61880ce2869dcd9b25b72129c28478e7f2a5e8dee945616", size = 46708208, upload-time = "2026-02-05T06:59:15.419Z" }, - { url = "https://files.pythonhosted.org/packages/fd/55/b3b49a1b97aabcfbbd6c7326df9cb0b6fa0c0aefa8e89d500939e04aa229/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:620d602b8f7d8b8dab5f4b99c6eb353e78d3fb8b0f53db1bd258bb1aa001c1d5", size = 72927042, upload-time = "2026-02-05T06:59:23.389Z" }, - { url = "https://files.pythonhosted.org/packages/fb/17/de5458312bcb07ddf434d7bfcb24bb52c59635ad58c6e7c751b48949b009/opencv_python-4.13.0.92-cp37-abi3-win32.whl", hash = "sha256:372fe164a3148ac1ca51e5f3ad0541a4a276452273f503441d718fab9c5e5f59", size = 30932638, upload-time = "2026-02-05T07:02:14.98Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a5/1be1516390333ff9be3a9cb648c9f33df79d5096e5884b5df71a588af463/opencv_python-4.13.0.92-cp37-abi3-win_amd64.whl", hash = "sha256:423d934c9fafb91aad38edf26efb46da91ffbc05f3f59c4b0c72e699720706f5", size = 40212062, upload-time = "2026-02-05T07:02:12.724Z" }, + { url = "https://files.pythonhosted.org/packages/fc/6f/5a28fef4c4a382be06afe3938c64cc168223016fa520c5abaf37e8862aa5/opencv_python-4.13.0.92-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:caf60c071ec391ba51ed00a4a920f996d0b64e3e46068aac1f646b5de0326a19", size = 46247052 }, + { url = "https://files.pythonhosted.org/packages/08/ac/6c98c44c650b8114a0fb901691351cfb3956d502e8e9b5cd27f4ee7fbf2f/opencv_python-4.13.0.92-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:5868a8c028a0b37561579bfb8ac1875babdc69546d236249fff296a8c010ccf9", size = 32568781 }, + { url = "https://files.pythonhosted.org/packages/3e/51/82fed528b45173bf629fa44effb76dff8bc9f4eeaee759038362dfa60237/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bc2596e68f972ca452d80f444bc404e08807d021fbba40df26b61b18e01838a", size = 47685527 }, + { url = "https://files.pythonhosted.org/packages/db/07/90b34a8e2cf9c50fe8ed25cac9011cde0676b4d9d9c973751ac7616223a2/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:402033cddf9d294693094de5ef532339f14ce821da3ad7df7c9f6e8316da32cf", size = 70460872 }, + { url = "https://files.pythonhosted.org/packages/02/6d/7a9cc719b3eaf4377b9c2e3edeb7ed3a81de41f96421510c0a169ca3cfd4/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bccaabf9eb7f897ca61880ce2869dcd9b25b72129c28478e7f2a5e8dee945616", size = 46708208 }, + { url = "https://files.pythonhosted.org/packages/fd/55/b3b49a1b97aabcfbbd6c7326df9cb0b6fa0c0aefa8e89d500939e04aa229/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:620d602b8f7d8b8dab5f4b99c6eb353e78d3fb8b0f53db1bd258bb1aa001c1d5", size = 72927042 }, + { url = "https://files.pythonhosted.org/packages/fb/17/de5458312bcb07ddf434d7bfcb24bb52c59635ad58c6e7c751b48949b009/opencv_python-4.13.0.92-cp37-abi3-win32.whl", hash = "sha256:372fe164a3148ac1ca51e5f3ad0541a4a276452273f503441d718fab9c5e5f59", size = 30932638 }, + { url = "https://files.pythonhosted.org/packages/e9/a5/1be1516390333ff9be3a9cb648c9f33df79d5096e5884b5df71a588af463/opencv_python-4.13.0.92-cp37-abi3-win_amd64.whl", hash = "sha256:423d934c9fafb91aad38edf26efb46da91ffbc05f3f59c4b0c72e699720706f5", size = 40212062 }, ] [[package]] @@ -5195,9 +5187,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "et-xmlfile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910 }, ] [[package]] @@ -5208,9 +5200,9 @@ dependencies = [ { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, + { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676 }, ] [[package]] @@ -5221,9 +5213,9 @@ dependencies = [ { name = "opentelemetry-exporter-otlp-proto-grpc" }, { name = "opentelemetry-exporter-otlp-proto-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d0/37/b6708e0eff5c5fb9aba2e0ea09f7f3bcbfd12a592d2a780241b5f6014df7/opentelemetry_exporter_otlp-1.40.0.tar.gz", hash = "sha256:7caa0870b95e2fcb59d64e16e2b639ecffb07771b6cd0000b5d12e5e4fef765a", size = 6152, upload-time = "2026-03-04T14:17:23.235Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/37/b6708e0eff5c5fb9aba2e0ea09f7f3bcbfd12a592d2a780241b5f6014df7/opentelemetry_exporter_otlp-1.40.0.tar.gz", hash = "sha256:7caa0870b95e2fcb59d64e16e2b639ecffb07771b6cd0000b5d12e5e4fef765a", size = 6152 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/fc/aea77c28d9f3ffef2fdafdc3f4a235aee4091d262ddabd25882f47ce5c5f/opentelemetry_exporter_otlp-1.40.0-py3-none-any.whl", hash = "sha256:48c87e539ec9afb30dc443775a1334cc5487de2f72a770a4c00b1610bf6c697d", size = 7023, upload-time = "2026-03-04T14:17:03.612Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fc/aea77c28d9f3ffef2fdafdc3f4a235aee4091d262ddabd25882f47ce5c5f/opentelemetry_exporter_otlp-1.40.0-py3-none-any.whl", hash = "sha256:48c87e539ec9afb30dc443775a1334cc5487de2f72a770a4c00b1610bf6c697d", size = 7023 }, ] [[package]] @@ -5233,9 +5225,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369 }, ] [[package]] @@ -5251,9 +5243,9 @@ dependencies = [ { name = "opentelemetry-sdk" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8f/7f/b9e60435cfcc7590fa87436edad6822240dddbc184643a2a005301cc31f4/opentelemetry_exporter_otlp_proto_grpc-1.40.0.tar.gz", hash = "sha256:bd4015183e40b635b3dab8da528b27161ba83bf4ef545776b196f0fb4ec47740", size = 25759, upload-time = "2026-03-04T14:17:24.4Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/7f/b9e60435cfcc7590fa87436edad6822240dddbc184643a2a005301cc31f4/opentelemetry_exporter_otlp_proto_grpc-1.40.0.tar.gz", hash = "sha256:bd4015183e40b635b3dab8da528b27161ba83bf4ef545776b196f0fb4ec47740", size = 25759 } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/6f/7ee0980afcbdcd2d40362da16f7f9796bd083bf7f0b8e038abfbc0300f5d/opentelemetry_exporter_otlp_proto_grpc-1.40.0-py3-none-any.whl", hash = "sha256:2aa0ca53483fe0cf6405087a7491472b70335bc5c7944378a0a8e72e86995c52", size = 20304, upload-time = "2026-03-04T14:17:05.942Z" }, + { url = "https://files.pythonhosted.org/packages/96/6f/7ee0980afcbdcd2d40362da16f7f9796bd083bf7f0b8e038abfbc0300f5d/opentelemetry_exporter_otlp_proto_grpc-1.40.0-py3-none-any.whl", hash = "sha256:2aa0ca53483fe0cf6405087a7491472b70335bc5c7944378a0a8e72e86995c52", size = 20304 }, ] [[package]] @@ -5269,9 +5261,9 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, + { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960 }, ] [[package]] @@ -5284,9 +5276,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448 }, ] [[package]] @@ -5300,9 +5292,9 @@ dependencies = [ { name = "opentelemetry-util-http" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/24fed4de661de107f2426b28bbd87b51eaab28a2339b62f269a36ae24505/opentelemetry_instrumentation_aiohttp_client-0.61b0.tar.gz", hash = "sha256:c53ab3b88efcb7ce98c1129cc0389f0a1f214eb3675269b6c157770adcf47877", size = 19292, upload-time = "2026-03-04T14:20:18.408Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/24fed4de661de107f2426b28bbd87b51eaab28a2339b62f269a36ae24505/opentelemetry_instrumentation_aiohttp_client-0.61b0.tar.gz", hash = "sha256:c53ab3b88efcb7ce98c1129cc0389f0a1f214eb3675269b6c157770adcf47877", size = 19292 } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/f3/1edc42716521a3f754ac32ffb908f102e0f131f8e43fcd9ab29cab286723/opentelemetry_instrumentation_aiohttp_client-0.61b0-py3-none-any.whl", hash = "sha256:09bc47514c162507b357366ce15578743fd6305078cf7d872db1c99c13fa6972", size = 14534, upload-time = "2026-03-04T14:19:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/df/f3/1edc42716521a3f754ac32ffb908f102e0f131f8e43fcd9ab29cab286723/opentelemetry_instrumentation_aiohttp_client-0.61b0-py3-none-any.whl", hash = "sha256:09bc47514c162507b357366ce15578743fd6305078cf7d872db1c99c13fa6972", size = 14534 }, ] [[package]] @@ -5316,9 +5308,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-util-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/3e/143cf5c034e58037307e6a24f06e0dd64b2c49ae60a965fc580027581931/opentelemetry_instrumentation_asgi-0.61b0.tar.gz", hash = "sha256:9d08e127244361dc33976d39dd4ca8f128b5aa5a7ae425208400a80a095019b5", size = 26691, upload-time = "2026-03-04T14:20:21.038Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/3e/143cf5c034e58037307e6a24f06e0dd64b2c49ae60a965fc580027581931/opentelemetry_instrumentation_asgi-0.61b0.tar.gz", hash = "sha256:9d08e127244361dc33976d39dd4ca8f128b5aa5a7ae425208400a80a095019b5", size = 26691 } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/78/154470cf9d741a7487fbb5067357b87386475bbb77948a6707cae982e158/opentelemetry_instrumentation_asgi-0.61b0-py3-none-any.whl", hash = "sha256:e4b3ce6b66074e525e717efff20745434e5efd5d9df6557710856fba356da7a4", size = 16980, upload-time = "2026-03-04T14:19:10.894Z" }, + { url = "https://files.pythonhosted.org/packages/19/78/154470cf9d741a7487fbb5067357b87386475bbb77948a6707cae982e158/opentelemetry_instrumentation_asgi-0.61b0-py3-none-any.whl", hash = "sha256:e4b3ce6b66074e525e717efff20745434e5efd5d9df6557710856fba356da7a4", size = 16980 }, ] [[package]] @@ -5330,9 +5322,9 @@ dependencies = [ { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-semantic-conventions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/43/e79108a804d16b1dc8ff28edd0e94ac393cf6359a5adcd7cdd2ec4be85f4/opentelemetry_instrumentation_celery-0.61b0.tar.gz", hash = "sha256:0e352a567dc89ed8bc083fc635035ce3c5b96bbbd92831ffd676e93b87f8e94f", size = 14780, upload-time = "2026-03-04T14:20:27.776Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/43/e79108a804d16b1dc8ff28edd0e94ac393cf6359a5adcd7cdd2ec4be85f4/opentelemetry_instrumentation_celery-0.61b0.tar.gz", hash = "sha256:0e352a567dc89ed8bc083fc635035ce3c5b96bbbd92831ffd676e93b87f8e94f", size = 14780 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/ed/c05f3c84b455654eb6c047474ffde61ed92efc24030f64213c98bca9d44b/opentelemetry_instrumentation_celery-0.61b0-py3-none-any.whl", hash = "sha256:01235733ff0cdf571cb03b270645abb14b9c8d830313dc5842097ec90146320b", size = 13856, upload-time = "2026-03-04T14:19:20.98Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ed/c05f3c84b455654eb6c047474ffde61ed92efc24030f64213c98bca9d44b/opentelemetry_instrumentation_celery-0.61b0-py3-none-any.whl", hash = "sha256:01235733ff0cdf571cb03b270645abb14b9c8d830313dc5842097ec90146320b", size = 13856 }, ] [[package]] @@ -5345,9 +5337,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/ed/ba91c9e4a3ec65781e9c59982109f0a36de9fa574f622596b33d1985dab5/opentelemetry_instrumentation_dbapi-0.61b0.tar.gz", hash = "sha256:02fa800682c1de87dcad0e59f2092b3b6fb8b8ea0636518f989e1166b418dcb9", size = 16761, upload-time = "2026-03-04T14:20:29.782Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/ed/ba91c9e4a3ec65781e9c59982109f0a36de9fa574f622596b33d1985dab5/opentelemetry_instrumentation_dbapi-0.61b0.tar.gz", hash = "sha256:02fa800682c1de87dcad0e59f2092b3b6fb8b8ea0636518f989e1166b418dcb9", size = 16761 } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/a5/d26c68f3fd33eb7410985cef7700bb426e2c4a26de9207902cbbffb19a3f/opentelemetry_instrumentation_dbapi-0.61b0-py3-none-any.whl", hash = "sha256:8f762c39c8edd20c6aef3282550a2cfbfec76c3f431bf5c36327dcf9ece2e5a0", size = 14134, upload-time = "2026-03-04T14:19:24.718Z" }, + { url = "https://files.pythonhosted.org/packages/73/a5/d26c68f3fd33eb7410985cef7700bb426e2c4a26de9207902cbbffb19a3f/opentelemetry_instrumentation_dbapi-0.61b0-py3-none-any.whl", hash = "sha256:8f762c39c8edd20c6aef3282550a2cfbfec76c3f431bf5c36327dcf9ece2e5a0", size = 14134 }, ] [[package]] @@ -5361,9 +5353,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "opentelemetry-util-http" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/35/aa727bb6e6ef930dcdc96a617b83748fece57b43c47d83ba8d83fbeca657/opentelemetry_instrumentation_fastapi-0.61b0.tar.gz", hash = "sha256:3a24f35b07c557ae1bbc483bf8412221f25d79a405f8b047de8b670722e2fa9f", size = 24800, upload-time = "2026-03-04T14:20:32.759Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/35/aa727bb6e6ef930dcdc96a617b83748fece57b43c47d83ba8d83fbeca657/opentelemetry_instrumentation_fastapi-0.61b0.tar.gz", hash = "sha256:3a24f35b07c557ae1bbc483bf8412221f25d79a405f8b047de8b670722e2fa9f", size = 24800 } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/05/acfeb2cccd434242a0a7d0ea29afaf077e04b42b35b485d89aee4e0d9340/opentelemetry_instrumentation_fastapi-0.61b0-py3-none-any.whl", hash = "sha256:a1a844d846540d687d377516b2ff698b51d87c781b59f47c214359c4a241047c", size = 13485, upload-time = "2026-03-04T14:19:30.351Z" }, + { url = "https://files.pythonhosted.org/packages/91/05/acfeb2cccd434242a0a7d0ea29afaf077e04b42b35b485d89aee4e0d9340/opentelemetry_instrumentation_fastapi-0.61b0-py3-none-any.whl", hash = "sha256:a1a844d846540d687d377516b2ff698b51d87c781b59f47c214359c4a241047c", size = 13485 }, ] [[package]] @@ -5377,9 +5369,9 @@ dependencies = [ { name = "opentelemetry-util-http" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/2a/e2becd55e33c29d1d9ef76e2579040ed1951cb33bacba259f6aff2fdd2a6/opentelemetry_instrumentation_httpx-0.61b0.tar.gz", hash = "sha256:6569ec097946c5551c2a4252f74c98666addd1bf047c1dde6b4ef426719ff8dd", size = 24104, upload-time = "2026-03-04T14:20:34.752Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/2a/e2becd55e33c29d1d9ef76e2579040ed1951cb33bacba259f6aff2fdd2a6/opentelemetry_instrumentation_httpx-0.61b0.tar.gz", hash = "sha256:6569ec097946c5551c2a4252f74c98666addd1bf047c1dde6b4ef426719ff8dd", size = 24104 } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/88/dde310dce56e2d85cf1a09507f5888544955309edc4b8d22971d6d3d1417/opentelemetry_instrumentation_httpx-0.61b0-py3-none-any.whl", hash = "sha256:dee05c93a6593a5dc3ae5d9d5c01df8b4e2c5d02e49275e5558534ee46343d5e", size = 17198, upload-time = "2026-03-04T14:19:33.585Z" }, + { url = "https://files.pythonhosted.org/packages/af/88/dde310dce56e2d85cf1a09507f5888544955309edc4b8d22971d6d3d1417/opentelemetry_instrumentation_httpx-0.61b0-py3-none-any.whl", hash = "sha256:dee05c93a6593a5dc3ae5d9d5c01df8b4e2c5d02e49275e5558534ee46343d5e", size = 17198 }, ] [[package]] @@ -5390,9 +5382,9 @@ dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-instrumentation" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/e0/69473f925acfe2d4edf5c23bcced36906ac3627aa7c5722a8e3f60825f3b/opentelemetry_instrumentation_logging-0.61b0.tar.gz", hash = "sha256:feaa30b700acd2a37cc81db5f562ab0c3a5b6cc2453595e98b72c01dcf649584", size = 17906, upload-time = "2026-03-04T14:20:37.398Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/e0/69473f925acfe2d4edf5c23bcced36906ac3627aa7c5722a8e3f60825f3b/opentelemetry_instrumentation_logging-0.61b0.tar.gz", hash = "sha256:feaa30b700acd2a37cc81db5f562ab0c3a5b6cc2453595e98b72c01dcf649584", size = 17906 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/0e/2137db5239cc5e564495549a4d11488a7af9b48fc76520a0eea20e69ddae/opentelemetry_instrumentation_logging-0.61b0-py3-none-any.whl", hash = "sha256:6d87e5ded6a0128d775d41511f8380910a1b610671081d16efb05ac3711c0074", size = 17076, upload-time = "2026-03-04T14:19:36.765Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0e/2137db5239cc5e564495549a4d11488a7af9b48fc76520a0eea20e69ddae/opentelemetry_instrumentation_logging-0.61b0-py3-none-any.whl", hash = "sha256:6d87e5ded6a0128d775d41511f8380910a1b610671081d16efb05ac3711c0074", size = 17076 }, ] [[package]] @@ -5404,9 +5396,9 @@ dependencies = [ { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-instrumentation-dbapi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/99/00/b98148b3054eb8301a56d523de82ee2fd86a047dba38330c2404d85496e3/opentelemetry_instrumentation_psycopg-0.61b0.tar.gz", hash = "sha256:74e9fed3802945f7ae335cffc30fd18cf58c34a4d0619315f799fa21eb5c74ff", size = 11907, upload-time = "2026-03-04T14:20:39.958Z" } +sdist = { url = "https://files.pythonhosted.org/packages/99/00/b98148b3054eb8301a56d523de82ee2fd86a047dba38330c2404d85496e3/opentelemetry_instrumentation_psycopg-0.61b0.tar.gz", hash = "sha256:74e9fed3802945f7ae335cffc30fd18cf58c34a4d0619315f799fa21eb5c74ff", size = 11907 } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/2b/3c36bfc6dc82a7c50c769aff407eaf32e688d655bc61a774609d96b55603/opentelemetry_instrumentation_psycopg-0.61b0-py3-none-any.whl", hash = "sha256:a3e242cad56c0ad4f4f872017c73ce7e6c7012081dda6bd0d776c127fedc358a", size = 11662, upload-time = "2026-03-04T14:19:41.108Z" }, + { url = "https://files.pythonhosted.org/packages/db/2b/3c36bfc6dc82a7c50c769aff407eaf32e688d655bc61a774609d96b55603/opentelemetry_instrumentation_psycopg-0.61b0-py3-none-any.whl", hash = "sha256:a3e242cad56c0ad4f4f872017c73ce7e6c7012081dda6bd0d776c127fedc358a", size = 11662 }, ] [[package]] @@ -5419,9 +5411,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cf/21/26205f89358a5f2be3ee5512d3d3bce16b622977f64aeaa9d3fa8887dd39/opentelemetry_instrumentation_redis-0.61b0.tar.gz", hash = "sha256:ae0fbb56be9a641e621d55b02a7d62977a2c77c5ee760addd79b9b266e46e523", size = 14781, upload-time = "2026-03-04T14:20:45.694Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/21/26205f89358a5f2be3ee5512d3d3bce16b622977f64aeaa9d3fa8887dd39/opentelemetry_instrumentation_redis-0.61b0.tar.gz", hash = "sha256:ae0fbb56be9a641e621d55b02a7d62977a2c77c5ee760addd79b9b266e46e523", size = 14781 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/e1/8f4c8e4194291dbe828aeabe779050a8497b379ad90040a5a0a7074b1d08/opentelemetry_instrumentation_redis-0.61b0-py3-none-any.whl", hash = "sha256:8d4e850bbb5f8eeafa44c0eac3a007990c7125de187bc9c3659e29ff7e091172", size = 15506, upload-time = "2026-03-04T14:19:48.588Z" }, + { url = "https://files.pythonhosted.org/packages/a5/e1/8f4c8e4194291dbe828aeabe779050a8497b379ad90040a5a0a7074b1d08/opentelemetry_instrumentation_redis-0.61b0-py3-none-any.whl", hash = "sha256:8d4e850bbb5f8eeafa44c0eac3a007990c7125de187bc9c3659e29ff7e091172", size = 15506 }, ] [[package]] @@ -5435,9 +5427,9 @@ dependencies = [ { name = "packaging" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9e/4f/3a325b180944610697a0a926d49d782b41a86120050d44fefb2715b630ac/opentelemetry_instrumentation_sqlalchemy-0.61b0.tar.gz", hash = "sha256:13a3a159a2043a52f0180b3757fbaa26741b0e08abb50deddce4394c118956e6", size = 15343, upload-time = "2026-03-04T14:20:47.648Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/4f/3a325b180944610697a0a926d49d782b41a86120050d44fefb2715b630ac/opentelemetry_instrumentation_sqlalchemy-0.61b0.tar.gz", hash = "sha256:13a3a159a2043a52f0180b3757fbaa26741b0e08abb50deddce4394c118956e6", size = 15343 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/97/b906a930c6a1a20c53ecc8b58cabc2cdd0ce560a2b5d44259084ffe4333e/opentelemetry_instrumentation_sqlalchemy-0.61b0-py3-none-any.whl", hash = "sha256:f115e0be54116ba4c327b8d7b68db4045ee18d44439d888ab8130a549c50d1c1", size = 14547, upload-time = "2026-03-04T14:19:53.088Z" }, + { url = "https://files.pythonhosted.org/packages/1f/97/b906a930c6a1a20c53ecc8b58cabc2cdd0ce560a2b5d44259084ffe4333e/opentelemetry_instrumentation_sqlalchemy-0.61b0-py3-none-any.whl", hash = "sha256:f115e0be54116ba4c327b8d7b68db4045ee18d44439d888ab8130a549c50d1c1", size = 14547 }, ] [[package]] @@ -5447,9 +5439,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073 }, ] [[package]] @@ -5461,9 +5453,9 @@ dependencies = [ { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951 }, ] [[package]] @@ -5474,128 +5466,128 @@ dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621 }, ] [[package]] name = "opentelemetry-util-http" version = "0.61b0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/3c/f0196223efc5c4ca19f8fad3d5462b171ac6333013335ce540c01af419e9/opentelemetry_util_http-0.61b0.tar.gz", hash = "sha256:1039cb891334ad2731affdf034d8fb8b48c239af9b6dd295e5fabd07f1c95572", size = 11361, upload-time = "2026-03-04T14:20:57.01Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/3c/f0196223efc5c4ca19f8fad3d5462b171ac6333013335ce540c01af419e9/opentelemetry_util_http-0.61b0.tar.gz", hash = "sha256:1039cb891334ad2731affdf034d8fb8b48c239af9b6dd295e5fabd07f1c95572", size = 11361 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/e5/c08aaaf2f64288d2b6ef65741d2de5454e64af3e050f34285fb1907492fe/opentelemetry_util_http-0.61b0-py3-none-any.whl", hash = "sha256:8e715e848233e9527ea47e275659ea60a57a75edf5206a3b937e236a6da5fc33", size = 9281, upload-time = "2026-03-04T14:20:08.364Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e5/c08aaaf2f64288d2b6ef65741d2de5454e64af3e050f34285fb1907492fe/opentelemetry_util_http-0.61b0-py3-none-any.whl", hash = "sha256:8e715e848233e9527ea47e275659ea60a57a75edf5206a3b937e236a6da5fc33", size = 9281 }, ] [[package]] name = "orjson" version = "3.11.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992, upload-time = "2026-02-02T15:38:49.29Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992 } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545, upload-time = "2026-02-02T15:37:46.376Z" }, - { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224, upload-time = "2026-02-02T15:37:47.697Z" }, - { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154, upload-time = "2026-02-02T15:37:48.954Z" }, - { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548, upload-time = "2026-02-02T15:37:50.187Z" }, - { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000, upload-time = "2026-02-02T15:37:51.426Z" }, - { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686, upload-time = "2026-02-02T15:37:52.607Z" }, - { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812, upload-time = "2026-02-02T15:37:53.944Z" }, - { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440, upload-time = "2026-02-02T15:37:55.615Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386, upload-time = "2026-02-02T15:37:56.814Z" }, - { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853, upload-time = "2026-02-02T15:37:58.291Z" }, - { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130, upload-time = "2026-02-02T15:38:00.163Z" }, - { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818, upload-time = "2026-02-02T15:38:01.507Z" }, - { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923, upload-time = "2026-02-02T15:38:02.75Z" }, - { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007, upload-time = "2026-02-02T15:38:04.032Z" }, - { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089, upload-time = "2026-02-02T15:38:05.297Z" }, - { url = "https://files.pythonhosted.org/packages/89/25/6e0e52cac5aab51d7b6dcd257e855e1dec1c2060f6b28566c509b4665f62/orjson-3.11.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1d98b30cc1313d52d4af17d9c3d307b08389752ec5f2e5febdfada70b0f8c733", size = 228390, upload-time = "2026-02-02T15:38:06.8Z" }, - { url = "https://files.pythonhosted.org/packages/a5/29/a77f48d2fc8a05bbc529e5ff481fb43d914f9e383ea2469d4f3d51df3d00/orjson-3.11.7-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d897e81f8d0cbd2abb82226d1860ad2e1ab3ff16d7b08c96ca00df9d45409ef4", size = 125189, upload-time = "2026-02-02T15:38:08.181Z" }, - { url = "https://files.pythonhosted.org/packages/89/25/0a16e0729a0e6a1504f9d1a13cdd365f030068aab64cec6958396b9969d7/orjson-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814be4b49b228cfc0b3c565acf642dd7d13538f966e3ccde61f4f55be3e20785", size = 128106, upload-time = "2026-02-02T15:38:09.41Z" }, - { url = "https://files.pythonhosted.org/packages/66/da/a2e505469d60666a05ab373f1a6322eb671cb2ba3a0ccfc7d4bc97196787/orjson-3.11.7-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d06e5c5fed5caedd2e540d62e5b1c25e8c82431b9e577c33537e5fa4aa909539", size = 123363, upload-time = "2026-02-02T15:38:10.73Z" }, - { url = "https://files.pythonhosted.org/packages/23/bf/ed73f88396ea35c71b38961734ea4a4746f7ca0768bf28fd551d37e48dd0/orjson-3.11.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31c80ce534ac4ea3739c5ee751270646cbc46e45aea7576a38ffec040b4029a1", size = 129007, upload-time = "2026-02-02T15:38:12.138Z" }, - { url = "https://files.pythonhosted.org/packages/73/3c/b05d80716f0225fc9008fbf8ab22841dcc268a626aa550561743714ce3bf/orjson-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1", size = 141667, upload-time = "2026-02-02T15:38:13.398Z" }, - { url = "https://files.pythonhosted.org/packages/61/e8/0be9b0addd9bf86abfc938e97441dcd0375d494594b1c8ad10fe57479617/orjson-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e54f3808e2b6b945078c41aa8d9b5834b28c50843846e97807e5adb75fa9705", size = 130832, upload-time = "2026-02-02T15:38:14.698Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ec/c68e3b9021a31d9ec15a94931db1410136af862955854ed5dd7e7e4f5bff/orjson-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12b80df61aab7b98b490fe9e4879925ba666fccdfcd175252ce4d9035865ace", size = 133373, upload-time = "2026-02-02T15:38:16.109Z" }, - { url = "https://files.pythonhosted.org/packages/d2/45/f3466739aaafa570cc8e77c6dbb853c48bf56e3b43738020e2661e08b0ac/orjson-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:996b65230271f1a97026fd0e6a753f51fbc0c335d2ad0c6201f711b0da32693b", size = 138307, upload-time = "2026-02-02T15:38:17.453Z" }, - { url = "https://files.pythonhosted.org/packages/e1/84/9f7f02288da1ffb31405c1be07657afd1eecbcb4b64ee2817b6fe0f785fa/orjson-3.11.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ab49d4b2a6a1d415ddb9f37a21e02e0d5dbfe10b7870b21bf779fc21e9156157", size = 408695, upload-time = "2026-02-02T15:38:18.831Z" }, - { url = "https://files.pythonhosted.org/packages/18/07/9dd2f0c0104f1a0295ffbe912bc8d63307a539b900dd9e2c48ef7810d971/orjson-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:390a1dce0c055ddf8adb6aa94a73b45a4a7d7177b5c584b8d1c1947f2ba60fb3", size = 144099, upload-time = "2026-02-02T15:38:20.28Z" }, - { url = "https://files.pythonhosted.org/packages/a5/66/857a8e4a3292e1f7b1b202883bcdeb43a91566cf59a93f97c53b44bd6801/orjson-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1eb80451a9c351a71dfaf5b7ccc13ad065405217726b59fdbeadbcc544f9d223", size = 134806, upload-time = "2026-02-02T15:38:22.186Z" }, - { url = "https://files.pythonhosted.org/packages/0a/5b/6ebcf3defc1aab3a338ca777214966851e92efb1f30dc7fc8285216e6d1b/orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3", size = 127914, upload-time = "2026-02-02T15:38:23.511Z" }, - { url = "https://files.pythonhosted.org/packages/00/04/c6f72daca5092e3117840a1b1e88dfc809cc1470cf0734890d0366b684a1/orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757", size = 124986, upload-time = "2026-02-02T15:38:24.836Z" }, - { url = "https://files.pythonhosted.org/packages/03/ba/077a0f6f1085d6b806937246860fafbd5b17f3919c70ee3f3d8d9c713f38/orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539", size = 126045, upload-time = "2026-02-02T15:38:26.216Z" }, - { url = "https://files.pythonhosted.org/packages/e9/1e/745565dca749813db9a093c5ebc4bac1a9475c64d54b95654336ac3ed961/orjson-3.11.7-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:de0a37f21d0d364954ad5de1970491d7fbd0fb1ef7417d4d56a36dc01ba0c0a0", size = 228391, upload-time = "2026-02-02T15:38:27.757Z" }, - { url = "https://files.pythonhosted.org/packages/46/19/e40f6225da4d3aa0c8dc6e5219c5e87c2063a560fe0d72a88deb59776794/orjson-3.11.7-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c2428d358d85e8da9d37cba18b8c4047c55222007a84f97156a5b22028dfbfc0", size = 125188, upload-time = "2026-02-02T15:38:29.241Z" }, - { url = "https://files.pythonhosted.org/packages/9d/7e/c4de2babef2c0817fd1f048fd176aa48c37bec8aef53d2fa932983032cce/orjson-3.11.7-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4bc6c6ac52cdaa267552544c73e486fecbd710b7ac09bc024d5a78555a22f6", size = 128097, upload-time = "2026-02-02T15:38:30.618Z" }, - { url = "https://files.pythonhosted.org/packages/eb/74/233d360632bafd2197f217eee7fb9c9d0229eac0c18128aee5b35b0014fe/orjson-3.11.7-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd0d68edd7dfca1b2eca9361a44ac9f24b078de3481003159929a0573f21a6bf", size = 123364, upload-time = "2026-02-02T15:38:32.363Z" }, - { url = "https://files.pythonhosted.org/packages/79/51/af79504981dd31efe20a9e360eb49c15f06df2b40e7f25a0a52d9ae888e8/orjson-3.11.7-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:623ad1b9548ef63886319c16fa317848e465a21513b31a6ad7b57443c3e0dcf5", size = 129076, upload-time = "2026-02-02T15:38:33.68Z" }, - { url = "https://files.pythonhosted.org/packages/67/e2/da898eb68b72304f8de05ca6715870d09d603ee98d30a27e8a9629abc64b/orjson-3.11.7-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e776b998ac37c0396093d10290e60283f59cfe0fc3fccbd0ccc4bd04dd19892", size = 141705, upload-time = "2026-02-02T15:38:34.989Z" }, - { url = "https://files.pythonhosted.org/packages/c5/89/15364d92acb3d903b029e28d834edb8780c2b97404cbf7929aa6b9abdb24/orjson-3.11.7-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c6c3af76716f4a9c290371ba2e390ede06f6603edb277b481daf37f6f464e", size = 130855, upload-time = "2026-02-02T15:38:36.379Z" }, - { url = "https://files.pythonhosted.org/packages/c2/8b/ecdad52d0b38d4b8f514be603e69ccd5eacf4e7241f972e37e79792212ec/orjson-3.11.7-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56df3239294ea5964adf074c54bcc4f0ccd21636049a2cf3ca9cf03b5d03cf1", size = 133386, upload-time = "2026-02-02T15:38:37.704Z" }, - { url = "https://files.pythonhosted.org/packages/b9/0e/45e1dcf10e17d0924b7c9162f87ec7b4ca79e28a0548acf6a71788d3e108/orjson-3.11.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bda117c4148e81f746655d5a3239ae9bd00cb7bc3ca178b5fc5a5997e9744183", size = 138295, upload-time = "2026-02-02T15:38:39.096Z" }, - { url = "https://files.pythonhosted.org/packages/63/d7/4d2e8b03561257af0450f2845b91fbd111d7e526ccdf737267108075e0ba/orjson-3.11.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:23d6c20517a97a9daf1d48b580fcdc6f0516c6f4b5038823426033690b4d2650", size = 408720, upload-time = "2026-02-02T15:38:40.634Z" }, - { url = "https://files.pythonhosted.org/packages/78/cf/d45343518282108b29c12a65892445fc51f9319dc3c552ceb51bb5905ed2/orjson-3.11.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8ff206156006da5b847c9304b6308a01e8cdbc8cce824e2779a5ba71c3def141", size = 144152, upload-time = "2026-02-02T15:38:42.262Z" }, - { url = "https://files.pythonhosted.org/packages/a9/3a/d6001f51a7275aacd342e77b735c71fa04125a3f93c36fee4526bc8c654e/orjson-3.11.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:962d046ee1765f74a1da723f4b33e3b228fe3a48bd307acce5021dfefe0e29b2", size = 134814, upload-time = "2026-02-02T15:38:43.627Z" }, - { url = "https://files.pythonhosted.org/packages/1d/d3/f19b47ce16820cc2c480f7f1723e17f6d411b3a295c60c8ad3aa9ff1c96a/orjson-3.11.7-cp314-cp314-win32.whl", hash = "sha256:89e13dd3f89f1c38a9c9eba5fbf7cdc2d1feca82f5f290864b4b7a6aac704576", size = 127997, upload-time = "2026-02-02T15:38:45.06Z" }, - { url = "https://files.pythonhosted.org/packages/12/df/172771902943af54bf661a8d102bdf2e7f932127968080632bda6054b62c/orjson-3.11.7-cp314-cp314-win_amd64.whl", hash = "sha256:845c3e0d8ded9c9271cd79596b9b552448b885b97110f628fb687aee2eed11c1", size = 124985, upload-time = "2026-02-02T15:38:46.388Z" }, - { url = "https://files.pythonhosted.org/packages/6f/1c/f2a8d8a1b17514660a614ce5f7aac74b934e69f5abc2700cc7ced882a009/orjson-3.11.7-cp314-cp314-win_arm64.whl", hash = "sha256:4a2e9c5be347b937a2e0203866f12bba36082e89b402ddb9e927d5822e43088d", size = 126038, upload-time = "2026-02-02T15:38:47.703Z" }, + { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545 }, + { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224 }, + { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154 }, + { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548 }, + { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000 }, + { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686 }, + { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812 }, + { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440 }, + { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386 }, + { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853 }, + { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130 }, + { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818 }, + { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923 }, + { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007 }, + { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089 }, + { url = "https://files.pythonhosted.org/packages/89/25/6e0e52cac5aab51d7b6dcd257e855e1dec1c2060f6b28566c509b4665f62/orjson-3.11.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1d98b30cc1313d52d4af17d9c3d307b08389752ec5f2e5febdfada70b0f8c733", size = 228390 }, + { url = "https://files.pythonhosted.org/packages/a5/29/a77f48d2fc8a05bbc529e5ff481fb43d914f9e383ea2469d4f3d51df3d00/orjson-3.11.7-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d897e81f8d0cbd2abb82226d1860ad2e1ab3ff16d7b08c96ca00df9d45409ef4", size = 125189 }, + { url = "https://files.pythonhosted.org/packages/89/25/0a16e0729a0e6a1504f9d1a13cdd365f030068aab64cec6958396b9969d7/orjson-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814be4b49b228cfc0b3c565acf642dd7d13538f966e3ccde61f4f55be3e20785", size = 128106 }, + { url = "https://files.pythonhosted.org/packages/66/da/a2e505469d60666a05ab373f1a6322eb671cb2ba3a0ccfc7d4bc97196787/orjson-3.11.7-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d06e5c5fed5caedd2e540d62e5b1c25e8c82431b9e577c33537e5fa4aa909539", size = 123363 }, + { url = "https://files.pythonhosted.org/packages/23/bf/ed73f88396ea35c71b38961734ea4a4746f7ca0768bf28fd551d37e48dd0/orjson-3.11.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31c80ce534ac4ea3739c5ee751270646cbc46e45aea7576a38ffec040b4029a1", size = 129007 }, + { url = "https://files.pythonhosted.org/packages/73/3c/b05d80716f0225fc9008fbf8ab22841dcc268a626aa550561743714ce3bf/orjson-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1", size = 141667 }, + { url = "https://files.pythonhosted.org/packages/61/e8/0be9b0addd9bf86abfc938e97441dcd0375d494594b1c8ad10fe57479617/orjson-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e54f3808e2b6b945078c41aa8d9b5834b28c50843846e97807e5adb75fa9705", size = 130832 }, + { url = "https://files.pythonhosted.org/packages/c9/ec/c68e3b9021a31d9ec15a94931db1410136af862955854ed5dd7e7e4f5bff/orjson-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12b80df61aab7b98b490fe9e4879925ba666fccdfcd175252ce4d9035865ace", size = 133373 }, + { url = "https://files.pythonhosted.org/packages/d2/45/f3466739aaafa570cc8e77c6dbb853c48bf56e3b43738020e2661e08b0ac/orjson-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:996b65230271f1a97026fd0e6a753f51fbc0c335d2ad0c6201f711b0da32693b", size = 138307 }, + { url = "https://files.pythonhosted.org/packages/e1/84/9f7f02288da1ffb31405c1be07657afd1eecbcb4b64ee2817b6fe0f785fa/orjson-3.11.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ab49d4b2a6a1d415ddb9f37a21e02e0d5dbfe10b7870b21bf779fc21e9156157", size = 408695 }, + { url = "https://files.pythonhosted.org/packages/18/07/9dd2f0c0104f1a0295ffbe912bc8d63307a539b900dd9e2c48ef7810d971/orjson-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:390a1dce0c055ddf8adb6aa94a73b45a4a7d7177b5c584b8d1c1947f2ba60fb3", size = 144099 }, + { url = "https://files.pythonhosted.org/packages/a5/66/857a8e4a3292e1f7b1b202883bcdeb43a91566cf59a93f97c53b44bd6801/orjson-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1eb80451a9c351a71dfaf5b7ccc13ad065405217726b59fdbeadbcc544f9d223", size = 134806 }, + { url = "https://files.pythonhosted.org/packages/0a/5b/6ebcf3defc1aab3a338ca777214966851e92efb1f30dc7fc8285216e6d1b/orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3", size = 127914 }, + { url = "https://files.pythonhosted.org/packages/00/04/c6f72daca5092e3117840a1b1e88dfc809cc1470cf0734890d0366b684a1/orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757", size = 124986 }, + { url = "https://files.pythonhosted.org/packages/03/ba/077a0f6f1085d6b806937246860fafbd5b17f3919c70ee3f3d8d9c713f38/orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539", size = 126045 }, + { url = "https://files.pythonhosted.org/packages/e9/1e/745565dca749813db9a093c5ebc4bac1a9475c64d54b95654336ac3ed961/orjson-3.11.7-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:de0a37f21d0d364954ad5de1970491d7fbd0fb1ef7417d4d56a36dc01ba0c0a0", size = 228391 }, + { url = "https://files.pythonhosted.org/packages/46/19/e40f6225da4d3aa0c8dc6e5219c5e87c2063a560fe0d72a88deb59776794/orjson-3.11.7-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c2428d358d85e8da9d37cba18b8c4047c55222007a84f97156a5b22028dfbfc0", size = 125188 }, + { url = "https://files.pythonhosted.org/packages/9d/7e/c4de2babef2c0817fd1f048fd176aa48c37bec8aef53d2fa932983032cce/orjson-3.11.7-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4bc6c6ac52cdaa267552544c73e486fecbd710b7ac09bc024d5a78555a22f6", size = 128097 }, + { url = "https://files.pythonhosted.org/packages/eb/74/233d360632bafd2197f217eee7fb9c9d0229eac0c18128aee5b35b0014fe/orjson-3.11.7-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd0d68edd7dfca1b2eca9361a44ac9f24b078de3481003159929a0573f21a6bf", size = 123364 }, + { url = "https://files.pythonhosted.org/packages/79/51/af79504981dd31efe20a9e360eb49c15f06df2b40e7f25a0a52d9ae888e8/orjson-3.11.7-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:623ad1b9548ef63886319c16fa317848e465a21513b31a6ad7b57443c3e0dcf5", size = 129076 }, + { url = "https://files.pythonhosted.org/packages/67/e2/da898eb68b72304f8de05ca6715870d09d603ee98d30a27e8a9629abc64b/orjson-3.11.7-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e776b998ac37c0396093d10290e60283f59cfe0fc3fccbd0ccc4bd04dd19892", size = 141705 }, + { url = "https://files.pythonhosted.org/packages/c5/89/15364d92acb3d903b029e28d834edb8780c2b97404cbf7929aa6b9abdb24/orjson-3.11.7-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c6c3af76716f4a9c290371ba2e390ede06f6603edb277b481daf37f6f464e", size = 130855 }, + { url = "https://files.pythonhosted.org/packages/c2/8b/ecdad52d0b38d4b8f514be603e69ccd5eacf4e7241f972e37e79792212ec/orjson-3.11.7-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56df3239294ea5964adf074c54bcc4f0ccd21636049a2cf3ca9cf03b5d03cf1", size = 133386 }, + { url = "https://files.pythonhosted.org/packages/b9/0e/45e1dcf10e17d0924b7c9162f87ec7b4ca79e28a0548acf6a71788d3e108/orjson-3.11.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bda117c4148e81f746655d5a3239ae9bd00cb7bc3ca178b5fc5a5997e9744183", size = 138295 }, + { url = "https://files.pythonhosted.org/packages/63/d7/4d2e8b03561257af0450f2845b91fbd111d7e526ccdf737267108075e0ba/orjson-3.11.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:23d6c20517a97a9daf1d48b580fcdc6f0516c6f4b5038823426033690b4d2650", size = 408720 }, + { url = "https://files.pythonhosted.org/packages/78/cf/d45343518282108b29c12a65892445fc51f9319dc3c552ceb51bb5905ed2/orjson-3.11.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8ff206156006da5b847c9304b6308a01e8cdbc8cce824e2779a5ba71c3def141", size = 144152 }, + { url = "https://files.pythonhosted.org/packages/a9/3a/d6001f51a7275aacd342e77b735c71fa04125a3f93c36fee4526bc8c654e/orjson-3.11.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:962d046ee1765f74a1da723f4b33e3b228fe3a48bd307acce5021dfefe0e29b2", size = 134814 }, + { url = "https://files.pythonhosted.org/packages/1d/d3/f19b47ce16820cc2c480f7f1723e17f6d411b3a295c60c8ad3aa9ff1c96a/orjson-3.11.7-cp314-cp314-win32.whl", hash = "sha256:89e13dd3f89f1c38a9c9eba5fbf7cdc2d1feca82f5f290864b4b7a6aac704576", size = 127997 }, + { url = "https://files.pythonhosted.org/packages/12/df/172771902943af54bf661a8d102bdf2e7f932127968080632bda6054b62c/orjson-3.11.7-cp314-cp314-win_amd64.whl", hash = "sha256:845c3e0d8ded9c9271cd79596b9b552448b885b97110f628fb687aee2eed11c1", size = 124985 }, + { url = "https://files.pythonhosted.org/packages/6f/1c/f2a8d8a1b17514660a614ce5f7aac74b934e69f5abc2700cc7ced882a009/orjson-3.11.7-cp314-cp314-win_arm64.whl", hash = "sha256:4a2e9c5be347b937a2e0203866f12bba36082e89b402ddb9e927d5822e43088d", size = 126038 }, ] [[package]] name = "ormsgpack" version = "1.12.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/0c/f1761e21486942ab9bb6feaebc610fa074f7c5e496e6962dea5873348077/ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33", size = 39031, upload-time = "2026-01-18T20:55:28.023Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/0c/f1761e21486942ab9bb6feaebc610fa074f7c5e496e6962dea5873348077/ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33", size = 39031 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/36/16c4b1921c308a92cef3bf6663226ae283395aa0ff6e154f925c32e91ff5/ormsgpack-1.12.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7", size = 378618, upload-time = "2026-01-18T20:55:50.835Z" }, - { url = "https://files.pythonhosted.org/packages/c0/68/468de634079615abf66ed13bb5c34ff71da237213f29294363beeeca5306/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d", size = 203186, upload-time = "2026-01-18T20:56:11.163Z" }, - { url = "https://files.pythonhosted.org/packages/73/a9/d756e01961442688b7939bacd87ce13bfad7d26ce24f910f6028178b2cc8/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e", size = 210738, upload-time = "2026-01-18T20:56:09.181Z" }, - { url = "https://files.pythonhosted.org/packages/7b/ba/795b1036888542c9113269a3f5690ab53dd2258c6fb17676ac4bd44fcf94/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc", size = 212569, upload-time = "2026-01-18T20:56:06.135Z" }, - { url = "https://files.pythonhosted.org/packages/6c/aa/bff73c57497b9e0cba8837c7e4bcab584b1a6dbc91a5dd5526784a5030c8/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e", size = 387166, upload-time = "2026-01-18T20:55:36.738Z" }, - { url = "https://files.pythonhosted.org/packages/d3/cf/f8283cba44bcb7b14f97b6274d449db276b3a86589bdb363169b51bc12de/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6", size = 482498, upload-time = "2026-01-18T20:55:29.626Z" }, - { url = "https://files.pythonhosted.org/packages/05/be/71e37b852d723dfcbe952ad04178c030df60d6b78eba26bfd14c9a40575e/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd", size = 425518, upload-time = "2026-01-18T20:55:49.556Z" }, - { url = "https://files.pythonhosted.org/packages/7a/0c/9803aa883d18c7ef197213cd2cbf73ba76472a11fe100fb7dab2884edf48/ormsgpack-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4", size = 117462, upload-time = "2026-01-18T20:55:47.726Z" }, - { url = "https://files.pythonhosted.org/packages/c8/9e/029e898298b2cc662f10d7a15652a53e3b525b1e7f07e21fef8536a09bb8/ormsgpack-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6", size = 111559, upload-time = "2026-01-18T20:55:54.273Z" }, - { url = "https://files.pythonhosted.org/packages/eb/29/bb0eba3288c0449efbb013e9c6f58aea79cf5cb9ee1921f8865f04c1a9d7/ormsgpack-1.12.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355", size = 378661, upload-time = "2026-01-18T20:55:57.765Z" }, - { url = "https://files.pythonhosted.org/packages/6e/31/5efa31346affdac489acade2926989e019e8ca98129658a183e3add7af5e/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1", size = 203194, upload-time = "2026-01-18T20:56:08.252Z" }, - { url = "https://files.pythonhosted.org/packages/eb/56/d0087278beef833187e0167f8527235ebe6f6ffc2a143e9de12a98b1ce87/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172", size = 210778, upload-time = "2026-01-18T20:55:17.694Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a2/072343e1413d9443e5a252a8eb591c2d5b1bffbe5e7bfc78c069361b92eb/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d", size = 212592, upload-time = "2026-01-18T20:55:32.747Z" }, - { url = "https://files.pythonhosted.org/packages/a2/8b/a0da3b98a91d41187a63b02dda14267eefc2a74fcb43cc2701066cf1510e/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7", size = 387164, upload-time = "2026-01-18T20:55:40.853Z" }, - { url = "https://files.pythonhosted.org/packages/19/bb/6d226bc4cf9fc20d8eb1d976d027a3f7c3491e8f08289a2e76abe96a65f3/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685", size = 482516, upload-time = "2026-01-18T20:55:42.033Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f1/bb2c7223398543dedb3dbf8bb93aaa737b387de61c5feaad6f908841b782/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258", size = 425539, upload-time = "2026-01-18T20:55:24.727Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e8/0fb45f57a2ada1fed374f7494c8cd55e2f88ccd0ab0a669aa3468716bf5f/ormsgpack-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9", size = 117459, upload-time = "2026-01-18T20:55:56.876Z" }, - { url = "https://files.pythonhosted.org/packages/7a/d4/0cfeea1e960d550a131001a7f38a5132c7ae3ebde4c82af1f364ccc5d904/ormsgpack-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709", size = 111577, upload-time = "2026-01-18T20:55:43.605Z" }, - { url = "https://files.pythonhosted.org/packages/94/16/24d18851334be09c25e87f74307c84950f18c324a4d3c0b41dabdbf19c29/ormsgpack-1.12.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bc68dd5915f4acf66ff2010ee47c8906dc1cf07399b16f4089f8c71733f6e36c", size = 378717, upload-time = "2026-01-18T20:55:26.164Z" }, - { url = "https://files.pythonhosted.org/packages/b5/a2/88b9b56f83adae8032ac6a6fa7f080c65b3baf9b6b64fd3d37bd202991d4/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46d084427b4132553940070ad95107266656cb646ea9da4975f85cb1a6676553", size = 203183, upload-time = "2026-01-18T20:55:18.815Z" }, - { url = "https://files.pythonhosted.org/packages/a9/80/43e4555963bf602e5bdc79cbc8debd8b6d5456c00d2504df9775e74b450b/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c010da16235806cf1d7bc4c96bf286bfa91c686853395a299b3ddb49499a3e13", size = 210814, upload-time = "2026-01-18T20:55:33.973Z" }, - { url = "https://files.pythonhosted.org/packages/78/e1/7cfbf28de8bca6efe7e525b329c31277d1b64ce08dcba723971c241a9d60/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18867233df592c997154ff942a6503df274b5ac1765215bceba7a231bea2745d", size = 212634, upload-time = "2026-01-18T20:55:28.634Z" }, - { url = "https://files.pythonhosted.org/packages/95/f8/30ae5716e88d792a4e879debee195653c26ddd3964c968594ddef0a3cc7e/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b009049086ddc6b8f80c76b3955df1aa22a5fbd7673c525cd63bf91f23122ede", size = 387139, upload-time = "2026-01-18T20:56:02.013Z" }, - { url = "https://files.pythonhosted.org/packages/dc/81/aee5b18a3e3a0e52f718b37ab4b8af6fae0d9d6a65103036a90c2a8ffb5d/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1dcc17d92b6390d4f18f937cf0b99054824a7815818012ddca925d6e01c2e49e", size = 482578, upload-time = "2026-01-18T20:55:35.117Z" }, - { url = "https://files.pythonhosted.org/packages/bd/17/71c9ba472d5d45f7546317f467a5fc941929cd68fb32796ca3d13dcbaec2/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f04b5e896d510b07c0ad733d7fce2d44b260c5e6c402d272128f8941984e4285", size = 425539, upload-time = "2026-01-18T20:56:04.009Z" }, - { url = "https://files.pythonhosted.org/packages/2e/a6/ac99cd7fe77e822fed5250ff4b86fa66dd4238937dd178d2299f10b69816/ormsgpack-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:ae3aba7eed4ca7cb79fd3436eddd29140f17ea254b91604aa1eb19bfcedb990f", size = 117493, upload-time = "2026-01-18T20:56:07.343Z" }, - { url = "https://files.pythonhosted.org/packages/3a/67/339872846a1ae4592535385a1c1f93614138566d7af094200c9c3b45d1e5/ormsgpack-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:118576ea6006893aea811b17429bfc561b4778fad393f5f538c84af70b01260c", size = 111579, upload-time = "2026-01-18T20:55:21.161Z" }, - { url = "https://files.pythonhosted.org/packages/49/c2/6feb972dc87285ad381749d3882d8aecbde9f6ecf908dd717d33d66df095/ormsgpack-1.12.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7121b3d355d3858781dc40dafe25a32ff8a8242b9d80c692fd548a4b1f7fd3c8", size = 378721, upload-time = "2026-01-18T20:55:52.12Z" }, - { url = "https://files.pythonhosted.org/packages/a3/9a/900a6b9b413e0f8a471cf07830f9cf65939af039a362204b36bd5b581d8b/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee766d2e78251b7a63daf1cddfac36a73562d3ddef68cacfb41b2af64698033", size = 203170, upload-time = "2026-01-18T20:55:44.469Z" }, - { url = "https://files.pythonhosted.org/packages/87/4c/27a95466354606b256f24fad464d7c97ab62bce6cc529dd4673e1179b8fb/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292410a7d23de9b40444636b9b8f1e4e4b814af7f1ef476e44887e52a123f09d", size = 212816, upload-time = "2026-01-18T20:55:23.501Z" }, - { url = "https://files.pythonhosted.org/packages/73/cd/29cee6007bddf7a834e6cd6f536754c0535fcb939d384f0f37a38b1cddb8/ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2", size = 117232, upload-time = "2026-01-18T20:55:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/4c/36/16c4b1921c308a92cef3bf6663226ae283395aa0ff6e154f925c32e91ff5/ormsgpack-1.12.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7", size = 378618 }, + { url = "https://files.pythonhosted.org/packages/c0/68/468de634079615abf66ed13bb5c34ff71da237213f29294363beeeca5306/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d", size = 203186 }, + { url = "https://files.pythonhosted.org/packages/73/a9/d756e01961442688b7939bacd87ce13bfad7d26ce24f910f6028178b2cc8/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e", size = 210738 }, + { url = "https://files.pythonhosted.org/packages/7b/ba/795b1036888542c9113269a3f5690ab53dd2258c6fb17676ac4bd44fcf94/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc", size = 212569 }, + { url = "https://files.pythonhosted.org/packages/6c/aa/bff73c57497b9e0cba8837c7e4bcab584b1a6dbc91a5dd5526784a5030c8/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e", size = 387166 }, + { url = "https://files.pythonhosted.org/packages/d3/cf/f8283cba44bcb7b14f97b6274d449db276b3a86589bdb363169b51bc12de/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6", size = 482498 }, + { url = "https://files.pythonhosted.org/packages/05/be/71e37b852d723dfcbe952ad04178c030df60d6b78eba26bfd14c9a40575e/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd", size = 425518 }, + { url = "https://files.pythonhosted.org/packages/7a/0c/9803aa883d18c7ef197213cd2cbf73ba76472a11fe100fb7dab2884edf48/ormsgpack-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4", size = 117462 }, + { url = "https://files.pythonhosted.org/packages/c8/9e/029e898298b2cc662f10d7a15652a53e3b525b1e7f07e21fef8536a09bb8/ormsgpack-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6", size = 111559 }, + { url = "https://files.pythonhosted.org/packages/eb/29/bb0eba3288c0449efbb013e9c6f58aea79cf5cb9ee1921f8865f04c1a9d7/ormsgpack-1.12.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355", size = 378661 }, + { url = "https://files.pythonhosted.org/packages/6e/31/5efa31346affdac489acade2926989e019e8ca98129658a183e3add7af5e/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1", size = 203194 }, + { url = "https://files.pythonhosted.org/packages/eb/56/d0087278beef833187e0167f8527235ebe6f6ffc2a143e9de12a98b1ce87/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172", size = 210778 }, + { url = "https://files.pythonhosted.org/packages/1c/a2/072343e1413d9443e5a252a8eb591c2d5b1bffbe5e7bfc78c069361b92eb/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d", size = 212592 }, + { url = "https://files.pythonhosted.org/packages/a2/8b/a0da3b98a91d41187a63b02dda14267eefc2a74fcb43cc2701066cf1510e/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7", size = 387164 }, + { url = "https://files.pythonhosted.org/packages/19/bb/6d226bc4cf9fc20d8eb1d976d027a3f7c3491e8f08289a2e76abe96a65f3/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685", size = 482516 }, + { url = "https://files.pythonhosted.org/packages/fb/f1/bb2c7223398543dedb3dbf8bb93aaa737b387de61c5feaad6f908841b782/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258", size = 425539 }, + { url = "https://files.pythonhosted.org/packages/7b/e8/0fb45f57a2ada1fed374f7494c8cd55e2f88ccd0ab0a669aa3468716bf5f/ormsgpack-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9", size = 117459 }, + { url = "https://files.pythonhosted.org/packages/7a/d4/0cfeea1e960d550a131001a7f38a5132c7ae3ebde4c82af1f364ccc5d904/ormsgpack-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709", size = 111577 }, + { url = "https://files.pythonhosted.org/packages/94/16/24d18851334be09c25e87f74307c84950f18c324a4d3c0b41dabdbf19c29/ormsgpack-1.12.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bc68dd5915f4acf66ff2010ee47c8906dc1cf07399b16f4089f8c71733f6e36c", size = 378717 }, + { url = "https://files.pythonhosted.org/packages/b5/a2/88b9b56f83adae8032ac6a6fa7f080c65b3baf9b6b64fd3d37bd202991d4/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46d084427b4132553940070ad95107266656cb646ea9da4975f85cb1a6676553", size = 203183 }, + { url = "https://files.pythonhosted.org/packages/a9/80/43e4555963bf602e5bdc79cbc8debd8b6d5456c00d2504df9775e74b450b/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c010da16235806cf1d7bc4c96bf286bfa91c686853395a299b3ddb49499a3e13", size = 210814 }, + { url = "https://files.pythonhosted.org/packages/78/e1/7cfbf28de8bca6efe7e525b329c31277d1b64ce08dcba723971c241a9d60/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18867233df592c997154ff942a6503df274b5ac1765215bceba7a231bea2745d", size = 212634 }, + { url = "https://files.pythonhosted.org/packages/95/f8/30ae5716e88d792a4e879debee195653c26ddd3964c968594ddef0a3cc7e/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b009049086ddc6b8f80c76b3955df1aa22a5fbd7673c525cd63bf91f23122ede", size = 387139 }, + { url = "https://files.pythonhosted.org/packages/dc/81/aee5b18a3e3a0e52f718b37ab4b8af6fae0d9d6a65103036a90c2a8ffb5d/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1dcc17d92b6390d4f18f937cf0b99054824a7815818012ddca925d6e01c2e49e", size = 482578 }, + { url = "https://files.pythonhosted.org/packages/bd/17/71c9ba472d5d45f7546317f467a5fc941929cd68fb32796ca3d13dcbaec2/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f04b5e896d510b07c0ad733d7fce2d44b260c5e6c402d272128f8941984e4285", size = 425539 }, + { url = "https://files.pythonhosted.org/packages/2e/a6/ac99cd7fe77e822fed5250ff4b86fa66dd4238937dd178d2299f10b69816/ormsgpack-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:ae3aba7eed4ca7cb79fd3436eddd29140f17ea254b91604aa1eb19bfcedb990f", size = 117493 }, + { url = "https://files.pythonhosted.org/packages/3a/67/339872846a1ae4592535385a1c1f93614138566d7af094200c9c3b45d1e5/ormsgpack-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:118576ea6006893aea811b17429bfc561b4778fad393f5f538c84af70b01260c", size = 111579 }, + { url = "https://files.pythonhosted.org/packages/49/c2/6feb972dc87285ad381749d3882d8aecbde9f6ecf908dd717d33d66df095/ormsgpack-1.12.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7121b3d355d3858781dc40dafe25a32ff8a8242b9d80c692fd548a4b1f7fd3c8", size = 378721 }, + { url = "https://files.pythonhosted.org/packages/a3/9a/900a6b9b413e0f8a471cf07830f9cf65939af039a362204b36bd5b581d8b/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee766d2e78251b7a63daf1cddfac36a73562d3ddef68cacfb41b2af64698033", size = 203170 }, + { url = "https://files.pythonhosted.org/packages/87/4c/27a95466354606b256f24fad464d7c97ab62bce6cc529dd4673e1179b8fb/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292410a7d23de9b40444636b9b8f1e4e4b814af7f1ef476e44887e52a123f09d", size = 212816 }, + { url = "https://files.pythonhosted.org/packages/73/cd/29cee6007bddf7a834e6cd6f536754c0535fcb939d384f0f37a38b1cddb8/ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2", size = 117232 }, ] [[package]] name = "overrides" version = "7.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, ] [[package]] name = "packaging" version = "24.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, ] [[package]] @@ -5607,56 +5599,56 @@ dependencies = [ { name = "python-dateutil" }, { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2e/0c/b28ed414f080ee0ad153f848586d61d1878f91689950f037f976ce15f6c8/pandas-3.0.1.tar.gz", hash = "sha256:4186a699674af418f655dbd420ed87f50d56b4cd6603784279d9eef6627823c8", size = 4641901, upload-time = "2026-02-17T22:20:16.434Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/0c/b28ed414f080ee0ad153f848586d61d1878f91689950f037f976ce15f6c8/pandas-3.0.1.tar.gz", hash = "sha256:4186a699674af418f655dbd420ed87f50d56b4cd6603784279d9eef6627823c8", size = 4641901 } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:476f84f8c20c9f5bc47252b66b4bb25e1a9fc2fa98cead96744d8116cb85771d", size = 10344357, upload-time = "2026-02-17T22:18:38.262Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ab749dfba921edf641d4036c4c21c0b3ea70fea478165cb98a998fb2a261955", size = 9884543, upload-time = "2026-02-17T22:18:41.476Z" }, - { url = "https://files.pythonhosted.org/packages/d7/39/327802e0b6d693182403c144edacbc27eb82907b57062f23ef5a4c4a5ea7/pandas-3.0.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e36891080b87823aff3640c78649b91b8ff6eea3c0d70aeabd72ea43ab069b", size = 10396030, upload-time = "2026-02-17T22:18:43.822Z" }, - { url = "https://files.pythonhosted.org/packages/3d/fe/89d77e424365280b79d99b3e1e7d606f5165af2f2ecfaf0c6d24c799d607/pandas-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:532527a701281b9dd371e2f582ed9094f4c12dd9ffb82c0c54ee28d8ac9520c4", size = 10876435, upload-time = "2026-02-17T22:18:45.954Z" }, - { url = "https://files.pythonhosted.org/packages/b5/a6/2a75320849dd154a793f69c951db759aedb8d1dd3939eeacda9bdcfa1629/pandas-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:356e5c055ed9b0da1580d465657bc7d00635af4fd47f30afb23025352ba764d1", size = 11405133, upload-time = "2026-02-17T22:18:48.533Z" }, - { url = "https://files.pythonhosted.org/packages/58/53/1d68fafb2e02d7881df66aa53be4cd748d25cbe311f3b3c85c93ea5d30ca/pandas-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d810036895f9ad6345b8f2a338dd6998a74e8483847403582cab67745bff821", size = 11932065, upload-time = "2026-02-17T22:18:50.837Z" }, - { url = "https://files.pythonhosted.org/packages/75/08/67cc404b3a966b6df27b38370ddd96b3b023030b572283d035181854aac5/pandas-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:536232a5fe26dd989bd633e7a0c450705fdc86a207fec7254a55e9a22950fe43", size = 9741627, upload-time = "2026-02-17T22:18:53.905Z" }, - { url = "https://files.pythonhosted.org/packages/86/4f/caf9952948fb00d23795f09b893d11f1cacb384e666854d87249530f7cbe/pandas-3.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f463ebfd8de7f326d38037c7363c6dacb857c5881ab8961fb387804d6daf2f7", size = 9052483, upload-time = "2026-02-17T22:18:57.31Z" }, - { url = "https://files.pythonhosted.org/packages/0b/48/aad6ec4f8d007534c091e9a7172b3ec1b1ee6d99a9cbb936b5eab6c6cf58/pandas-3.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5272627187b5d9c20e55d27caf5f2cd23e286aba25cadf73c8590e432e2b7262", size = 10317509, upload-time = "2026-02-17T22:18:59.498Z" }, - { url = "https://files.pythonhosted.org/packages/a8/14/5990826f779f79148ae9d3a2c39593dc04d61d5d90541e71b5749f35af95/pandas-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:661e0f665932af88c7877f31da0dc743fe9c8f2524bdffe23d24fdcb67ef9d56", size = 9860561, upload-time = "2026-02-17T22:19:02.265Z" }, - { url = "https://files.pythonhosted.org/packages/fa/80/f01ff54664b6d70fed71475543d108a9b7c888e923ad210795bef04ffb7d/pandas-3.0.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75e6e292ff898679e47a2199172593d9f6107fd2dd3617c22c2946e97d5df46e", size = 10365506, upload-time = "2026-02-17T22:19:05.017Z" }, - { url = "https://files.pythonhosted.org/packages/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ff8cf1d2896e34343197685f432450ec99a85ba8d90cce2030c5eee2ef98791", size = 10873196, upload-time = "2026-02-17T22:19:07.204Z" }, - { url = "https://files.pythonhosted.org/packages/48/a9/9301c83d0b47c23ac5deab91c6b39fd98d5b5db4d93b25df8d381451828f/pandas-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eca8b4510f6763f3d37359c2105df03a7a221a508f30e396a51d0713d462e68a", size = 11370859, upload-time = "2026-02-17T22:19:09.436Z" }, - { url = "https://files.pythonhosted.org/packages/59/fe/0c1fc5bd2d29c7db2ab372330063ad555fb83e08422829c785f5ec2176ca/pandas-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06aff2ad6f0b94a17822cf8b83bbb563b090ed82ff4fe7712db2ce57cd50d9b8", size = 11924584, upload-time = "2026-02-17T22:19:11.562Z" }, - { url = "https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fea306c783e28884c29057a1d9baa11a349bbf99538ec1da44c8476563d1b25", size = 9742769, upload-time = "2026-02-17T22:19:13.926Z" }, - { url = "https://files.pythonhosted.org/packages/c4/cb/810a22a6af9a4e97c8ab1c946b47f3489c5bca5adc483ce0ffc84c9cc768/pandas-3.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a8d37a43c52917427e897cb2e429f67a449327394396a81034a4449b99afda59", size = 9043855, upload-time = "2026-02-17T22:19:16.09Z" }, - { url = "https://files.pythonhosted.org/packages/92/fa/423c89086cca1f039cf1253c3ff5b90f157b5b3757314aa635f6bf3e30aa/pandas-3.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d54855f04f8246ed7b6fc96b05d4871591143c46c0b6f4af874764ed0d2d6f06", size = 10752673, upload-time = "2026-02-17T22:19:18.304Z" }, - { url = "https://files.pythonhosted.org/packages/22/23/b5a08ec1f40020397f0faba72f1e2c11f7596a6169c7b3e800abff0e433f/pandas-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e1b677accee34a09e0dc2ce5624e4a58a1870ffe56fc021e9caf7f23cd7668f", size = 10404967, upload-time = "2026-02-17T22:19:20.726Z" }, - { url = "https://files.pythonhosted.org/packages/5c/81/94841f1bb4afdc2b52a99daa895ac2c61600bb72e26525ecc9543d453ebc/pandas-3.0.1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9cabbdcd03f1b6cd254d6dda8ae09b0252524be1592594c00b7895916cb1324", size = 10320575, upload-time = "2026-02-17T22:19:24.919Z" }, - { url = "https://files.pythonhosted.org/packages/0a/8b/2ae37d66a5342a83adadfd0cb0b4bf9c3c7925424dd5f40d15d6cfaa35ee/pandas-3.0.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ae2ab1f166668b41e770650101e7090824fd34d17915dd9cd479f5c5e0065e9", size = 10710921, upload-time = "2026-02-17T22:19:27.181Z" }, - { url = "https://files.pythonhosted.org/packages/a2/61/772b2e2757855e232b7ccf7cb8079a5711becb3a97f291c953def15a833f/pandas-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6bf0603c2e30e2cafac32807b06435f28741135cb8697eae8b28c7d492fc7d76", size = 11334191, upload-time = "2026-02-17T22:19:29.411Z" }, - { url = "https://files.pythonhosted.org/packages/1b/08/b16c6df3ef555d8495d1d265a7963b65be166785d28f06a350913a4fac78/pandas-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c426422973973cae1f4a23e51d4ae85974f44871b24844e4f7de752dd877098", size = 11782256, upload-time = "2026-02-17T22:19:32.34Z" }, - { url = "https://files.pythonhosted.org/packages/55/80/178af0594890dee17e239fca96d3d8670ba0f5ff59b7d0439850924a9c09/pandas-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b03f91ae8c10a85c1613102c7bef5229b5379f343030a3ccefeca8a33414cf35", size = 10485047, upload-time = "2026-02-17T22:19:34.605Z" }, - { url = "https://files.pythonhosted.org/packages/bb/8b/4bb774a998b97e6c2fd62a9e6cfdaae133b636fd1c468f92afb4ae9a447a/pandas-3.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:99d0f92ed92d3083d140bf6b97774f9f13863924cf3f52a70711f4e7588f9d0a", size = 10322465, upload-time = "2026-02-17T22:19:36.803Z" }, - { url = "https://files.pythonhosted.org/packages/72/3a/5b39b51c64159f470f1ca3b1c2a87da290657ca022f7cd11442606f607d1/pandas-3.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3b66857e983208654294bb6477b8a63dee26b37bdd0eb34d010556e91261784f", size = 9910632, upload-time = "2026-02-17T22:19:39.001Z" }, - { url = "https://files.pythonhosted.org/packages/4e/f7/b449ffb3f68c11da12fc06fbf6d2fa3a41c41e17d0284d23a79e1c13a7e4/pandas-3.0.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56cf59638bf24dc9bdf2154c81e248b3289f9a09a6d04e63608c159022352749", size = 10440535, upload-time = "2026-02-17T22:19:41.157Z" }, - { url = "https://files.pythonhosted.org/packages/55/77/6ea82043db22cb0f2bbfe7198da3544000ddaadb12d26be36e19b03a2dc5/pandas-3.0.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1a9f55e0f46951874b863d1f3906dcb57df2d9be5c5847ba4dfb55b2c815249", size = 10893940, upload-time = "2026-02-17T22:19:43.493Z" }, - { url = "https://files.pythonhosted.org/packages/03/30/f1b502a72468c89412c1b882a08f6eed8a4ee9dc033f35f65d0663df6081/pandas-3.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1849f0bba9c8a2fb0f691d492b834cc8dadf617e29015c66e989448d58d011ee", size = 11442711, upload-time = "2026-02-17T22:19:46.074Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f0/ebb6ddd8fc049e98cabac5c2924d14d1dda26a20adb70d41ea2e428d3ec4/pandas-3.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3d288439e11b5325b02ae6e9cc83e6805a62c40c5a6220bea9beb899c073b1c", size = 11963918, upload-time = "2026-02-17T22:19:48.838Z" }, - { url = "https://files.pythonhosted.org/packages/09/f8/8ce132104074f977f907442790eaae24e27bce3b3b454e82faa3237ff098/pandas-3.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:93325b0fe372d192965f4cca88d97667f49557398bbf94abdda3bf1b591dbe66", size = 9862099, upload-time = "2026-02-17T22:19:51.081Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b7/6af9aac41ef2456b768ef0ae60acf8abcebb450a52043d030a65b4b7c9bd/pandas-3.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:97ca08674e3287c7148f4858b01136f8bdfe7202ad25ad04fec602dd1d29d132", size = 9185333, upload-time = "2026-02-17T22:19:53.266Z" }, - { url = "https://files.pythonhosted.org/packages/66/fc/848bb6710bc6061cb0c5badd65b92ff75c81302e0e31e496d00029fe4953/pandas-3.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:58eeb1b2e0fb322befcf2bbc9ba0af41e616abadb3d3414a6bc7167f6cbfce32", size = 10772664, upload-time = "2026-02-17T22:19:55.806Z" }, - { url = "https://files.pythonhosted.org/packages/69/5c/866a9bbd0f79263b4b0db6ec1a341be13a1473323f05c122388e0f15b21d/pandas-3.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cd9af1276b5ca9e298bd79a26bda32fa9cc87ed095b2a9a60978d2ca058eaf87", size = 10421286, upload-time = "2026-02-17T22:19:58.091Z" }, - { url = "https://files.pythonhosted.org/packages/51/a4/2058fb84fb1cfbfb2d4a6d485e1940bb4ad5716e539d779852494479c580/pandas-3.0.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f87a04984d6b63788327cd9f79dda62b7f9043909d2440ceccf709249ca988", size = 10342050, upload-time = "2026-02-17T22:20:01.376Z" }, - { url = "https://files.pythonhosted.org/packages/22/1b/674e89996cc4be74db3c4eb09240c4bb549865c9c3f5d9b086ff8fcfbf00/pandas-3.0.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85fe4c4df62e1e20f9db6ebfb88c844b092c22cd5324bdcf94bfa2fc1b391221", size = 10740055, upload-time = "2026-02-17T22:20:04.328Z" }, - { url = "https://files.pythonhosted.org/packages/d0/f8/e954b750764298c22fa4614376531fe63c521ef517e7059a51f062b87dca/pandas-3.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:331ca75a2f8672c365ae25c0b29e46f5ac0c6551fdace8eec4cd65e4fac271ff", size = 11357632, upload-time = "2026-02-17T22:20:06.647Z" }, - { url = "https://files.pythonhosted.org/packages/6d/02/c6e04b694ffd68568297abd03588b6d30295265176a5c01b7459d3bc35a3/pandas-3.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15860b1fdb1973fffade772fdb931ccf9b2f400a3f5665aef94a00445d7d8dd5", size = 11810974, upload-time = "2026-02-17T22:20:08.946Z" }, - { url = "https://files.pythonhosted.org/packages/89/41/d7dfb63d2407f12055215070c42fc6ac41b66e90a2946cdc5e759058398b/pandas-3.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:44f1364411d5670efa692b146c748f4ed013df91ee91e9bec5677fb1fd58b937", size = 10884622, upload-time = "2026-02-17T22:20:11.711Z" }, - { url = "https://files.pythonhosted.org/packages/68/b0/34937815889fa982613775e4b97fddd13250f11012d769949c5465af2150/pandas-3.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:108dd1790337a494aa80e38def654ca3f0968cf4f362c85f44c15e471667102d", size = 9452085, upload-time = "2026-02-17T22:20:14.331Z" }, + { url = "https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:476f84f8c20c9f5bc47252b66b4bb25e1a9fc2fa98cead96744d8116cb85771d", size = 10344357 }, + { url = "https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ab749dfba921edf641d4036c4c21c0b3ea70fea478165cb98a998fb2a261955", size = 9884543 }, + { url = "https://files.pythonhosted.org/packages/d7/39/327802e0b6d693182403c144edacbc27eb82907b57062f23ef5a4c4a5ea7/pandas-3.0.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e36891080b87823aff3640c78649b91b8ff6eea3c0d70aeabd72ea43ab069b", size = 10396030 }, + { url = "https://files.pythonhosted.org/packages/3d/fe/89d77e424365280b79d99b3e1e7d606f5165af2f2ecfaf0c6d24c799d607/pandas-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:532527a701281b9dd371e2f582ed9094f4c12dd9ffb82c0c54ee28d8ac9520c4", size = 10876435 }, + { url = "https://files.pythonhosted.org/packages/b5/a6/2a75320849dd154a793f69c951db759aedb8d1dd3939eeacda9bdcfa1629/pandas-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:356e5c055ed9b0da1580d465657bc7d00635af4fd47f30afb23025352ba764d1", size = 11405133 }, + { url = "https://files.pythonhosted.org/packages/58/53/1d68fafb2e02d7881df66aa53be4cd748d25cbe311f3b3c85c93ea5d30ca/pandas-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d810036895f9ad6345b8f2a338dd6998a74e8483847403582cab67745bff821", size = 11932065 }, + { url = "https://files.pythonhosted.org/packages/75/08/67cc404b3a966b6df27b38370ddd96b3b023030b572283d035181854aac5/pandas-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:536232a5fe26dd989bd633e7a0c450705fdc86a207fec7254a55e9a22950fe43", size = 9741627 }, + { url = "https://files.pythonhosted.org/packages/86/4f/caf9952948fb00d23795f09b893d11f1cacb384e666854d87249530f7cbe/pandas-3.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f463ebfd8de7f326d38037c7363c6dacb857c5881ab8961fb387804d6daf2f7", size = 9052483 }, + { url = "https://files.pythonhosted.org/packages/0b/48/aad6ec4f8d007534c091e9a7172b3ec1b1ee6d99a9cbb936b5eab6c6cf58/pandas-3.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5272627187b5d9c20e55d27caf5f2cd23e286aba25cadf73c8590e432e2b7262", size = 10317509 }, + { url = "https://files.pythonhosted.org/packages/a8/14/5990826f779f79148ae9d3a2c39593dc04d61d5d90541e71b5749f35af95/pandas-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:661e0f665932af88c7877f31da0dc743fe9c8f2524bdffe23d24fdcb67ef9d56", size = 9860561 }, + { url = "https://files.pythonhosted.org/packages/fa/80/f01ff54664b6d70fed71475543d108a9b7c888e923ad210795bef04ffb7d/pandas-3.0.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75e6e292ff898679e47a2199172593d9f6107fd2dd3617c22c2946e97d5df46e", size = 10365506 }, + { url = "https://files.pythonhosted.org/packages/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ff8cf1d2896e34343197685f432450ec99a85ba8d90cce2030c5eee2ef98791", size = 10873196 }, + { url = "https://files.pythonhosted.org/packages/48/a9/9301c83d0b47c23ac5deab91c6b39fd98d5b5db4d93b25df8d381451828f/pandas-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eca8b4510f6763f3d37359c2105df03a7a221a508f30e396a51d0713d462e68a", size = 11370859 }, + { url = "https://files.pythonhosted.org/packages/59/fe/0c1fc5bd2d29c7db2ab372330063ad555fb83e08422829c785f5ec2176ca/pandas-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06aff2ad6f0b94a17822cf8b83bbb563b090ed82ff4fe7712db2ce57cd50d9b8", size = 11924584 }, + { url = "https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fea306c783e28884c29057a1d9baa11a349bbf99538ec1da44c8476563d1b25", size = 9742769 }, + { url = "https://files.pythonhosted.org/packages/c4/cb/810a22a6af9a4e97c8ab1c946b47f3489c5bca5adc483ce0ffc84c9cc768/pandas-3.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a8d37a43c52917427e897cb2e429f67a449327394396a81034a4449b99afda59", size = 9043855 }, + { url = "https://files.pythonhosted.org/packages/92/fa/423c89086cca1f039cf1253c3ff5b90f157b5b3757314aa635f6bf3e30aa/pandas-3.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d54855f04f8246ed7b6fc96b05d4871591143c46c0b6f4af874764ed0d2d6f06", size = 10752673 }, + { url = "https://files.pythonhosted.org/packages/22/23/b5a08ec1f40020397f0faba72f1e2c11f7596a6169c7b3e800abff0e433f/pandas-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e1b677accee34a09e0dc2ce5624e4a58a1870ffe56fc021e9caf7f23cd7668f", size = 10404967 }, + { url = "https://files.pythonhosted.org/packages/5c/81/94841f1bb4afdc2b52a99daa895ac2c61600bb72e26525ecc9543d453ebc/pandas-3.0.1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9cabbdcd03f1b6cd254d6dda8ae09b0252524be1592594c00b7895916cb1324", size = 10320575 }, + { url = "https://files.pythonhosted.org/packages/0a/8b/2ae37d66a5342a83adadfd0cb0b4bf9c3c7925424dd5f40d15d6cfaa35ee/pandas-3.0.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ae2ab1f166668b41e770650101e7090824fd34d17915dd9cd479f5c5e0065e9", size = 10710921 }, + { url = "https://files.pythonhosted.org/packages/a2/61/772b2e2757855e232b7ccf7cb8079a5711becb3a97f291c953def15a833f/pandas-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6bf0603c2e30e2cafac32807b06435f28741135cb8697eae8b28c7d492fc7d76", size = 11334191 }, + { url = "https://files.pythonhosted.org/packages/1b/08/b16c6df3ef555d8495d1d265a7963b65be166785d28f06a350913a4fac78/pandas-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c426422973973cae1f4a23e51d4ae85974f44871b24844e4f7de752dd877098", size = 11782256 }, + { url = "https://files.pythonhosted.org/packages/55/80/178af0594890dee17e239fca96d3d8670ba0f5ff59b7d0439850924a9c09/pandas-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b03f91ae8c10a85c1613102c7bef5229b5379f343030a3ccefeca8a33414cf35", size = 10485047 }, + { url = "https://files.pythonhosted.org/packages/bb/8b/4bb774a998b97e6c2fd62a9e6cfdaae133b636fd1c468f92afb4ae9a447a/pandas-3.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:99d0f92ed92d3083d140bf6b97774f9f13863924cf3f52a70711f4e7588f9d0a", size = 10322465 }, + { url = "https://files.pythonhosted.org/packages/72/3a/5b39b51c64159f470f1ca3b1c2a87da290657ca022f7cd11442606f607d1/pandas-3.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3b66857e983208654294bb6477b8a63dee26b37bdd0eb34d010556e91261784f", size = 9910632 }, + { url = "https://files.pythonhosted.org/packages/4e/f7/b449ffb3f68c11da12fc06fbf6d2fa3a41c41e17d0284d23a79e1c13a7e4/pandas-3.0.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56cf59638bf24dc9bdf2154c81e248b3289f9a09a6d04e63608c159022352749", size = 10440535 }, + { url = "https://files.pythonhosted.org/packages/55/77/6ea82043db22cb0f2bbfe7198da3544000ddaadb12d26be36e19b03a2dc5/pandas-3.0.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1a9f55e0f46951874b863d1f3906dcb57df2d9be5c5847ba4dfb55b2c815249", size = 10893940 }, + { url = "https://files.pythonhosted.org/packages/03/30/f1b502a72468c89412c1b882a08f6eed8a4ee9dc033f35f65d0663df6081/pandas-3.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1849f0bba9c8a2fb0f691d492b834cc8dadf617e29015c66e989448d58d011ee", size = 11442711 }, + { url = "https://files.pythonhosted.org/packages/0d/f0/ebb6ddd8fc049e98cabac5c2924d14d1dda26a20adb70d41ea2e428d3ec4/pandas-3.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3d288439e11b5325b02ae6e9cc83e6805a62c40c5a6220bea9beb899c073b1c", size = 11963918 }, + { url = "https://files.pythonhosted.org/packages/09/f8/8ce132104074f977f907442790eaae24e27bce3b3b454e82faa3237ff098/pandas-3.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:93325b0fe372d192965f4cca88d97667f49557398bbf94abdda3bf1b591dbe66", size = 9862099 }, + { url = "https://files.pythonhosted.org/packages/e6/b7/6af9aac41ef2456b768ef0ae60acf8abcebb450a52043d030a65b4b7c9bd/pandas-3.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:97ca08674e3287c7148f4858b01136f8bdfe7202ad25ad04fec602dd1d29d132", size = 9185333 }, + { url = "https://files.pythonhosted.org/packages/66/fc/848bb6710bc6061cb0c5badd65b92ff75c81302e0e31e496d00029fe4953/pandas-3.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:58eeb1b2e0fb322befcf2bbc9ba0af41e616abadb3d3414a6bc7167f6cbfce32", size = 10772664 }, + { url = "https://files.pythonhosted.org/packages/69/5c/866a9bbd0f79263b4b0db6ec1a341be13a1473323f05c122388e0f15b21d/pandas-3.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cd9af1276b5ca9e298bd79a26bda32fa9cc87ed095b2a9a60978d2ca058eaf87", size = 10421286 }, + { url = "https://files.pythonhosted.org/packages/51/a4/2058fb84fb1cfbfb2d4a6d485e1940bb4ad5716e539d779852494479c580/pandas-3.0.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f87a04984d6b63788327cd9f79dda62b7f9043909d2440ceccf709249ca988", size = 10342050 }, + { url = "https://files.pythonhosted.org/packages/22/1b/674e89996cc4be74db3c4eb09240c4bb549865c9c3f5d9b086ff8fcfbf00/pandas-3.0.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85fe4c4df62e1e20f9db6ebfb88c844b092c22cd5324bdcf94bfa2fc1b391221", size = 10740055 }, + { url = "https://files.pythonhosted.org/packages/d0/f8/e954b750764298c22fa4614376531fe63c521ef517e7059a51f062b87dca/pandas-3.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:331ca75a2f8672c365ae25c0b29e46f5ac0c6551fdace8eec4cd65e4fac271ff", size = 11357632 }, + { url = "https://files.pythonhosted.org/packages/6d/02/c6e04b694ffd68568297abd03588b6d30295265176a5c01b7459d3bc35a3/pandas-3.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15860b1fdb1973fffade772fdb931ccf9b2f400a3f5665aef94a00445d7d8dd5", size = 11810974 }, + { url = "https://files.pythonhosted.org/packages/89/41/d7dfb63d2407f12055215070c42fc6ac41b66e90a2946cdc5e759058398b/pandas-3.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:44f1364411d5670efa692b146c748f4ed013df91ee91e9bec5677fb1fd58b937", size = 10884622 }, + { url = "https://files.pythonhosted.org/packages/68/b0/34937815889fa982613775e4b97fddd13250f11012d769949c5465af2150/pandas-3.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:108dd1790337a494aa80e38def654ca3f0968cf4f362c85f44c15e471667102d", size = 9452085 }, ] [[package]] name = "pathspec" version = "1.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206 }, ] [[package]] @@ -5666,9 +5658,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/d8/b280f01045555dc257b8153c00dee3bc75830f91a744cd5f84ef3a0a64b1/pdf2image-1.17.0.tar.gz", hash = "sha256:eaa959bc116b420dd7ec415fcae49b98100dda3dd18cd2fdfa86d09f112f6d57", size = 12811, upload-time = "2024-01-07T20:33:01.965Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/d8/b280f01045555dc257b8153c00dee3bc75830f91a744cd5f84ef3a0a64b1/pdf2image-1.17.0.tar.gz", hash = "sha256:eaa959bc116b420dd7ec415fcae49b98100dda3dd18cd2fdfa86d09f112f6d57", size = 12811 } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/33/61766ae033518957f877ab246f87ca30a85b778ebaad65b7f74fa7e52988/pdf2image-1.17.0-py3-none-any.whl", hash = "sha256:ecdd58d7afb810dffe21ef2b1bbc057ef434dabbac6c33778a38a3f7744a27e2", size = 11618, upload-time = "2024-01-07T20:32:59.957Z" }, + { url = "https://files.pythonhosted.org/packages/62/33/61766ae033518957f877ab246f87ca30a85b778ebaad65b7f74fa7e52988/pdf2image-1.17.0-py3-none-any.whl", hash = "sha256:ecdd58d7afb810dffe21ef2b1bbc057ef434dabbac6c33778a38a3f7744a27e2", size = 11618 }, ] [[package]] @@ -5679,9 +5671,9 @@ dependencies = [ { name = "charset-normalizer" }, { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/a4/5cec1112009f0439a5ca6afa8ace321f0ab2f48da3255b7a1c8953014670/pdfminer_six-20260107.tar.gz", hash = "sha256:96bfd431e3577a55a0efd25676968ca4ce8fd5b53f14565f85716ff363889602", size = 8512094, upload-time = "2026-01-07T13:29:12.937Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/a4/5cec1112009f0439a5ca6afa8ace321f0ab2f48da3255b7a1c8953014670/pdfminer_six-20260107.tar.gz", hash = "sha256:96bfd431e3577a55a0efd25676968ca4ce8fd5b53f14565f85716ff363889602", size = 8512094 } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/8b/28c4eaec9d6b036a52cb44720408f26b1a143ca9bce76cc19e8f5de00ab4/pdfminer_six-20260107-py3-none-any.whl", hash = "sha256:366585ba97e80dffa8f00cebe303d2f381884d8637af4ce422f1df3ef38111a9", size = 6592252, upload-time = "2026-01-07T13:29:10.742Z" }, + { url = "https://files.pythonhosted.org/packages/20/8b/28c4eaec9d6b036a52cb44720408f26b1a143ca9bce76cc19e8f5de00ab4/pdfminer_six-20260107-py3-none-any.whl", hash = "sha256:366585ba97e80dffa8f00cebe303d2f381884d8637af4ce422f1df3ef38111a9", size = 6592252 }, ] [[package]] @@ -5691,9 +5683,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/d8/fd6009cee3e03214667df488cdcf9609461d729968da94e4f95d6359d304/pgvector-0.3.6.tar.gz", hash = "sha256:31d01690e6ea26cea8a633cde5f0f55f5b246d9c8292d68efdef8c22ec994ade", size = 25421, upload-time = "2024-10-27T00:15:09.632Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/d8/fd6009cee3e03214667df488cdcf9609461d729968da94e4f95d6359d304/pgvector-0.3.6.tar.gz", hash = "sha256:31d01690e6ea26cea8a633cde5f0f55f5b246d9c8292d68efdef8c22ec994ade", size = 25421 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/81/f457d6d361e04d061bef413749a6e1ab04d98cfeec6d8abcfe40184750f3/pgvector-0.3.6-py3-none-any.whl", hash = "sha256:f6c269b3c110ccb7496bac87202148ed18f34b390a0189c783e351062400a75a", size = 24880, upload-time = "2024-10-27T00:15:08.045Z" }, + { url = "https://files.pythonhosted.org/packages/fb/81/f457d6d361e04d061bef413749a6e1ab04d98cfeec6d8abcfe40184750f3/pgvector-0.3.6-py3-none-any.whl", hash = "sha256:f6c269b3c110ccb7496bac87202148ed18f34b390a0189c783e351062400a75a", size = 24880 }, ] [[package]] @@ -5707,9 +5699,9 @@ dependencies = [ { name = "segments" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/fa/9294d2f11890ca49d0bdac7a4da60cbe5686629bfd4987cae0ad75e051cc/phonemizer_fork-3.3.2.tar.gz", hash = "sha256:10e16e827d0443b087062e21b55e805c00989cf1343b2e81e734cae5f6c0cf69", size = 300989, upload-time = "2025-01-30T13:02:31.201Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/fa/9294d2f11890ca49d0bdac7a4da60cbe5686629bfd4987cae0ad75e051cc/phonemizer_fork-3.3.2.tar.gz", hash = "sha256:10e16e827d0443b087062e21b55e805c00989cf1343b2e81e734cae5f6c0cf69", size = 300989 } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/f1/0dcce21b0ae16a82df4b6583f8f3ad8e55b35f7e98b6bf536a4dd225fa08/phonemizer_fork-3.3.2-py3-none-any.whl", hash = "sha256:97305c76f4183b3825dae8f4c032265fe78c9946ce58c47d4b62161349264b74", size = 82700, upload-time = "2025-01-30T13:02:28.667Z" }, + { url = "https://files.pythonhosted.org/packages/64/f1/0dcce21b0ae16a82df4b6583f8f3ad8e55b35f7e98b6bf536a4dd225fa08/phonemizer_fork-3.3.2-py3-none-any.whl", hash = "sha256:97305c76f4183b3825dae8f4c032265fe78c9946ce58c47d4b62161349264b74", size = 82700 }, ] [[package]] @@ -5719,36 +5711,36 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/4a/4a18057a7b64254abdcc4f78d92503fc4f5b8fcc66da118ba87989111ee8/pi_heif-1.3.0.tar.gz", hash = "sha256:58151840d0d60507330654a466b06cbf7ca8fb3759eadb5234d70b4dc2bc990c", size = 17131114, upload-time = "2026-02-27T12:22:40.544Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/4a/4a18057a7b64254abdcc4f78d92503fc4f5b8fcc66da118ba87989111ee8/pi_heif-1.3.0.tar.gz", hash = "sha256:58151840d0d60507330654a466b06cbf7ca8fb3759eadb5234d70b4dc2bc990c", size = 17131114 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/eb/4cb3f9789c2fff42ca0b40b0f57fc2a72f68cf62d54c836864cbc2032ec6/pi_heif-1.3.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:09cba007708cef90f95c15c382ece6f51e7ba33fb7fce96b54d786b02c9544e6", size = 1047196, upload-time = "2026-02-27T12:21:58.035Z" }, - { url = "https://files.pythonhosted.org/packages/d2/58/5aeeec1b7f0030902f9d96b168f26b7adaae0c8f758262bba0fa489036a4/pi_heif-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:04ce68ac95103d59b5c8fd25a8a51b40541e76d161d0eff834b9a9a3350fa401", size = 942299, upload-time = "2026-02-27T12:21:59.041Z" }, - { url = "https://files.pythonhosted.org/packages/b2/5b/d706a05b96945aabb122932028f14c21524a81e9655f38fad40de9c096f1/pi_heif-1.3.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7aa8e52e3d736cc07dd0657f87c841be069954a7717ecd6fd24ca8afcc16f6cb", size = 1361016, upload-time = "2026-02-27T12:22:00.039Z" }, - { url = "https://files.pythonhosted.org/packages/90/78/c7e141f8a9943d711a63d1f9c55b4f69b6cad0718d8c80e3a65ca3d42a61/pi_heif-1.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ed464485f7df1d1b575dc1ff539182b09b8312d06c141882bbcfd428dc842cb1", size = 1489604, upload-time = "2026-02-27T12:22:01.096Z" }, - { url = "https://files.pythonhosted.org/packages/a5/26/06f0ba0fcb6a800d8afa73e63c78be6baaae0c442d17da13ff3e7d9033af/pi_heif-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c2f7d26435d25be915914aba7ed383025a594453e3e84fd297975a9584b580c", size = 2343656, upload-time = "2026-02-27T12:22:02.153Z" }, - { url = "https://files.pythonhosted.org/packages/87/f5/9deb76f59f36451dea69ebf0330171c1f953ae514dd03ac82ef2aa902ee3/pi_heif-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:26b3d101f838fbacebaa63e0c8b60a4333ba4d3fe93f4a3b51169ecaaf13c0ac", size = 2507970, upload-time = "2026-02-27T12:22:03.23Z" }, - { url = "https://files.pythonhosted.org/packages/95/08/41c95822b8bbbd61a15e34a25e9a170035a17ef64bf12f95ad0040441b2e/pi_heif-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:633b6053875b8e482538fdc18cf66ba1f94ce7704d244aa325ed7197073155ee", size = 1946959, upload-time = "2026-02-27T12:22:04.672Z" }, - { url = "https://files.pythonhosted.org/packages/87/a3/e921a28ea4b24bbd96cb9e1cd9272ab9a6525e875dcf1fadaeaf73369e81/pi_heif-1.3.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:1b151e3fb9a0ac4f3729da083eacca2ec4389d312d879ac4e01bb6a1c5fa0812", size = 1047186, upload-time = "2026-02-27T12:22:05.778Z" }, - { url = "https://files.pythonhosted.org/packages/68/c9/ea00b10871c63bc856760a47f9a40b2d6c3c50aaff2e7bc336b6f1205749/pi_heif-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ee96ef255f37df9ed0b2d7865e6a746ff594d328c510ee457913f2f677c4f759", size = 942286, upload-time = "2026-02-27T12:22:06.799Z" }, - { url = "https://files.pythonhosted.org/packages/36/28/3accdd524cc56417df99a87d0e1416656100fe3e13e6aee42f5657540eb5/pi_heif-1.3.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d73d35540119e3ccce88a070fbe10e1cf29d119b149bd344c40ac30824edc8f5", size = 1361062, upload-time = "2026-02-27T12:22:08.56Z" }, - { url = "https://files.pythonhosted.org/packages/f2/11/e68468fea402318a1a422467b1077a053ac192281bdd04625a452c3e13ad/pi_heif-1.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd610ad8bc319e78c65e106da2ab71f3f4ba85851f77c1634e7c2352a09e7f97", size = 1489616, upload-time = "2026-02-27T12:22:09.815Z" }, - { url = "https://files.pythonhosted.org/packages/46/9b/470790bb3f37ac52edaba9f4b6ec315060fb0e9114e6ac9b8a704754f1d3/pi_heif-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:baedb73888a9d7c2dc2cfe86831c725b6ee640d6405b709d801e09409a7d0da6", size = 2343656, upload-time = "2026-02-27T12:22:11.199Z" }, - { url = "https://files.pythonhosted.org/packages/15/50/17dcf1f8c05eb1cc0ebd479faba3f5832eb5f2dc477ce48d772bebca196c/pi_heif-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:74488dc873986f584beb27c25fa1484a9d9ae10272f442a2571ca771915c28ea", size = 2508037, upload-time = "2026-02-27T12:22:12.212Z" }, - { url = "https://files.pythonhosted.org/packages/c9/6f/5c246d55bcdcfbfdc3d43dbc29c8a845c6b1c7739c4c88b0b29b93956003/pi_heif-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0ce66f8ce661f5fb15e73ed91f697cec116ce41a6c6849e8b70ead1d3ad60973", size = 1946953, upload-time = "2026-02-27T12:22:13.532Z" }, - { url = "https://files.pythonhosted.org/packages/ea/e6/a4c05ae1fe025f5fe3839b8ab277a6dc861c5feac5214e286bc277ae5ae3/pi_heif-1.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c00a918a20fb8da1883b3142506c0acb52ecff7901014962aa8d30b3ab78a5e2", size = 1047211, upload-time = "2026-02-27T12:22:14.835Z" }, - { url = "https://files.pythonhosted.org/packages/86/fe/b99741aa4ebd31a28ed4f1bb5703b242211b2968aec15f574a7c75993c89/pi_heif-1.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e224db6932794bde6d18a2f4e417785a3944b8a61a6b582d8473026b5cdf0408", size = 942366, upload-time = "2026-02-27T12:22:15.942Z" }, - { url = "https://files.pythonhosted.org/packages/f9/2b/2a07a116a843a70b4f1320d75727ec2ab616609a4f84201fcbeb72afc685/pi_heif-1.3.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab4764fbf8ec958c6c2b3643a2fa313a7f0275649783ce99ed68a1ce5b71ea96", size = 1361322, upload-time = "2026-02-27T12:22:16.939Z" }, - { url = "https://files.pythonhosted.org/packages/56/3c/93fb4aa1734722d4182ad521832c8e5009934d453b157e994b36e4444c02/pi_heif-1.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f84471adc59a80b06476aba241cfd7c56550ba891a3b6525f5b7aa8eadf8166b", size = 1489732, upload-time = "2026-02-27T12:22:17.977Z" }, - { url = "https://files.pythonhosted.org/packages/2a/5c/62f7be4abb279c8ff69bad8c811cdb1224618ab0c5c857ffdb9b4149dc28/pi_heif-1.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dc2cd95a871d26d604d2a6bbf99c4e7644afbe0d302cdf34065deca41f8a2c30", size = 2343780, upload-time = "2026-02-27T12:22:18.996Z" }, - { url = "https://files.pythonhosted.org/packages/e5/7c/26bdeb9f632058d8558e409c37dddd069e58c726286247d693ecef833516/pi_heif-1.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71f568ec93271bedd53917e59f617cf2410dbd8ca307e4bd55e319110d253bc1", size = 2508113, upload-time = "2026-02-27T12:22:20.066Z" }, - { url = "https://files.pythonhosted.org/packages/60/6b/42a1f0c4544d77d87116bb9ca77040566254ec45de5bca5e7201e0b56a6e/pi_heif-1.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:caefadb3a8fcfb7857cd065038b24263b286ddd2ecfd8c8a6c01618d00cc8543", size = 2015496, upload-time = "2026-02-27T12:22:21.102Z" }, - { url = "https://files.pythonhosted.org/packages/95/2a/03baff344d2d664ca955c8d8797920bae49d66c8928134c0a071ab6e0319/pi_heif-1.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:3ffaf9a8a73c686cf6c24aedc9151f06c776591db47ff4245ee8a41a23f1cd22", size = 1048171, upload-time = "2026-02-27T12:22:22.137Z" }, - { url = "https://files.pythonhosted.org/packages/33/06/6b7f6f7e7d5bb08c720d04b15c67d4802154d4516feb371e46dd3d0f6698/pi_heif-1.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:42db92eb41825e9a3cb58a497bd382e61478dd4e2b0e531cdec3f5ddc2f6cefc", size = 943106, upload-time = "2026-02-27T12:22:23.189Z" }, - { url = "https://files.pythonhosted.org/packages/5c/21/75c676f96307eef0da33955481658adbedfff85c37f943b9ed528f633a76/pi_heif-1.3.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ea595ea1fdd64dbcc29e4ab4e84902b22ef16812a12f459e876b3928d35c848", size = 1366398, upload-time = "2026-02-27T12:22:24.489Z" }, - { url = "https://files.pythonhosted.org/packages/77/aa/b8fb005c0e09dfee67fc4965d12bee41a2333e004574e47e1290a16bf851/pi_heif-1.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86d10a002567de7e7b2da6ae993fb5c99d6f6a727c9b457e238987b047ad7f98", size = 1493859, upload-time = "2026-02-27T12:22:25.634Z" }, - { url = "https://files.pythonhosted.org/packages/d2/55/f76fba8d8ca1b95d89673e72067455ea1ba85c8d4cacacb0cee4c4882f52/pi_heif-1.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:95651a2a628ea1560e9f2669f9bb58ecbd02436cc52b6a8f2fff91d4f73107fb", size = 2348962, upload-time = "2026-02-27T12:22:26.992Z" }, - { url = "https://files.pythonhosted.org/packages/57/5a/af51148cf5804a120615548e5ec2fee2f22c19b1d88a0ee705a9f09b9f75/pi_heif-1.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:437f424d8d8bad9f4f23ee4febd8e93b4a2800746e45f676f4543435a7938ca1", size = 2512181, upload-time = "2026-02-27T12:22:29.11Z" }, - { url = "https://files.pythonhosted.org/packages/be/be/83f6f42c1a82cd3eb4a4d85abad9dbf917d4340ece240ba403ee4150de88/pi_heif-1.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:eba226ab71b1f6fde28a020bc3aeb4c6f2daad1cb7784f7dd57f85f9ef204892", size = 2016126, upload-time = "2026-02-27T12:22:30.377Z" }, + { url = "https://files.pythonhosted.org/packages/1e/eb/4cb3f9789c2fff42ca0b40b0f57fc2a72f68cf62d54c836864cbc2032ec6/pi_heif-1.3.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:09cba007708cef90f95c15c382ece6f51e7ba33fb7fce96b54d786b02c9544e6", size = 1047196 }, + { url = "https://files.pythonhosted.org/packages/d2/58/5aeeec1b7f0030902f9d96b168f26b7adaae0c8f758262bba0fa489036a4/pi_heif-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:04ce68ac95103d59b5c8fd25a8a51b40541e76d161d0eff834b9a9a3350fa401", size = 942299 }, + { url = "https://files.pythonhosted.org/packages/b2/5b/d706a05b96945aabb122932028f14c21524a81e9655f38fad40de9c096f1/pi_heif-1.3.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7aa8e52e3d736cc07dd0657f87c841be069954a7717ecd6fd24ca8afcc16f6cb", size = 1361016 }, + { url = "https://files.pythonhosted.org/packages/90/78/c7e141f8a9943d711a63d1f9c55b4f69b6cad0718d8c80e3a65ca3d42a61/pi_heif-1.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ed464485f7df1d1b575dc1ff539182b09b8312d06c141882bbcfd428dc842cb1", size = 1489604 }, + { url = "https://files.pythonhosted.org/packages/a5/26/06f0ba0fcb6a800d8afa73e63c78be6baaae0c442d17da13ff3e7d9033af/pi_heif-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c2f7d26435d25be915914aba7ed383025a594453e3e84fd297975a9584b580c", size = 2343656 }, + { url = "https://files.pythonhosted.org/packages/87/f5/9deb76f59f36451dea69ebf0330171c1f953ae514dd03ac82ef2aa902ee3/pi_heif-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:26b3d101f838fbacebaa63e0c8b60a4333ba4d3fe93f4a3b51169ecaaf13c0ac", size = 2507970 }, + { url = "https://files.pythonhosted.org/packages/95/08/41c95822b8bbbd61a15e34a25e9a170035a17ef64bf12f95ad0040441b2e/pi_heif-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:633b6053875b8e482538fdc18cf66ba1f94ce7704d244aa325ed7197073155ee", size = 1946959 }, + { url = "https://files.pythonhosted.org/packages/87/a3/e921a28ea4b24bbd96cb9e1cd9272ab9a6525e875dcf1fadaeaf73369e81/pi_heif-1.3.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:1b151e3fb9a0ac4f3729da083eacca2ec4389d312d879ac4e01bb6a1c5fa0812", size = 1047186 }, + { url = "https://files.pythonhosted.org/packages/68/c9/ea00b10871c63bc856760a47f9a40b2d6c3c50aaff2e7bc336b6f1205749/pi_heif-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ee96ef255f37df9ed0b2d7865e6a746ff594d328c510ee457913f2f677c4f759", size = 942286 }, + { url = "https://files.pythonhosted.org/packages/36/28/3accdd524cc56417df99a87d0e1416656100fe3e13e6aee42f5657540eb5/pi_heif-1.3.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d73d35540119e3ccce88a070fbe10e1cf29d119b149bd344c40ac30824edc8f5", size = 1361062 }, + { url = "https://files.pythonhosted.org/packages/f2/11/e68468fea402318a1a422467b1077a053ac192281bdd04625a452c3e13ad/pi_heif-1.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd610ad8bc319e78c65e106da2ab71f3f4ba85851f77c1634e7c2352a09e7f97", size = 1489616 }, + { url = "https://files.pythonhosted.org/packages/46/9b/470790bb3f37ac52edaba9f4b6ec315060fb0e9114e6ac9b8a704754f1d3/pi_heif-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:baedb73888a9d7c2dc2cfe86831c725b6ee640d6405b709d801e09409a7d0da6", size = 2343656 }, + { url = "https://files.pythonhosted.org/packages/15/50/17dcf1f8c05eb1cc0ebd479faba3f5832eb5f2dc477ce48d772bebca196c/pi_heif-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:74488dc873986f584beb27c25fa1484a9d9ae10272f442a2571ca771915c28ea", size = 2508037 }, + { url = "https://files.pythonhosted.org/packages/c9/6f/5c246d55bcdcfbfdc3d43dbc29c8a845c6b1c7739c4c88b0b29b93956003/pi_heif-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0ce66f8ce661f5fb15e73ed91f697cec116ce41a6c6849e8b70ead1d3ad60973", size = 1946953 }, + { url = "https://files.pythonhosted.org/packages/ea/e6/a4c05ae1fe025f5fe3839b8ab277a6dc861c5feac5214e286bc277ae5ae3/pi_heif-1.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c00a918a20fb8da1883b3142506c0acb52ecff7901014962aa8d30b3ab78a5e2", size = 1047211 }, + { url = "https://files.pythonhosted.org/packages/86/fe/b99741aa4ebd31a28ed4f1bb5703b242211b2968aec15f574a7c75993c89/pi_heif-1.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e224db6932794bde6d18a2f4e417785a3944b8a61a6b582d8473026b5cdf0408", size = 942366 }, + { url = "https://files.pythonhosted.org/packages/f9/2b/2a07a116a843a70b4f1320d75727ec2ab616609a4f84201fcbeb72afc685/pi_heif-1.3.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab4764fbf8ec958c6c2b3643a2fa313a7f0275649783ce99ed68a1ce5b71ea96", size = 1361322 }, + { url = "https://files.pythonhosted.org/packages/56/3c/93fb4aa1734722d4182ad521832c8e5009934d453b157e994b36e4444c02/pi_heif-1.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f84471adc59a80b06476aba241cfd7c56550ba891a3b6525f5b7aa8eadf8166b", size = 1489732 }, + { url = "https://files.pythonhosted.org/packages/2a/5c/62f7be4abb279c8ff69bad8c811cdb1224618ab0c5c857ffdb9b4149dc28/pi_heif-1.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dc2cd95a871d26d604d2a6bbf99c4e7644afbe0d302cdf34065deca41f8a2c30", size = 2343780 }, + { url = "https://files.pythonhosted.org/packages/e5/7c/26bdeb9f632058d8558e409c37dddd069e58c726286247d693ecef833516/pi_heif-1.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71f568ec93271bedd53917e59f617cf2410dbd8ca307e4bd55e319110d253bc1", size = 2508113 }, + { url = "https://files.pythonhosted.org/packages/60/6b/42a1f0c4544d77d87116bb9ca77040566254ec45de5bca5e7201e0b56a6e/pi_heif-1.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:caefadb3a8fcfb7857cd065038b24263b286ddd2ecfd8c8a6c01618d00cc8543", size = 2015496 }, + { url = "https://files.pythonhosted.org/packages/95/2a/03baff344d2d664ca955c8d8797920bae49d66c8928134c0a071ab6e0319/pi_heif-1.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:3ffaf9a8a73c686cf6c24aedc9151f06c776591db47ff4245ee8a41a23f1cd22", size = 1048171 }, + { url = "https://files.pythonhosted.org/packages/33/06/6b7f6f7e7d5bb08c720d04b15c67d4802154d4516feb371e46dd3d0f6698/pi_heif-1.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:42db92eb41825e9a3cb58a497bd382e61478dd4e2b0e531cdec3f5ddc2f6cefc", size = 943106 }, + { url = "https://files.pythonhosted.org/packages/5c/21/75c676f96307eef0da33955481658adbedfff85c37f943b9ed528f633a76/pi_heif-1.3.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ea595ea1fdd64dbcc29e4ab4e84902b22ef16812a12f459e876b3928d35c848", size = 1366398 }, + { url = "https://files.pythonhosted.org/packages/77/aa/b8fb005c0e09dfee67fc4965d12bee41a2333e004574e47e1290a16bf851/pi_heif-1.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86d10a002567de7e7b2da6ae993fb5c99d6f6a727c9b457e238987b047ad7f98", size = 1493859 }, + { url = "https://files.pythonhosted.org/packages/d2/55/f76fba8d8ca1b95d89673e72067455ea1ba85c8d4cacacb0cee4c4882f52/pi_heif-1.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:95651a2a628ea1560e9f2669f9bb58ecbd02436cc52b6a8f2fff91d4f73107fb", size = 2348962 }, + { url = "https://files.pythonhosted.org/packages/57/5a/af51148cf5804a120615548e5ec2fee2f22c19b1d88a0ee705a9f09b9f75/pi_heif-1.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:437f424d8d8bad9f4f23ee4febd8e93b4a2800746e45f676f4543435a7938ca1", size = 2512181 }, + { url = "https://files.pythonhosted.org/packages/be/be/83f6f42c1a82cd3eb4a4d85abad9dbf917d4340ece240ba403ee4150de88/pi_heif-1.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:eba226ab71b1f6fde28a020bc3aeb4c6f2daad1cb7784f7dd57f85f9ef204892", size = 2016126 }, ] [[package]] @@ -5761,98 +5753,98 @@ dependencies = [ { name = "packaging" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/66/32a45480d84cb239c7ad31209c956496fe5b20f6fb163d794db4c79f840c/pikepdf-10.5.1.tar.gz", hash = "sha256:ffa6c7d0b77deb3af9735e0b0cae177c897431e10d342bb171b62e5527a622b7", size = 4582470, upload-time = "2026-03-18T07:56:00.036Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/66/32a45480d84cb239c7ad31209c956496fe5b20f6fb163d794db4c79f840c/pikepdf-10.5.1.tar.gz", hash = "sha256:ffa6c7d0b77deb3af9735e0b0cae177c897431e10d342bb171b62e5527a622b7", size = 4582470 } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/c8/f0c8ea17555e6bfffa5f598988edc9f1c5861f9909ca72ee745362958453/pikepdf-10.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:03665c0d3658f4bb6084dd65d2db3a44f5af2ef0cd005cbb2ef0af82bcad8c83", size = 4772405, upload-time = "2026-03-18T07:55:20.562Z" }, - { url = "https://files.pythonhosted.org/packages/b8/90/9c201894f8a27a2dad1b6dce92dd497e785e81f4f902f2e261ee04e8c1d6/pikepdf-10.5.1-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:141dab118d6462abf9324f3fe79f18f597db75c6ac96e90984b65f5544e540a3", size = 5089114, upload-time = "2026-03-18T07:55:22.298Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e1/2a0f82254265d432ee0b7323cf897fcbc062f8036853a0353ced58cb5521/pikepdf-10.5.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5d5d0fbfd54acfce3496693f1378d0a0c43025ad96abeb2ffe466737bddaaa0", size = 2491105, upload-time = "2026-03-18T07:55:23.899Z" }, - { url = "https://files.pythonhosted.org/packages/92/23/2d56b5a478aa62d5b1307aa273ca3bb67ac7db7f948708e3ab9dba9eb6b4/pikepdf-10.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3246732f3733dee4048c69a2141c2c0a80af7c9e1d31f35222d6d0d108e3678", size = 2735333, upload-time = "2026-03-18T07:55:25.527Z" }, - { url = "https://files.pythonhosted.org/packages/a6/dd/9678100282f538e5804eb80d885cf0131b1a7a36ca6acbb204858c52c6bd/pikepdf-10.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1580124500a328444c68b8b82ba9bf6166c31e02c5e4924e4bbcea2a8d2e7ee0", size = 3700125, upload-time = "2026-03-18T07:55:27.48Z" }, - { url = "https://files.pythonhosted.org/packages/88/2b/70e9ee1257b9f0010083bd3d9a51e648749284892ad3bb9e3a8691799953/pikepdf-10.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bc2b338a157c8aabafd8ecc7f2aab15e45bf2dcd0ebfe388ffff4fb4147a9e97", size = 3908975, upload-time = "2026-03-18T07:55:29.232Z" }, - { url = "https://files.pythonhosted.org/packages/ad/b0/87cc2fbdcd8ce0a8aeace28c52b0f2acc56cc19a064ec514ed80f246f891/pikepdf-10.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:b220200d96bcaec722c8c8e4a96037515c9d212775587b588fafe692c630a89e", size = 3812237, upload-time = "2026-03-18T07:55:31.285Z" }, - { url = "https://files.pythonhosted.org/packages/7f/d4/eb00bb96b383a1dd3151d347a6339408af642d75ed998f8ac7368ddf5bcd/pikepdf-10.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:0b30d192baf0132e6d945e8b2200288bd32f2b0ec2357b1fe414ef595531b181", size = 4772545, upload-time = "2026-03-18T07:55:33.251Z" }, - { url = "https://files.pythonhosted.org/packages/42/6f/f25b9e66afd647cd090d0e62a5287135ec0ae4971b2f1601a1e3dad96fa9/pikepdf-10.5.1-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:d59a710ba6fc5a5220ac59dba4bd43612663a2fde33973a616843bc79eaf0fac", size = 5088950, upload-time = "2026-03-18T07:55:35.222Z" }, - { url = "https://files.pythonhosted.org/packages/69/9e/f2781afe47f149f88b1c2a3e72a0f2501fcc104c23bffb2e68c89ec81ea7/pikepdf-10.5.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f245df7aeb1a69c166e923ceae9bf47c895a06286dcb94a92225f1b10156e6f", size = 2490804, upload-time = "2026-03-18T07:55:37.247Z" }, - { url = "https://files.pythonhosted.org/packages/9a/77/f87710f01d74dfe8d3713cfe682b350c77aa7a5443552fffceb7b3b40543/pikepdf-10.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e1cdfdeec93a6eca49e6ce592269fd78007d13440719d6f95f3a5a33e609d9f", size = 2734878, upload-time = "2026-03-18T07:55:39.061Z" }, - { url = "https://files.pythonhosted.org/packages/7b/b1/b350dc5cf82de45c0c1c79fd01384b0af07e3ba82da77e276bc98ca00489/pikepdf-10.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b21b093335069d79eecf8639b150e6100043b1275ffdeb00501640d2bcbdf760", size = 3699375, upload-time = "2026-03-18T07:55:40.984Z" }, - { url = "https://files.pythonhosted.org/packages/2c/5e/f7c7473c36687d453bede6afb0a4d8fb0ebb2e846f35219db12542889df1/pikepdf-10.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:89cc87b440f663f1e4f51670930f0aa310cec30cc02d9a1c36a61432be9380fd", size = 3908458, upload-time = "2026-03-18T07:55:43.051Z" }, - { url = "https://files.pythonhosted.org/packages/38/4a/b2949669f3eaae08cc32d21b13f505ebbcabb0d7dd8808fdf743a9eb69ae/pikepdf-10.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:d10f915c80881be4802204a54ba3ce5ee9e13dd59aa6fbe4cb95230039defa86", size = 3812315, upload-time = "2026-03-18T07:55:44.829Z" }, - { url = "https://files.pythonhosted.org/packages/3c/48/b513468b4a5c7d4d9007c2c9b59686d15cca88c339225e4f2069c15799f9/pikepdf-10.5.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:ac7a96d6e4a23cd2dfc07f2cec9f55b000fcd4be4be888dbbe5a767dcd4c409e", size = 4770764, upload-time = "2026-03-18T07:55:46.597Z" }, - { url = "https://files.pythonhosted.org/packages/2d/42/da2abc72d0d04b48158b6bccad6e31dfd2cef63f501cdb6192af100d7545/pikepdf-10.5.1-cp314-cp314-macosx_15_0_x86_64.whl", hash = "sha256:1b81fd3f3de40ec4ad87fe9337d91e5f1301d4c4450ff02f4aa581c76609a3b7", size = 5089799, upload-time = "2026-03-18T07:55:48.45Z" }, - { url = "https://files.pythonhosted.org/packages/1d/47/19731ef57be6007fb5007438618d6803d7abb4adaa095e55a8e7bd5cfa25/pikepdf-10.5.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:781ee394f38ebdf412dc674a1e3333a844dbab673d4d8db04050055062b8fa8d", size = 2493677, upload-time = "2026-03-18T07:55:50.219Z" }, - { url = "https://files.pythonhosted.org/packages/25/87/3e115b7a47c3a5bb3a58a7ba51de20d4964393735fab0085fc94a979113b/pikepdf-10.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17e6a136b870424e66167035406662842783049ee14262f49ed2572caa584475", size = 2736452, upload-time = "2026-03-18T07:55:52.266Z" }, - { url = "https://files.pythonhosted.org/packages/ae/2b/f30006c28b58a12116561fe4fe62c9cd630e97a88de799c27a3073c6bb55/pikepdf-10.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c9ae38d8dd7acf1b4c9bcd9a38fb75735bc62049c34634b1899e47068b314c5f", size = 3702958, upload-time = "2026-03-18T07:55:54.294Z" }, - { url = "https://files.pythonhosted.org/packages/83/47/d467f13394c3be1c57b0fbc4264a8f0d1f6ae42ae61299c4695a89b2d983/pikepdf-10.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:013411404eb129b8d0cd565ce09c4f99254b9837a8e66f90d150b7c1f23a7124", size = 3910716, upload-time = "2026-03-18T07:55:56.233Z" }, - { url = "https://files.pythonhosted.org/packages/5c/b5/7753d726905f217b78e677fee82e877a6880bc326db3a13c1934884ed54b/pikepdf-10.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:70d1b5ae2e61ab5a08e2c978d18208766d5aa0747a2181c95d6e3acaa114f278", size = 3923717, upload-time = "2026-03-18T07:55:58.16Z" }, + { url = "https://files.pythonhosted.org/packages/71/c8/f0c8ea17555e6bfffa5f598988edc9f1c5861f9909ca72ee745362958453/pikepdf-10.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:03665c0d3658f4bb6084dd65d2db3a44f5af2ef0cd005cbb2ef0af82bcad8c83", size = 4772405 }, + { url = "https://files.pythonhosted.org/packages/b8/90/9c201894f8a27a2dad1b6dce92dd497e785e81f4f902f2e261ee04e8c1d6/pikepdf-10.5.1-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:141dab118d6462abf9324f3fe79f18f597db75c6ac96e90984b65f5544e540a3", size = 5089114 }, + { url = "https://files.pythonhosted.org/packages/c9/e1/2a0f82254265d432ee0b7323cf897fcbc062f8036853a0353ced58cb5521/pikepdf-10.5.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5d5d0fbfd54acfce3496693f1378d0a0c43025ad96abeb2ffe466737bddaaa0", size = 2491105 }, + { url = "https://files.pythonhosted.org/packages/92/23/2d56b5a478aa62d5b1307aa273ca3bb67ac7db7f948708e3ab9dba9eb6b4/pikepdf-10.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3246732f3733dee4048c69a2141c2c0a80af7c9e1d31f35222d6d0d108e3678", size = 2735333 }, + { url = "https://files.pythonhosted.org/packages/a6/dd/9678100282f538e5804eb80d885cf0131b1a7a36ca6acbb204858c52c6bd/pikepdf-10.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1580124500a328444c68b8b82ba9bf6166c31e02c5e4924e4bbcea2a8d2e7ee0", size = 3700125 }, + { url = "https://files.pythonhosted.org/packages/88/2b/70e9ee1257b9f0010083bd3d9a51e648749284892ad3bb9e3a8691799953/pikepdf-10.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bc2b338a157c8aabafd8ecc7f2aab15e45bf2dcd0ebfe388ffff4fb4147a9e97", size = 3908975 }, + { url = "https://files.pythonhosted.org/packages/ad/b0/87cc2fbdcd8ce0a8aeace28c52b0f2acc56cc19a064ec514ed80f246f891/pikepdf-10.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:b220200d96bcaec722c8c8e4a96037515c9d212775587b588fafe692c630a89e", size = 3812237 }, + { url = "https://files.pythonhosted.org/packages/7f/d4/eb00bb96b383a1dd3151d347a6339408af642d75ed998f8ac7368ddf5bcd/pikepdf-10.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:0b30d192baf0132e6d945e8b2200288bd32f2b0ec2357b1fe414ef595531b181", size = 4772545 }, + { url = "https://files.pythonhosted.org/packages/42/6f/f25b9e66afd647cd090d0e62a5287135ec0ae4971b2f1601a1e3dad96fa9/pikepdf-10.5.1-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:d59a710ba6fc5a5220ac59dba4bd43612663a2fde33973a616843bc79eaf0fac", size = 5088950 }, + { url = "https://files.pythonhosted.org/packages/69/9e/f2781afe47f149f88b1c2a3e72a0f2501fcc104c23bffb2e68c89ec81ea7/pikepdf-10.5.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f245df7aeb1a69c166e923ceae9bf47c895a06286dcb94a92225f1b10156e6f", size = 2490804 }, + { url = "https://files.pythonhosted.org/packages/9a/77/f87710f01d74dfe8d3713cfe682b350c77aa7a5443552fffceb7b3b40543/pikepdf-10.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e1cdfdeec93a6eca49e6ce592269fd78007d13440719d6f95f3a5a33e609d9f", size = 2734878 }, + { url = "https://files.pythonhosted.org/packages/7b/b1/b350dc5cf82de45c0c1c79fd01384b0af07e3ba82da77e276bc98ca00489/pikepdf-10.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b21b093335069d79eecf8639b150e6100043b1275ffdeb00501640d2bcbdf760", size = 3699375 }, + { url = "https://files.pythonhosted.org/packages/2c/5e/f7c7473c36687d453bede6afb0a4d8fb0ebb2e846f35219db12542889df1/pikepdf-10.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:89cc87b440f663f1e4f51670930f0aa310cec30cc02d9a1c36a61432be9380fd", size = 3908458 }, + { url = "https://files.pythonhosted.org/packages/38/4a/b2949669f3eaae08cc32d21b13f505ebbcabb0d7dd8808fdf743a9eb69ae/pikepdf-10.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:d10f915c80881be4802204a54ba3ce5ee9e13dd59aa6fbe4cb95230039defa86", size = 3812315 }, + { url = "https://files.pythonhosted.org/packages/3c/48/b513468b4a5c7d4d9007c2c9b59686d15cca88c339225e4f2069c15799f9/pikepdf-10.5.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:ac7a96d6e4a23cd2dfc07f2cec9f55b000fcd4be4be888dbbe5a767dcd4c409e", size = 4770764 }, + { url = "https://files.pythonhosted.org/packages/2d/42/da2abc72d0d04b48158b6bccad6e31dfd2cef63f501cdb6192af100d7545/pikepdf-10.5.1-cp314-cp314-macosx_15_0_x86_64.whl", hash = "sha256:1b81fd3f3de40ec4ad87fe9337d91e5f1301d4c4450ff02f4aa581c76609a3b7", size = 5089799 }, + { url = "https://files.pythonhosted.org/packages/1d/47/19731ef57be6007fb5007438618d6803d7abb4adaa095e55a8e7bd5cfa25/pikepdf-10.5.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:781ee394f38ebdf412dc674a1e3333a844dbab673d4d8db04050055062b8fa8d", size = 2493677 }, + { url = "https://files.pythonhosted.org/packages/25/87/3e115b7a47c3a5bb3a58a7ba51de20d4964393735fab0085fc94a979113b/pikepdf-10.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17e6a136b870424e66167035406662842783049ee14262f49ed2572caa584475", size = 2736452 }, + { url = "https://files.pythonhosted.org/packages/ae/2b/f30006c28b58a12116561fe4fe62c9cd630e97a88de799c27a3073c6bb55/pikepdf-10.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c9ae38d8dd7acf1b4c9bcd9a38fb75735bc62049c34634b1899e47068b314c5f", size = 3702958 }, + { url = "https://files.pythonhosted.org/packages/83/47/d467f13394c3be1c57b0fbc4264a8f0d1f6ae42ae61299c4695a89b2d983/pikepdf-10.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:013411404eb129b8d0cd565ce09c4f99254b9837a8e66f90d150b7c1f23a7124", size = 3910716 }, + { url = "https://files.pythonhosted.org/packages/5c/b5/7753d726905f217b78e677fee82e877a6880bc326db3a13c1934884ed54b/pikepdf-10.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:70d1b5ae2e61ab5a08e2c978d18208766d5aa0747a2181c95d6e3acaa114f278", size = 3923717 }, ] [[package]] name = "pillow" version = "12.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264 } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, - { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, - { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, - { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, - { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, - { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, - { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, - { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, - { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, - { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, - { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, - { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, - { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, - { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, - { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, - { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, - { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, - { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, - { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, - { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, - { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, - { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, - { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, - { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, - { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, - { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, - { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, - { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, - { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, - { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, - { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, - { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, - { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, - { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, - { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, - { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, - { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, - { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, - { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, - { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, - { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, - { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, - { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, - { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, - { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803 }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601 }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995 }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012 }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638 }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540 }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613 }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745 }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823 }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367 }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811 }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689 }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535 }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364 }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561 }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460 }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698 }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706 }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621 }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069 }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040 }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523 }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552 }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108 }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712 }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880 }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616 }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008 }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226 }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136 }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129 }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807 }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954 }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441 }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383 }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104 }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652 }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823 }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143 }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254 }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499 }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137 }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721 }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798 }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315 }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360 }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438 }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503 }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748 }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314 }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612 }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567 }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951 }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769 }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358 }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558 }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028 }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940 }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736 }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894 }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446 }, ] [[package]] @@ -5868,9 +5860,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/e4/8303133de5b3850c85d56caf9cc23cc38c74942bb8a940890b225245d7df/pinecone-8.1.0.tar.gz", hash = "sha256:48a00843fb232ccfd57eba618f0c0294e918b030e1bc7e853fb88d04f80ba569", size = 1041965, upload-time = "2026-02-19T20:08:32.999Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/e4/8303133de5b3850c85d56caf9cc23cc38c74942bb8a940890b225245d7df/pinecone-8.1.0.tar.gz", hash = "sha256:48a00843fb232ccfd57eba618f0c0294e918b030e1bc7e853fb88d04f80ba569", size = 1041965 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/f7/beee7033ef92e5964e570fc29a048627e298745916e65c66105378405d06/pinecone-8.1.0-py3-none-any.whl", hash = "sha256:b0ba9c55c9a072fbe4fc7381bc3e5eb1b14550a8007233a3368ada74b1747534", size = 742745, upload-time = "2026-02-19T20:08:31.319Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f7/beee7033ef92e5964e570fc29a048627e298745916e65c66105378405d06/pinecone-8.1.0-py3-none-any.whl", hash = "sha256:b0ba9c55c9a072fbe4fc7381bc3e5eb1b14550a8007233a3368ada74b1747534", size = 742745 }, ] [[package]] @@ -5881,27 +5873,27 @@ dependencies = [ { name = "packaging" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/16/dcaff42ddfeab75dccd17685a0db46489717c3d23753dc14c55770e12aa8/pinecone_plugin_assistant-3.0.2.tar.gz", hash = "sha256:04163af282ad7895b581ab89f850ed139e4ddcea72010cadfa4c573759d5c896", size = 152066, upload-time = "2026-02-01T09:08:48.04Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/16/dcaff42ddfeab75dccd17685a0db46489717c3d23753dc14c55770e12aa8/pinecone_plugin_assistant-3.0.2.tar.gz", hash = "sha256:04163af282ad7895b581ab89f850ed139e4ddcea72010cadfa4c573759d5c896", size = 152066 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/dd/8bc4f3baf6c03acfb0b300f5aba53d19cc3a319281da518182bf22671b92/pinecone_plugin_assistant-3.0.2-py3-none-any.whl", hash = "sha256:de21ff696219fcad6c7ec86a3d1f70875024314537758ab345b6230462342903", size = 280863, upload-time = "2026-02-01T09:08:49.384Z" }, + { url = "https://files.pythonhosted.org/packages/4a/dd/8bc4f3baf6c03acfb0b300f5aba53d19cc3a319281da518182bf22671b92/pinecone_plugin_assistant-3.0.2-py3-none-any.whl", hash = "sha256:de21ff696219fcad6c7ec86a3d1f70875024314537758ab345b6230462342903", size = 280863 }, ] [[package]] name = "pinecone-plugin-interface" version = "0.0.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f4/fb/e8a4063264953ead9e2b24d9b390152c60f042c951c47f4592e9996e57ff/pinecone_plugin_interface-0.0.7.tar.gz", hash = "sha256:b8e6675e41847333aa13923cc44daa3f85676d7157324682dc1640588a982846", size = 3370, upload-time = "2024-06-05T01:57:52.093Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/fb/e8a4063264953ead9e2b24d9b390152c60f042c951c47f4592e9996e57ff/pinecone_plugin_interface-0.0.7.tar.gz", hash = "sha256:b8e6675e41847333aa13923cc44daa3f85676d7157324682dc1640588a982846", size = 3370 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/1d/a21fdfcd6d022cb64cef5c2a29ee6691c6c103c4566b41646b080b7536a5/pinecone_plugin_interface-0.0.7-py3-none-any.whl", hash = "sha256:875857ad9c9fc8bbc074dbe780d187a2afd21f5bfe0f3b08601924a61ef1bba8", size = 6249, upload-time = "2024-06-05T01:57:50.583Z" }, + { url = "https://files.pythonhosted.org/packages/3b/1d/a21fdfcd6d022cb64cef5c2a29ee6691c6c103c4566b41646b080b7536a5/pinecone_plugin_interface-0.0.7-py3-none-any.whl", hash = "sha256:875857ad9c9fc8bbc074dbe780d187a2afd21f5bfe0f3b08601924a61ef1bba8", size = 6249 }, ] [[package]] name = "platformdirs" version = "4.9.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737 } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, + { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216 }, ] [[package]] @@ -5913,23 +5905,23 @@ dependencies = [ { name = "pyee" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/c9/9c6061d5703267f1baae6a4647bfd1862e386fbfdb97d889f6f6ae9e3f64/playwright-1.58.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:96e3204aac292ee639edbfdef6298b4be2ea0a55a16b7068df91adac077cc606", size = 42251098, upload-time = "2026-01-30T15:09:24.028Z" }, - { url = "https://files.pythonhosted.org/packages/e0/40/59d34a756e02f8c670f0fee987d46f7ee53d05447d43cd114ca015cb168c/playwright-1.58.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:70c763694739d28df71ed578b9c8202bb83e8fe8fb9268c04dd13afe36301f71", size = 41039625, upload-time = "2026-01-30T15:09:27.558Z" }, - { url = "https://files.pythonhosted.org/packages/e1/ee/3ce6209c9c74a650aac9028c621f357a34ea5cd4d950700f8e2c4b7fe2c4/playwright-1.58.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:185e0132578733d02802dfddfbbc35f42be23a45ff49ccae5081f25952238117", size = 42251098, upload-time = "2026-01-30T15:09:30.461Z" }, - { url = "https://files.pythonhosted.org/packages/f1/af/009958cbf23fac551a940d34e3206e6c7eed2b8c940d0c3afd1feb0b0589/playwright-1.58.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:c95568ba1eda83812598c1dc9be60b4406dffd60b149bc1536180ad108723d6b", size = 46235268, upload-time = "2026-01-30T15:09:33.787Z" }, - { url = "https://files.pythonhosted.org/packages/d9/a6/0e66ad04b6d3440dae73efb39540c5685c5fc95b17c8b29340b62abbd952/playwright-1.58.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f9999948f1ab541d98812de25e3a8c410776aa516d948807140aff797b4bffa", size = 45964214, upload-time = "2026-01-30T15:09:36.751Z" }, - { url = "https://files.pythonhosted.org/packages/0e/4b/236e60ab9f6d62ed0fd32150d61f1f494cefbf02304c0061e78ed80c1c32/playwright-1.58.0-py3-none-win32.whl", hash = "sha256:1e03be090e75a0fabbdaeab65ce17c308c425d879fa48bb1d7986f96bfad0b99", size = 36815998, upload-time = "2026-01-30T15:09:39.627Z" }, - { url = "https://files.pythonhosted.org/packages/41/f8/5ec599c5e59d2f2f336a05b4f318e733077cd5044f24adb6f86900c3e6a7/playwright-1.58.0-py3-none-win_amd64.whl", hash = "sha256:a2bf639d0ce33b3ba38de777e08697b0d8f3dc07ab6802e4ac53fb65e3907af8", size = 36816005, upload-time = "2026-01-30T15:09:42.449Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c4/cc0229fea55c87d6c9c67fe44a21e2cd28d1d558a5478ed4d617e9fb0c93/playwright-1.58.0-py3-none-win_arm64.whl", hash = "sha256:32ffe5c303901a13a0ecab91d1c3f74baf73b84f4bedbb6b935f5bc11cc98e1b", size = 33085919, upload-time = "2026-01-30T15:09:45.71Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c9/9c6061d5703267f1baae6a4647bfd1862e386fbfdb97d889f6f6ae9e3f64/playwright-1.58.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:96e3204aac292ee639edbfdef6298b4be2ea0a55a16b7068df91adac077cc606", size = 42251098 }, + { url = "https://files.pythonhosted.org/packages/e0/40/59d34a756e02f8c670f0fee987d46f7ee53d05447d43cd114ca015cb168c/playwright-1.58.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:70c763694739d28df71ed578b9c8202bb83e8fe8fb9268c04dd13afe36301f71", size = 41039625 }, + { url = "https://files.pythonhosted.org/packages/e1/ee/3ce6209c9c74a650aac9028c621f357a34ea5cd4d950700f8e2c4b7fe2c4/playwright-1.58.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:185e0132578733d02802dfddfbbc35f42be23a45ff49ccae5081f25952238117", size = 42251098 }, + { url = "https://files.pythonhosted.org/packages/f1/af/009958cbf23fac551a940d34e3206e6c7eed2b8c940d0c3afd1feb0b0589/playwright-1.58.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:c95568ba1eda83812598c1dc9be60b4406dffd60b149bc1536180ad108723d6b", size = 46235268 }, + { url = "https://files.pythonhosted.org/packages/d9/a6/0e66ad04b6d3440dae73efb39540c5685c5fc95b17c8b29340b62abbd952/playwright-1.58.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f9999948f1ab541d98812de25e3a8c410776aa516d948807140aff797b4bffa", size = 45964214 }, + { url = "https://files.pythonhosted.org/packages/0e/4b/236e60ab9f6d62ed0fd32150d61f1f494cefbf02304c0061e78ed80c1c32/playwright-1.58.0-py3-none-win32.whl", hash = "sha256:1e03be090e75a0fabbdaeab65ce17c308c425d879fa48bb1d7986f96bfad0b99", size = 36815998 }, + { url = "https://files.pythonhosted.org/packages/41/f8/5ec599c5e59d2f2f336a05b4f318e733077cd5044f24adb6f86900c3e6a7/playwright-1.58.0-py3-none-win_amd64.whl", hash = "sha256:a2bf639d0ce33b3ba38de777e08697b0d8f3dc07ab6802e4ac53fb65e3907af8", size = 36816005 }, + { url = "https://files.pythonhosted.org/packages/c8/c4/cc0229fea55c87d6c9c67fe44a21e2cd28d1d558a5478ed4d617e9fb0c93/playwright-1.58.0-py3-none-win_arm64.whl", hash = "sha256:32ffe5c303901a13a0ecab91d1c3f74baf73b84f4bedbb6b935f5bc11cc98e1b", size = 33085919 }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, ] [[package]] @@ -5940,9 +5932,9 @@ dependencies = [ { name = "faker" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/68/7717bd9e63ed254617a7d3dc9260904fb736d6ea203e58ffddcb186c64e4/polyfactory-3.3.0.tar.gz", hash = "sha256:237258b6ff43edf362ffd1f68086bb796466f786adfa002b0ac256dbf2246e9a", size = 348668, upload-time = "2026-02-22T09:46:28.01Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/68/7717bd9e63ed254617a7d3dc9260904fb736d6ea203e58ffddcb186c64e4/polyfactory-3.3.0.tar.gz", hash = "sha256:237258b6ff43edf362ffd1f68086bb796466f786adfa002b0ac256dbf2246e9a", size = 348668 } wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/34/b6f19941adcdaf415b5e8a8d577499f5b6a76b59cbae37f9b125a9ffe9f2/polyfactory-3.3.0-py3-none-any.whl", hash = "sha256:686abcaa761930d3df87b91e95b26b8d8cb9fdbbbe0b03d5f918acff5c72606e", size = 62707, upload-time = "2026-02-22T09:46:25.985Z" }, + { url = "https://files.pythonhosted.org/packages/dd/34/b6f19941adcdaf415b5e8a8d577499f5b6a76b59cbae37f9b125a9ffe9f2/polyfactory-3.3.0-py3-none-any.whl", hash = "sha256:686abcaa761930d3df87b91e95b26b8d8cb9fdbbbe0b03d5f918acff5c72606e", size = 62707 }, ] [[package]] @@ -5952,9 +5944,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644, upload-time = "2025-06-14T13:20:40.03Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424, upload-time = "2025-06-14T13:20:38.083Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424 }, ] [[package]] @@ -5965,58 +5957,58 @@ dependencies = [ { name = "cymem" }, { name = "murmurhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/75/fe6b7bbd0dea530a001b0e24c331b21a0be2786e402abf3c57f5dce43d4b/preshed-3.0.13.tar.gz", hash = "sha256:d75f718bbfd97e992f7827e0fa7faf6a91bdd9c922d5baa4b50d62731396cb89", size = 18338, upload-time = "2026-03-23T08:57:31.378Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/75/fe6b7bbd0dea530a001b0e24c331b21a0be2786e402abf3c57f5dce43d4b/preshed-3.0.13.tar.gz", hash = "sha256:d75f718bbfd97e992f7827e0fa7faf6a91bdd9c922d5baa4b50d62731396cb89", size = 18338 } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/fb/ccff23c44c04088c248539005fcda78b9014512a34d170c5360f02ad908b/preshed-3.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5d14eea14bd01291388928991d7df7d60b9fd19ae970e55006eb4d29b0c1e8eb", size = 138497, upload-time = "2026-03-23T08:56:35.321Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ce/cad5a8145881a771e6c0d002f2e585fc19b962f120860b54d32af5baa342/preshed-3.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f05b08ce92399c0655b5e0eb5a1cc1f9e295703ed3aabdfaf6538dfa8ae23d57", size = 138010, upload-time = "2026-03-23T08:56:36.399Z" }, - { url = "https://files.pythonhosted.org/packages/a7/a2/c5fed4fb3e946699259d11e4036a3cfdd8c89b3e542e3077d46781642425/preshed-3.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:62cf7f3113132891d6bba70ff547ad81c6fe50a31930bbbb8499f1d47cd122b7", size = 861498, upload-time = "2026-03-23T08:56:37.67Z" }, - { url = "https://files.pythonhosted.org/packages/51/94/8c9bc48a6ea4903f53a1a0031ce8e35687526949f25821762ef21493c007/preshed-3.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b8de3f58043070a354477995acdd98626ce43e4193c708ebd0f694e467f5155", size = 868988, upload-time = "2026-03-23T08:56:39.324Z" }, - { url = "https://files.pythonhosted.org/packages/b6/df/ecd2f40055ff52527ca117ffbfafb888c1a3079b59fbabe03c5b8f9b7240/preshed-3.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:183b339956a9e1d7a4a00038a3b9587a734db9e8bd915939a49791bd1b372156", size = 1847382, upload-time = "2026-03-23T08:56:40.89Z" }, - { url = "https://files.pythonhosted.org/packages/e6/88/bdb244e40284ded3632a9f88c23bc80230bd7b2ae4a8b7f2cc91adead7a8/preshed-3.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e77bed56aded7cbe5d28d6bd2178bc5b13eda0e0e464dab205fb578fa915000", size = 1919236, upload-time = "2026-03-23T08:56:42.616Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c9/c91ea56342e6c364fc69b444a1ac5432327857199c44032c9cc9dc4c3a23/preshed-3.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:04d8f13f2986e5d11af5ac51f55ce3106c70c41b483d20ea392e6180bdd0f870", size = 122938, upload-time = "2026-03-23T08:56:44.271Z" }, - { url = "https://files.pythonhosted.org/packages/b2/0b/6a99d99619fd83b14c696e2489caed7070647488d4d3ac0b723d35db2de0/preshed-3.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:19318dc1cd8cac6663c6c830bf7e0002d2de853769fb03e056774e97c21bedfd", size = 109194, upload-time = "2026-03-23T08:56:45.346Z" }, - { url = "https://files.pythonhosted.org/packages/0e/2a/401158195d6dc7f6aef0b354d74d0e95c9da124499448c2b3dbb95b71204/preshed-3.0.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0d0c14187dc0078d8a63bf190ec045a4d13e7748b6caeb557a7d575e411410b", size = 137289, upload-time = "2026-03-23T08:56:46.516Z" }, - { url = "https://files.pythonhosted.org/packages/88/8f/e20e64573988528785447a6893b2e7ab287ecfd85b3888e978b28812fd20/preshed-3.0.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7770987c2e57497cd26124a9be5f652b5b3ccd0def89859ab0da8bca6144a3de", size = 136847, upload-time = "2026-03-23T08:56:47.572Z" }, - { url = "https://files.pythonhosted.org/packages/b9/72/18168f881359c4482d312f8dc196371bdd61c1583a52b34390da4c88bbea/preshed-3.0.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4a7bc48220de579be6bdb0a8715482cf36e2a625a6fd5ad26c9f43485a4a23b5", size = 831478, upload-time = "2026-03-23T08:56:48.769Z" }, - { url = "https://files.pythonhosted.org/packages/fd/3a/3543476091087102775568cea9885dde3453569e9aeee365809108de572f/preshed-3.0.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5c8462472f790c16708306aef3a102a762bd19dfe3d2f8ee08bd5e12f51b835", size = 839913, upload-time = "2026-03-23T08:56:49.937Z" }, - { url = "https://files.pythonhosted.org/packages/cf/65/b13f01329decc44ef53cfb6b4601ba85382dcb2a4ec78d9250f03a418066/preshed-3.0.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c046736239cc8d72670749b79b526e4111839a2fc461a58545d212797649129c", size = 1816452, upload-time = "2026-03-23T08:56:51.233Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c7/f1a996c6832234efd4d543041b582418d41ac480ee55c557ec9e65344637/preshed-3.0.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7c333f18e9a81c8a6de0603fd8781e17115324b117c445ca91abdf7bfb1abe49", size = 1888978, upload-time = "2026-03-23T08:56:52.591Z" }, - { url = "https://files.pythonhosted.org/packages/e3/b9/96fb71499049885ce19545903fdd38877bbc2be0da47e37c04d01f3e9f66/preshed-3.0.13-cp313-cp313-win_amd64.whl", hash = "sha256:461327f8dd36520dcf1fd55a671e0c3c2c97a2d95e22fc85faa31173f4785dda", size = 122134, upload-time = "2026-03-23T08:56:54.392Z" }, - { url = "https://files.pythonhosted.org/packages/ef/a7/32a4903019d936a2316fdd330bedddac287ac26326107d24fb76a1fbc60a/preshed-3.0.13-cp313-cp313-win_arm64.whl", hash = "sha256:35d6c5acb3ee3b12b87a551913063f0cec784055c2af16e028c19fe875f079d0", size = 108497, upload-time = "2026-03-23T08:56:55.816Z" }, - { url = "https://files.pythonhosted.org/packages/bb/b5/993886c98f5caaa6f07a648cac97a7c62a3093091cad65e1e43a1bd41cc4/preshed-3.0.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d2f1efae396cadab5f3890a2fd43d2ee65373ef9096ccbb805e51e8d8bcc563b", size = 137882, upload-time = "2026-03-23T08:56:56.878Z" }, - { url = "https://files.pythonhosted.org/packages/c6/86/b7fd137cbf140afd6c45e895946068a15f5b55642916de0075e6eb18581c/preshed-3.0.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8d6acc1f5031a535a55a6f7148e2f274554a8343a16309c700cebea0fe7aee8c", size = 138233, upload-time = "2026-03-23T08:56:58.318Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ca/21a7e79625614134273dfed32bca5bb4c2ec1313e33fbd12d41657536f1f/preshed-3.0.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7da9d931e7660dcdd757e5870269f0c159126d682ed73ed313971d199eb0f334", size = 834835, upload-time = "2026-03-23T08:56:59.48Z" }, - { url = "https://files.pythonhosted.org/packages/8f/3a/2dbd299516461831ae90e0d5b0637137bf28520c4e6dd0b01d6f1886659a/preshed-3.0.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4ae5cfe075bb7a07982e382bca44f41ddf041f4d24cbd358e8cccfc049259b8", size = 834928, upload-time = "2026-03-23T08:57:01.075Z" }, - { url = "https://files.pythonhosted.org/packages/7c/d3/af654eba4f6587c4ee02c5043e62c194b0a1c4431ffef0c67b9518f6b61c/preshed-3.0.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7557963d0125a3a7bcdb2eb6948f3e45da31b5a7f066b55320de3dea22d7557f", size = 1820368, upload-time = "2026-03-23T08:57:02.351Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9b/ebcb2b9e8cb881e40b55b0bf450f8a6b187e2ef3ae0c685cce81d2d85026/preshed-3.0.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c4bc60dc994864095d784b7e4d77dba3e64188d169ac88722b699d175561fddb", size = 1888251, upload-time = "2026-03-23T08:57:04.158Z" }, - { url = "https://files.pythonhosted.org/packages/97/f7/c6c012779edcaa6e2cd092c554e98dc53e77f41205b07208655ba77e2327/preshed-3.0.13-cp314-cp314-win_amd64.whl", hash = "sha256:208dcebbe294bf1881ce33fb015d56ab2a7587aece85a09147727174207892e4", size = 125211, upload-time = "2026-03-23T08:57:05.83Z" }, - { url = "https://files.pythonhosted.org/packages/f8/82/390ef87d732ef64e673ef6bf9e5d898453986e979efa50fb3a400e2c0766/preshed-3.0.13-cp314-cp314-win_arm64.whl", hash = "sha256:cf8e1a7a1823b2a7765121446c630140ac6e8650c07a6efbf375e168d1fef4f7", size = 111942, upload-time = "2026-03-23T08:57:06.996Z" }, - { url = "https://files.pythonhosted.org/packages/80/3a/a9dde3167bcecb27ae82ce4567b5ab1aa3989113ae6814c092ce223cc4ef/preshed-3.0.13-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9ca43ecbc3783eda4d6ab3416ae2ecd9ef23dca5f53995843f69f7457bcd0677", size = 144997, upload-time = "2026-03-23T08:57:08.064Z" }, - { url = "https://files.pythonhosted.org/packages/74/d4/22d9355b50b6a13b407dcad0a81df83fb1d5602092d1f05834674dde8fda/preshed-3.0.13-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c8596e41a258ff213553a441e0bb3eb388fd8158e84a7bf3aae6d8ede2c166d3", size = 147294, upload-time = "2026-03-23T08:57:09.411Z" }, - { url = "https://files.pythonhosted.org/packages/70/42/a225ee83fdb306d2a503f21a627953b820f4e079c90c8a84338957cb8ff5/preshed-3.0.13-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4f8856ca3d88e9b250630d70abb4f260d8933151ddfb413024784b25b009868e", size = 952110, upload-time = "2026-03-23T08:57:10.592Z" }, - { url = "https://files.pythonhosted.org/packages/40/ba/09a9dfe3d22d7e745483fd5d7f2a82cd4d39c161f7d2daa0faa4bd6402be/preshed-3.0.13-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e5b2865aecbd2e1e10e5d19bb8bfad765863c1307c6c3e51f2a08bd64122409", size = 932217, upload-time = "2026-03-23T08:57:12.124Z" }, - { url = "https://files.pythonhosted.org/packages/6c/5c/e10e2e05133e7fcbd7c40536af1148c82dd24357b8f5726e2c7bc51cfd53/preshed-3.0.13-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:09f96b477c987755b3c945df214ea1c1c80bfb350e9f34e78da89585535b77e8", size = 1896542, upload-time = "2026-03-23T08:57:13.525Z" }, - { url = "https://files.pythonhosted.org/packages/37/aa/51e5b4109a4cdfae28c3613eeeb10764a3794ebef8de93ffbb109465bea3/preshed-3.0.13-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:670db59a52e1823b5f088c764df474e65b686592d4093adbeef14581c95ee2cb", size = 1959473, upload-time = "2026-03-23T08:57:15.706Z" }, - { url = "https://files.pythonhosted.org/packages/0e/6a/1d966f367a14c703dde629d150d996c1b727d442f620300b21c9ec1a24d1/preshed-3.0.13-cp314-cp314t-win_amd64.whl", hash = "sha256:b03e21b0bf95eb56e23973f32cabb930e94f352228652f81c0955dbd6967d904", size = 146229, upload-time = "2026-03-23T08:57:17.457Z" }, - { url = "https://files.pythonhosted.org/packages/22/80/368139067603e590a000122355f9c8576c8ebed4fb0b8849feaa2698489d/preshed-3.0.13-cp314-cp314t-win_arm64.whl", hash = "sha256:b980f3ea9bb74b7f94464bc3d6eb3c9162b6b79b531febd14c6465c24344d2cc", size = 119339, upload-time = "2026-03-23T08:57:18.882Z" }, + { url = "https://files.pythonhosted.org/packages/39/fb/ccff23c44c04088c248539005fcda78b9014512a34d170c5360f02ad908b/preshed-3.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5d14eea14bd01291388928991d7df7d60b9fd19ae970e55006eb4d29b0c1e8eb", size = 138497 }, + { url = "https://files.pythonhosted.org/packages/8e/ce/cad5a8145881a771e6c0d002f2e585fc19b962f120860b54d32af5baa342/preshed-3.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f05b08ce92399c0655b5e0eb5a1cc1f9e295703ed3aabdfaf6538dfa8ae23d57", size = 138010 }, + { url = "https://files.pythonhosted.org/packages/a7/a2/c5fed4fb3e946699259d11e4036a3cfdd8c89b3e542e3077d46781642425/preshed-3.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:62cf7f3113132891d6bba70ff547ad81c6fe50a31930bbbb8499f1d47cd122b7", size = 861498 }, + { url = "https://files.pythonhosted.org/packages/51/94/8c9bc48a6ea4903f53a1a0031ce8e35687526949f25821762ef21493c007/preshed-3.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b8de3f58043070a354477995acdd98626ce43e4193c708ebd0f694e467f5155", size = 868988 }, + { url = "https://files.pythonhosted.org/packages/b6/df/ecd2f40055ff52527ca117ffbfafb888c1a3079b59fbabe03c5b8f9b7240/preshed-3.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:183b339956a9e1d7a4a00038a3b9587a734db9e8bd915939a49791bd1b372156", size = 1847382 }, + { url = "https://files.pythonhosted.org/packages/e6/88/bdb244e40284ded3632a9f88c23bc80230bd7b2ae4a8b7f2cc91adead7a8/preshed-3.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e77bed56aded7cbe5d28d6bd2178bc5b13eda0e0e464dab205fb578fa915000", size = 1919236 }, + { url = "https://files.pythonhosted.org/packages/a0/c9/c91ea56342e6c364fc69b444a1ac5432327857199c44032c9cc9dc4c3a23/preshed-3.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:04d8f13f2986e5d11af5ac51f55ce3106c70c41b483d20ea392e6180bdd0f870", size = 122938 }, + { url = "https://files.pythonhosted.org/packages/b2/0b/6a99d99619fd83b14c696e2489caed7070647488d4d3ac0b723d35db2de0/preshed-3.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:19318dc1cd8cac6663c6c830bf7e0002d2de853769fb03e056774e97c21bedfd", size = 109194 }, + { url = "https://files.pythonhosted.org/packages/0e/2a/401158195d6dc7f6aef0b354d74d0e95c9da124499448c2b3dbb95b71204/preshed-3.0.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0d0c14187dc0078d8a63bf190ec045a4d13e7748b6caeb557a7d575e411410b", size = 137289 }, + { url = "https://files.pythonhosted.org/packages/88/8f/e20e64573988528785447a6893b2e7ab287ecfd85b3888e978b28812fd20/preshed-3.0.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7770987c2e57497cd26124a9be5f652b5b3ccd0def89859ab0da8bca6144a3de", size = 136847 }, + { url = "https://files.pythonhosted.org/packages/b9/72/18168f881359c4482d312f8dc196371bdd61c1583a52b34390da4c88bbea/preshed-3.0.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4a7bc48220de579be6bdb0a8715482cf36e2a625a6fd5ad26c9f43485a4a23b5", size = 831478 }, + { url = "https://files.pythonhosted.org/packages/fd/3a/3543476091087102775568cea9885dde3453569e9aeee365809108de572f/preshed-3.0.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5c8462472f790c16708306aef3a102a762bd19dfe3d2f8ee08bd5e12f51b835", size = 839913 }, + { url = "https://files.pythonhosted.org/packages/cf/65/b13f01329decc44ef53cfb6b4601ba85382dcb2a4ec78d9250f03a418066/preshed-3.0.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c046736239cc8d72670749b79b526e4111839a2fc461a58545d212797649129c", size = 1816452 }, + { url = "https://files.pythonhosted.org/packages/d1/c7/f1a996c6832234efd4d543041b582418d41ac480ee55c557ec9e65344637/preshed-3.0.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7c333f18e9a81c8a6de0603fd8781e17115324b117c445ca91abdf7bfb1abe49", size = 1888978 }, + { url = "https://files.pythonhosted.org/packages/e3/b9/96fb71499049885ce19545903fdd38877bbc2be0da47e37c04d01f3e9f66/preshed-3.0.13-cp313-cp313-win_amd64.whl", hash = "sha256:461327f8dd36520dcf1fd55a671e0c3c2c97a2d95e22fc85faa31173f4785dda", size = 122134 }, + { url = "https://files.pythonhosted.org/packages/ef/a7/32a4903019d936a2316fdd330bedddac287ac26326107d24fb76a1fbc60a/preshed-3.0.13-cp313-cp313-win_arm64.whl", hash = "sha256:35d6c5acb3ee3b12b87a551913063f0cec784055c2af16e028c19fe875f079d0", size = 108497 }, + { url = "https://files.pythonhosted.org/packages/bb/b5/993886c98f5caaa6f07a648cac97a7c62a3093091cad65e1e43a1bd41cc4/preshed-3.0.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d2f1efae396cadab5f3890a2fd43d2ee65373ef9096ccbb805e51e8d8bcc563b", size = 137882 }, + { url = "https://files.pythonhosted.org/packages/c6/86/b7fd137cbf140afd6c45e895946068a15f5b55642916de0075e6eb18581c/preshed-3.0.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8d6acc1f5031a535a55a6f7148e2f274554a8343a16309c700cebea0fe7aee8c", size = 138233 }, + { url = "https://files.pythonhosted.org/packages/8b/ca/21a7e79625614134273dfed32bca5bb4c2ec1313e33fbd12d41657536f1f/preshed-3.0.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7da9d931e7660dcdd757e5870269f0c159126d682ed73ed313971d199eb0f334", size = 834835 }, + { url = "https://files.pythonhosted.org/packages/8f/3a/2dbd299516461831ae90e0d5b0637137bf28520c4e6dd0b01d6f1886659a/preshed-3.0.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4ae5cfe075bb7a07982e382bca44f41ddf041f4d24cbd358e8cccfc049259b8", size = 834928 }, + { url = "https://files.pythonhosted.org/packages/7c/d3/af654eba4f6587c4ee02c5043e62c194b0a1c4431ffef0c67b9518f6b61c/preshed-3.0.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7557963d0125a3a7bcdb2eb6948f3e45da31b5a7f066b55320de3dea22d7557f", size = 1820368 }, + { url = "https://files.pythonhosted.org/packages/bf/9b/ebcb2b9e8cb881e40b55b0bf450f8a6b187e2ef3ae0c685cce81d2d85026/preshed-3.0.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c4bc60dc994864095d784b7e4d77dba3e64188d169ac88722b699d175561fddb", size = 1888251 }, + { url = "https://files.pythonhosted.org/packages/97/f7/c6c012779edcaa6e2cd092c554e98dc53e77f41205b07208655ba77e2327/preshed-3.0.13-cp314-cp314-win_amd64.whl", hash = "sha256:208dcebbe294bf1881ce33fb015d56ab2a7587aece85a09147727174207892e4", size = 125211 }, + { url = "https://files.pythonhosted.org/packages/f8/82/390ef87d732ef64e673ef6bf9e5d898453986e979efa50fb3a400e2c0766/preshed-3.0.13-cp314-cp314-win_arm64.whl", hash = "sha256:cf8e1a7a1823b2a7765121446c630140ac6e8650c07a6efbf375e168d1fef4f7", size = 111942 }, + { url = "https://files.pythonhosted.org/packages/80/3a/a9dde3167bcecb27ae82ce4567b5ab1aa3989113ae6814c092ce223cc4ef/preshed-3.0.13-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9ca43ecbc3783eda4d6ab3416ae2ecd9ef23dca5f53995843f69f7457bcd0677", size = 144997 }, + { url = "https://files.pythonhosted.org/packages/74/d4/22d9355b50b6a13b407dcad0a81df83fb1d5602092d1f05834674dde8fda/preshed-3.0.13-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c8596e41a258ff213553a441e0bb3eb388fd8158e84a7bf3aae6d8ede2c166d3", size = 147294 }, + { url = "https://files.pythonhosted.org/packages/70/42/a225ee83fdb306d2a503f21a627953b820f4e079c90c8a84338957cb8ff5/preshed-3.0.13-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4f8856ca3d88e9b250630d70abb4f260d8933151ddfb413024784b25b009868e", size = 952110 }, + { url = "https://files.pythonhosted.org/packages/40/ba/09a9dfe3d22d7e745483fd5d7f2a82cd4d39c161f7d2daa0faa4bd6402be/preshed-3.0.13-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e5b2865aecbd2e1e10e5d19bb8bfad765863c1307c6c3e51f2a08bd64122409", size = 932217 }, + { url = "https://files.pythonhosted.org/packages/6c/5c/e10e2e05133e7fcbd7c40536af1148c82dd24357b8f5726e2c7bc51cfd53/preshed-3.0.13-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:09f96b477c987755b3c945df214ea1c1c80bfb350e9f34e78da89585535b77e8", size = 1896542 }, + { url = "https://files.pythonhosted.org/packages/37/aa/51e5b4109a4cdfae28c3613eeeb10764a3794ebef8de93ffbb109465bea3/preshed-3.0.13-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:670db59a52e1823b5f088c764df474e65b686592d4093adbeef14581c95ee2cb", size = 1959473 }, + { url = "https://files.pythonhosted.org/packages/0e/6a/1d966f367a14c703dde629d150d996c1b727d442f620300b21c9ec1a24d1/preshed-3.0.13-cp314-cp314t-win_amd64.whl", hash = "sha256:b03e21b0bf95eb56e23973f32cabb930e94f352228652f81c0955dbd6967d904", size = 146229 }, + { url = "https://files.pythonhosted.org/packages/22/80/368139067603e590a000122355f9c8576c8ebed4fb0b8849feaa2698489d/preshed-3.0.13-cp314-cp314t-win_arm64.whl", hash = "sha256:b980f3ea9bb74b7f94464bc3d6eb3c9162b6b79b531febd14c6465c24344d2cc", size = 119339 }, ] [[package]] name = "progress" version = "1.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ac/26/3b086f0c5d6c1c18c2430d6fac3a99d79553884ca6cdf759cf256dd43b7d/progress-1.6.1.tar.gz", hash = "sha256:c1ba719f862ce885232a759eab47971fe74dfc7bb76ab8a51ef5940bad35086c", size = 7164, upload-time = "2025-07-01T05:50:43.33Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/26/3b086f0c5d6c1c18c2430d6fac3a99d79553884ca6cdf759cf256dd43b7d/progress-1.6.1.tar.gz", hash = "sha256:c1ba719f862ce885232a759eab47971fe74dfc7bb76ab8a51ef5940bad35086c", size = 7164 } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/59/123aee44a039b212cfb8d90be1adf06496a99b313ee1683aadf90b3d9799/progress-1.6.1-py3-none-any.whl", hash = "sha256:5239f22f305c12fdc8ce6e0e47f70f21622a935e16eafc4535617112e7c7ea0b", size = 9761, upload-time = "2025-07-01T05:50:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/123aee44a039b212cfb8d90be1adf06496a99b313ee1683aadf90b3d9799/progress-1.6.1-py3-none-any.whl", hash = "sha256:5239f22f305c12fdc8ce6e0e47f70f21622a935e16eafc4535617112e7c7ea0b", size = 9761 }, ] [[package]] name = "prometheus-client" version = "0.24.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616, upload-time = "2026-01-14T15:26:26.965Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616 } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057, upload-time = "2026-01-14T15:26:24.42Z" }, + { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057 }, ] [[package]] @@ -6026,93 +6018,93 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198 } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431 }, ] [[package]] name = "propcache" version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, - { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, - { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, - { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, - { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, - { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, - { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, - { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, - { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, - { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, - { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, - { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, - { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, - { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, - { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, - { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, - { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, - { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, - { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, - { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, - { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, - { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, - { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, - { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, - { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, - { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, - { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, - { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, - { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, - { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, - { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, - { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, - { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, - { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, - { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, - { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, - { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, - { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, - { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, - { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, - { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, - { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, - { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, - { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, - { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, - { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, - { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, - { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, - { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, - { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, - { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, - { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, - { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, - { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, - { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, - { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, - { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, - { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, - { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061 }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037 }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324 }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505 }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242 }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474 }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575 }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736 }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019 }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376 }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988 }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615 }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066 }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655 }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789 }, + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750 }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780 }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308 }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182 }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215 }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112 }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442 }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398 }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920 }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748 }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877 }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437 }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586 }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790 }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158 }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451 }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374 }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396 }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950 }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856 }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420 }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254 }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205 }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873 }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739 }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514 }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781 }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396 }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897 }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789 }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152 }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869 }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596 }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981 }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490 }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371 }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424 }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566 }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130 }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625 }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209 }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797 }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140 }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257 }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097 }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455 }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372 }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411 }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712 }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557 }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015 }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880 }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938 }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641 }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510 }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161 }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393 }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546 }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259 }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428 }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305 }, ] [[package]] @@ -6122,52 +6114,52 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/0d/94dfe80193e79d55258345901acd2917523d56e8381bc4dee7fd38e3868a/proto_plus-1.27.2.tar.gz", hash = "sha256:b2adde53adadf75737c44d3dcb0104fde65250dfc83ad59168b4aa3e574b6a24", size = 57204, upload-time = "2026-03-26T22:18:57.174Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/0d/94dfe80193e79d55258345901acd2917523d56e8381bc4dee7fd38e3868a/proto_plus-1.27.2.tar.gz", hash = "sha256:b2adde53adadf75737c44d3dcb0104fde65250dfc83ad59168b4aa3e574b6a24", size = 57204 } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/f3/1fba73eeffafc998a25d59703b63f8be4fe8a5cb12eaff7386a0ba0f7125/proto_plus-1.27.2-py3-none-any.whl", hash = "sha256:6432f75893d3b9e70b9c412f1d2f03f65b11fb164b793d14ae2ca01821d22718", size = 50450, upload-time = "2026-03-26T22:13:42.927Z" }, + { url = "https://files.pythonhosted.org/packages/84/f3/1fba73eeffafc998a25d59703b63f8be4fe8a5cb12eaff7386a0ba0f7125/proto_plus-1.27.2-py3-none-any.whl", hash = "sha256:6432f75893d3b9e70b9c412f1d2f03f65b11fb164b793d14ae2ca01821d22718", size = 50450 }, ] [[package]] name = "protobuf" version = "6.33.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, - { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, - { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, - { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, - { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, - { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739 }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089 }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737 }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610 }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381 }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436 }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656 }, ] [[package]] name = "psutil" version = "7.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740 } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, - { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, - { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, - { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, - { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, - { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, - { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, - { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, - { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, - { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, - { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, - { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, - { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595 }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082 }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476 }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062 }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893 }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589 }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664 }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087 }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383 }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210 }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228 }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284 }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090 }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859 }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560 }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997 }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972 }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266 }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737 }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617 }, ] [[package]] @@ -6178,9 +6170,9 @@ dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.13'" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/b6/379d0a960f8f435ec78720462fd94c4863e7a31237cf81bf76d0af5883bf/psycopg-3.3.3.tar.gz", hash = "sha256:5e9a47458b3c1583326513b2556a2a9473a1001a56c9efe9e587245b43148dd9", size = 165624, upload-time = "2026-02-18T16:52:16.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/b6/379d0a960f8f435ec78720462fd94c4863e7a31237cf81bf76d0af5883bf/psycopg-3.3.3.tar.gz", hash = "sha256:5e9a47458b3c1583326513b2556a2a9473a1001a56c9efe9e587245b43148dd9", size = 165624 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/5b/181e2e3becb7672b502f0ed7f16ed7352aca7c109cfb94cf3878a9186db9/psycopg-3.3.3-py3-none-any.whl", hash = "sha256:f96525a72bcfade6584ab17e89de415ff360748c766f0106959144dcbb38c698", size = 212768, upload-time = "2026-02-18T16:46:27.365Z" }, + { url = "https://files.pythonhosted.org/packages/c8/5b/181e2e3becb7672b502f0ed7f16ed7352aca7c109cfb94cf3878a9186db9/psycopg-3.3.3-py3-none-any.whl", hash = "sha256:f96525a72bcfade6584ab17e89de415ff360748c766f0106959144dcbb38c698", size = 212768 }, ] [package.optional-dependencies] @@ -6196,39 +6188,39 @@ name = "psycopg-binary" version = "3.3.3" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/15/021be5c0cbc5b7c1ab46e91cc3434eb42569f79a0592e67b8d25e66d844d/psycopg_binary-3.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6698dbab5bcef8fdb570fc9d35fd9ac52041771bfcfe6fd0fc5f5c4e36f1e99d", size = 4591170, upload-time = "2026-02-18T16:48:55.594Z" }, - { url = "https://files.pythonhosted.org/packages/f1/54/a60211c346c9a2f8c6b272b5f2bbe21f6e11800ce7f61e99ba75cf8b63e1/psycopg_binary-3.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:329ff393441e75f10b673ae99ab45276887993d49e65f141da20d915c05aafd8", size = 4670009, upload-time = "2026-02-18T16:49:03.608Z" }, - { url = "https://files.pythonhosted.org/packages/c1/53/ac7c18671347c553362aadbf65f92786eef9540676ca24114cc02f5be405/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:eb072949b8ebf4082ae24289a2b0fd724da9adc8f22743409d6fd718ddb379df", size = 5469735, upload-time = "2026-02-18T16:49:10.128Z" }, - { url = "https://files.pythonhosted.org/packages/7f/c3/4f4e040902b82a344eff1c736cde2f2720f127fe939c7e7565706f96dd44/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:263a24f39f26e19ed7fc982d7859a36f17841b05bebad3eb47bb9cd2dd785351", size = 5152919, upload-time = "2026-02-18T16:49:16.335Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e7/d929679c6a5c212bcf738806c7c89f5b3d0919f2e1685a0e08d6ff877945/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5152d50798c2fa5bd9b68ec68eb68a1b71b95126c1d70adaa1a08cd5eefdc23d", size = 6738785, upload-time = "2026-02-18T16:49:22.687Z" }, - { url = "https://files.pythonhosted.org/packages/69/b0/09703aeb69a9443d232d7b5318d58742e8ca51ff79f90ffe6b88f1db45e7/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d6a1e56dd267848edb824dbeb08cf5bac649e02ee0b03ba883ba3f4f0bd54f2", size = 4979008, upload-time = "2026-02-18T16:49:27.313Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a6/e662558b793c6e13a7473b970fee327d635270e41eded3090ef14045a6a5/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73eaaf4bb04709f545606c1db2f65f4000e8a04cdbf3e00d165a23004692093e", size = 4508255, upload-time = "2026-02-18T16:49:31.575Z" }, - { url = "https://files.pythonhosted.org/packages/5f/7f/0f8b2e1d5e0093921b6f324a948a5c740c1447fbb45e97acaf50241d0f39/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:162e5675efb4704192411eaf8e00d07f7960b679cd3306e7efb120bb8d9456cc", size = 4189166, upload-time = "2026-02-18T16:49:35.801Z" }, - { url = "https://files.pythonhosted.org/packages/92/ec/ce2e91c33bc8d10b00c87e2f6b0fb570641a6a60042d6a9ae35658a3a797/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:fab6b5e37715885c69f5d091f6ff229be71e235f272ebaa35158d5a46fd548a0", size = 3924544, upload-time = "2026-02-18T16:49:41.129Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2f/7718141485f73a924205af60041c392938852aa447a94c8cbd222ff389a1/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a4aab31bd6d1057f287c96c0effca3a25584eb9cc702f282ecb96ded7814e830", size = 4235297, upload-time = "2026-02-18T16:49:46.726Z" }, - { url = "https://files.pythonhosted.org/packages/57/f9/1add717e2643a003bbde31b1b220172e64fbc0cb09f06429820c9173f7fc/psycopg_binary-3.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:59aa31fe11a0e1d1bcc2ce37ed35fe2ac84cd65bb9036d049b1a1c39064d0f14", size = 3547659, upload-time = "2026-02-18T16:49:52.999Z" }, - { url = "https://files.pythonhosted.org/packages/03/0a/cac9fdf1df16a269ba0e5f0f06cac61f826c94cadb39df028cdfe19d3a33/psycopg_binary-3.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05f32239aec25c5fb15f7948cffdc2dc0dac098e48b80a140e4ba32b572a2e7d", size = 4590414, upload-time = "2026-02-18T16:50:01.441Z" }, - { url = "https://files.pythonhosted.org/packages/9c/c0/d8f8508fbf440edbc0099b1abff33003cd80c9e66eb3a1e78834e3fb4fb9/psycopg_binary-3.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c84f9d214f2d1de2fafebc17fa68ac3f6561a59e291553dfc45ad299f4898c1", size = 4669021, upload-time = "2026-02-18T16:50:08.803Z" }, - { url = "https://files.pythonhosted.org/packages/04/05/097016b77e343b4568feddf12c72171fc513acef9a4214d21b9478569068/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e77957d2ba17cada11be09a5066d93026cdb61ada7c8893101d7fe1c6e1f3925", size = 5467453, upload-time = "2026-02-18T16:50:14.985Z" }, - { url = "https://files.pythonhosted.org/packages/91/23/73244e5feb55b5ca109cede6e97f32ef45189f0fdac4c80d75c99862729d/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:42961609ac07c232a427da7c87a468d3c82fee6762c220f38e37cfdacb2b178d", size = 5151135, upload-time = "2026-02-18T16:50:24.82Z" }, - { url = "https://files.pythonhosted.org/packages/11/49/5309473b9803b207682095201d8708bbc7842ddf3f192488a69204e36455/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae07a3114313dd91fce686cab2f4c44af094398519af0e0f854bc707e1aeedf1", size = 6737315, upload-time = "2026-02-18T16:50:35.106Z" }, - { url = "https://files.pythonhosted.org/packages/d4/5d/03abe74ef34d460b33c4d9662bf6ec1dd38888324323c1a1752133c10377/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d257c58d7b36a621dcce1d01476ad8b60f12d80eb1406aee4cf796f88b2ae482", size = 4979783, upload-time = "2026-02-18T16:50:42.067Z" }, - { url = "https://files.pythonhosted.org/packages/f0/6c/3fbf8e604e15f2f3752900434046c00c90bb8764305a1b81112bff30ba24/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07c7211f9327d522c9c47560cae00a4ecf6687f4e02d779d035dd3177b41cb12", size = 4509023, upload-time = "2026-02-18T16:50:50.116Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6b/1a06b43b7c7af756c80b67eac8bfaa51d77e68635a8a8d246e4f0bb7604a/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8e7e9eca9b363dbedeceeadd8be97149d2499081f3c52d141d7cd1f395a91f83", size = 4185874, upload-time = "2026-02-18T16:50:55.97Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d3/bf49e3dcaadba510170c8d111e5e69e5ae3f981c1554c5bb71c75ce354bb/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:cb85b1d5702877c16f28d7b92ba030c1f49ebcc9b87d03d8c10bf45a2f1c7508", size = 3925668, upload-time = "2026-02-18T16:51:03.299Z" }, - { url = "https://files.pythonhosted.org/packages/f8/92/0aac830ed6a944fe334404e1687a074e4215630725753f0e3e9a9a595b62/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d4606c84d04b80f9138d72f1e28c6c02dc5ae0c7b8f3f8aaf89c681ce1cd1b1", size = 4234973, upload-time = "2026-02-18T16:51:09.097Z" }, - { url = "https://files.pythonhosted.org/packages/2e/96/102244653ee5a143ece5afe33f00f52fe64e389dfce8dbc87580c6d70d3d/psycopg_binary-3.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:74eae563166ebf74e8d950ff359be037b85723d99ca83f57d9b244a871d6c13b", size = 3551342, upload-time = "2026-02-18T16:51:13.892Z" }, - { url = "https://files.pythonhosted.org/packages/a2/71/7a57e5b12275fe7e7d84d54113f0226080423a869118419c9106c083a21c/psycopg_binary-3.3.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:497852c5eaf1f0c2d88ab74a64a8097c099deac0c71de1cbcf18659a8a04a4b2", size = 4607368, upload-time = "2026-02-18T16:51:19.295Z" }, - { url = "https://files.pythonhosted.org/packages/c7/04/cb834f120f2b2c10d4003515ef9ca9d688115b9431735e3936ae48549af8/psycopg_binary-3.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:258d1ea53464d29768bf25930f43291949f4c7becc706f6e220c515a63a24edd", size = 4687047, upload-time = "2026-02-18T16:51:23.84Z" }, - { url = "https://files.pythonhosted.org/packages/40/e9/47a69692d3da9704468041aa5ed3ad6fc7f6bb1a5ae788d261a26bbca6c7/psycopg_binary-3.3.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:111c59897a452196116db12e7f608da472fbff000693a21040e35fc978b23430", size = 5487096, upload-time = "2026-02-18T16:51:29.645Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b6/0e0dd6a2f802864a4ae3dbadf4ec620f05e3904c7842b326aafc43e5f464/psycopg_binary-3.3.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:17bb6600e2455993946385249a3c3d0af52cd70c1c1cdbf712e9d696d0b0bf1b", size = 5168720, upload-time = "2026-02-18T16:51:36.499Z" }, - { url = "https://files.pythonhosted.org/packages/6f/0d/977af38ac19a6b55d22dff508bd743fd7c1901e1b73657e7937c7cccb0a3/psycopg_binary-3.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:642050398583d61c9856210568eb09a8e4f2fe8224bf3be21b67a370e677eead", size = 6762076, upload-time = "2026-02-18T16:51:43.167Z" }, - { url = "https://files.pythonhosted.org/packages/34/40/912a39d48322cf86895c0eaf2d5b95cb899402443faefd4b09abbba6b6e1/psycopg_binary-3.3.3-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:533efe6dc3a7cba5e2a84e38970786bb966306863e45f3db152007e9f48638a6", size = 4997623, upload-time = "2026-02-18T16:51:47.707Z" }, - { url = "https://files.pythonhosted.org/packages/98/0c/c14d0e259c65dc7be854d926993f151077887391d5a081118907a9d89603/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5958dbf28b77ce2033482f6cb9ef04d43f5d8f4b7636e6963d5626f000efb23e", size = 4532096, upload-time = "2026-02-18T16:51:51.421Z" }, - { url = "https://files.pythonhosted.org/packages/39/21/8b7c50a194cfca6ea0fd4d1f276158307785775426e90700ab2eba5cd623/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a6af77b6626ce92b5817bf294b4d45ec1a6161dba80fc2d82cdffdd6814fd023", size = 4208884, upload-time = "2026-02-18T16:51:57.336Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2c/a4981bf42cf30ebba0424971d7ce70a222ae9b82594c42fc3f2105d7b525/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:47f06fcbe8542b4d96d7392c476a74ada521c5aebdb41c3c0155f6595fc14c8d", size = 3944542, upload-time = "2026-02-18T16:52:04.266Z" }, - { url = "https://files.pythonhosted.org/packages/60/e9/b7c29b56aa0b85a4e0c4d89db691c1ceef08f46a356369144430c155a2f5/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e7800e6c6b5dc4b0ca7cc7370f770f53ac83886b76afda0848065a674231e856", size = 4254339, upload-time = "2026-02-18T16:52:10.444Z" }, - { url = "https://files.pythonhosted.org/packages/98/5a/291d89f44d3820fffb7a04ebc8f3ef5dda4f542f44a5daea0c55a84abf45/psycopg_binary-3.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:165f22ab5a9513a3d7425ffb7fcc7955ed8ccaeef6d37e369d6cc1dff1582383", size = 3652796, upload-time = "2026-02-18T16:52:14.02Z" }, + { url = "https://files.pythonhosted.org/packages/90/15/021be5c0cbc5b7c1ab46e91cc3434eb42569f79a0592e67b8d25e66d844d/psycopg_binary-3.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6698dbab5bcef8fdb570fc9d35fd9ac52041771bfcfe6fd0fc5f5c4e36f1e99d", size = 4591170 }, + { url = "https://files.pythonhosted.org/packages/f1/54/a60211c346c9a2f8c6b272b5f2bbe21f6e11800ce7f61e99ba75cf8b63e1/psycopg_binary-3.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:329ff393441e75f10b673ae99ab45276887993d49e65f141da20d915c05aafd8", size = 4670009 }, + { url = "https://files.pythonhosted.org/packages/c1/53/ac7c18671347c553362aadbf65f92786eef9540676ca24114cc02f5be405/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:eb072949b8ebf4082ae24289a2b0fd724da9adc8f22743409d6fd718ddb379df", size = 5469735 }, + { url = "https://files.pythonhosted.org/packages/7f/c3/4f4e040902b82a344eff1c736cde2f2720f127fe939c7e7565706f96dd44/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:263a24f39f26e19ed7fc982d7859a36f17841b05bebad3eb47bb9cd2dd785351", size = 5152919 }, + { url = "https://files.pythonhosted.org/packages/0c/e7/d929679c6a5c212bcf738806c7c89f5b3d0919f2e1685a0e08d6ff877945/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5152d50798c2fa5bd9b68ec68eb68a1b71b95126c1d70adaa1a08cd5eefdc23d", size = 6738785 }, + { url = "https://files.pythonhosted.org/packages/69/b0/09703aeb69a9443d232d7b5318d58742e8ca51ff79f90ffe6b88f1db45e7/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d6a1e56dd267848edb824dbeb08cf5bac649e02ee0b03ba883ba3f4f0bd54f2", size = 4979008 }, + { url = "https://files.pythonhosted.org/packages/cc/a6/e662558b793c6e13a7473b970fee327d635270e41eded3090ef14045a6a5/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73eaaf4bb04709f545606c1db2f65f4000e8a04cdbf3e00d165a23004692093e", size = 4508255 }, + { url = "https://files.pythonhosted.org/packages/5f/7f/0f8b2e1d5e0093921b6f324a948a5c740c1447fbb45e97acaf50241d0f39/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:162e5675efb4704192411eaf8e00d07f7960b679cd3306e7efb120bb8d9456cc", size = 4189166 }, + { url = "https://files.pythonhosted.org/packages/92/ec/ce2e91c33bc8d10b00c87e2f6b0fb570641a6a60042d6a9ae35658a3a797/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:fab6b5e37715885c69f5d091f6ff229be71e235f272ebaa35158d5a46fd548a0", size = 3924544 }, + { url = "https://files.pythonhosted.org/packages/c5/2f/7718141485f73a924205af60041c392938852aa447a94c8cbd222ff389a1/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a4aab31bd6d1057f287c96c0effca3a25584eb9cc702f282ecb96ded7814e830", size = 4235297 }, + { url = "https://files.pythonhosted.org/packages/57/f9/1add717e2643a003bbde31b1b220172e64fbc0cb09f06429820c9173f7fc/psycopg_binary-3.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:59aa31fe11a0e1d1bcc2ce37ed35fe2ac84cd65bb9036d049b1a1c39064d0f14", size = 3547659 }, + { url = "https://files.pythonhosted.org/packages/03/0a/cac9fdf1df16a269ba0e5f0f06cac61f826c94cadb39df028cdfe19d3a33/psycopg_binary-3.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05f32239aec25c5fb15f7948cffdc2dc0dac098e48b80a140e4ba32b572a2e7d", size = 4590414 }, + { url = "https://files.pythonhosted.org/packages/9c/c0/d8f8508fbf440edbc0099b1abff33003cd80c9e66eb3a1e78834e3fb4fb9/psycopg_binary-3.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c84f9d214f2d1de2fafebc17fa68ac3f6561a59e291553dfc45ad299f4898c1", size = 4669021 }, + { url = "https://files.pythonhosted.org/packages/04/05/097016b77e343b4568feddf12c72171fc513acef9a4214d21b9478569068/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e77957d2ba17cada11be09a5066d93026cdb61ada7c8893101d7fe1c6e1f3925", size = 5467453 }, + { url = "https://files.pythonhosted.org/packages/91/23/73244e5feb55b5ca109cede6e97f32ef45189f0fdac4c80d75c99862729d/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:42961609ac07c232a427da7c87a468d3c82fee6762c220f38e37cfdacb2b178d", size = 5151135 }, + { url = "https://files.pythonhosted.org/packages/11/49/5309473b9803b207682095201d8708bbc7842ddf3f192488a69204e36455/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae07a3114313dd91fce686cab2f4c44af094398519af0e0f854bc707e1aeedf1", size = 6737315 }, + { url = "https://files.pythonhosted.org/packages/d4/5d/03abe74ef34d460b33c4d9662bf6ec1dd38888324323c1a1752133c10377/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d257c58d7b36a621dcce1d01476ad8b60f12d80eb1406aee4cf796f88b2ae482", size = 4979783 }, + { url = "https://files.pythonhosted.org/packages/f0/6c/3fbf8e604e15f2f3752900434046c00c90bb8764305a1b81112bff30ba24/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07c7211f9327d522c9c47560cae00a4ecf6687f4e02d779d035dd3177b41cb12", size = 4509023 }, + { url = "https://files.pythonhosted.org/packages/9c/6b/1a06b43b7c7af756c80b67eac8bfaa51d77e68635a8a8d246e4f0bb7604a/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8e7e9eca9b363dbedeceeadd8be97149d2499081f3c52d141d7cd1f395a91f83", size = 4185874 }, + { url = "https://files.pythonhosted.org/packages/2b/d3/bf49e3dcaadba510170c8d111e5e69e5ae3f981c1554c5bb71c75ce354bb/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:cb85b1d5702877c16f28d7b92ba030c1f49ebcc9b87d03d8c10bf45a2f1c7508", size = 3925668 }, + { url = "https://files.pythonhosted.org/packages/f8/92/0aac830ed6a944fe334404e1687a074e4215630725753f0e3e9a9a595b62/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d4606c84d04b80f9138d72f1e28c6c02dc5ae0c7b8f3f8aaf89c681ce1cd1b1", size = 4234973 }, + { url = "https://files.pythonhosted.org/packages/2e/96/102244653ee5a143ece5afe33f00f52fe64e389dfce8dbc87580c6d70d3d/psycopg_binary-3.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:74eae563166ebf74e8d950ff359be037b85723d99ca83f57d9b244a871d6c13b", size = 3551342 }, + { url = "https://files.pythonhosted.org/packages/a2/71/7a57e5b12275fe7e7d84d54113f0226080423a869118419c9106c083a21c/psycopg_binary-3.3.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:497852c5eaf1f0c2d88ab74a64a8097c099deac0c71de1cbcf18659a8a04a4b2", size = 4607368 }, + { url = "https://files.pythonhosted.org/packages/c7/04/cb834f120f2b2c10d4003515ef9ca9d688115b9431735e3936ae48549af8/psycopg_binary-3.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:258d1ea53464d29768bf25930f43291949f4c7becc706f6e220c515a63a24edd", size = 4687047 }, + { url = "https://files.pythonhosted.org/packages/40/e9/47a69692d3da9704468041aa5ed3ad6fc7f6bb1a5ae788d261a26bbca6c7/psycopg_binary-3.3.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:111c59897a452196116db12e7f608da472fbff000693a21040e35fc978b23430", size = 5487096 }, + { url = "https://files.pythonhosted.org/packages/0b/b6/0e0dd6a2f802864a4ae3dbadf4ec620f05e3904c7842b326aafc43e5f464/psycopg_binary-3.3.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:17bb6600e2455993946385249a3c3d0af52cd70c1c1cdbf712e9d696d0b0bf1b", size = 5168720 }, + { url = "https://files.pythonhosted.org/packages/6f/0d/977af38ac19a6b55d22dff508bd743fd7c1901e1b73657e7937c7cccb0a3/psycopg_binary-3.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:642050398583d61c9856210568eb09a8e4f2fe8224bf3be21b67a370e677eead", size = 6762076 }, + { url = "https://files.pythonhosted.org/packages/34/40/912a39d48322cf86895c0eaf2d5b95cb899402443faefd4b09abbba6b6e1/psycopg_binary-3.3.3-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:533efe6dc3a7cba5e2a84e38970786bb966306863e45f3db152007e9f48638a6", size = 4997623 }, + { url = "https://files.pythonhosted.org/packages/98/0c/c14d0e259c65dc7be854d926993f151077887391d5a081118907a9d89603/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5958dbf28b77ce2033482f6cb9ef04d43f5d8f4b7636e6963d5626f000efb23e", size = 4532096 }, + { url = "https://files.pythonhosted.org/packages/39/21/8b7c50a194cfca6ea0fd4d1f276158307785775426e90700ab2eba5cd623/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a6af77b6626ce92b5817bf294b4d45ec1a6161dba80fc2d82cdffdd6814fd023", size = 4208884 }, + { url = "https://files.pythonhosted.org/packages/c7/2c/a4981bf42cf30ebba0424971d7ce70a222ae9b82594c42fc3f2105d7b525/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:47f06fcbe8542b4d96d7392c476a74ada521c5aebdb41c3c0155f6595fc14c8d", size = 3944542 }, + { url = "https://files.pythonhosted.org/packages/60/e9/b7c29b56aa0b85a4e0c4d89db691c1ceef08f46a356369144430c155a2f5/psycopg_binary-3.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e7800e6c6b5dc4b0ca7cc7370f770f53ac83886b76afda0848065a674231e856", size = 4254339 }, + { url = "https://files.pythonhosted.org/packages/98/5a/291d89f44d3820fffb7a04ebc8f3ef5dda4f542f44a5daea0c55a84abf45/psycopg_binary-3.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:165f22ab5a9513a3d7425ffb7fcc7955ed8ccaeef6d37e369d6cc1dff1582383", size = 3652796 }, ] [[package]] @@ -6238,59 +6230,59 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/9a/9470d013d0d50af0da9c4251614aeb3c1823635cab3edc211e3839db0bcf/psycopg_pool-3.3.0.tar.gz", hash = "sha256:fa115eb2860bd88fce1717d75611f41490dec6135efb619611142b24da3f6db5", size = 31606, upload-time = "2025-12-01T11:34:33.11Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9a/9470d013d0d50af0da9c4251614aeb3c1823635cab3edc211e3839db0bcf/psycopg_pool-3.3.0.tar.gz", hash = "sha256:fa115eb2860bd88fce1717d75611f41490dec6135efb619611142b24da3f6db5", size = 31606 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/c3/26b8a0908a9db249de3b4169692e1c7c19048a9bc41a4d3209cee7dbb758/psycopg_pool-3.3.0-py3-none-any.whl", hash = "sha256:2e44329155c410b5e8666372db44276a8b1ebd8c90f1c3026ebba40d4bc81063", size = 39995, upload-time = "2025-12-01T11:34:29.761Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c3/26b8a0908a9db249de3b4169692e1c7c19048a9bc41a4d3209cee7dbb758/psycopg_pool-3.3.0-py3-none-any.whl", hash = "sha256:2e44329155c410b5e8666372db44276a8b1ebd8c90f1c3026ebba40d4bc81063", size = 39995 }, ] [[package]] name = "psycopg2-binary" version = "2.9.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba424643dc87348c6f1754dd9f48e80fdc1b9f7ca5c3a7c213/psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c", size = 379620, upload-time = "2025-10-10T11:14:48.041Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba424643dc87348c6f1754dd9f48e80fdc1b9f7ca5c3a7c213/psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c", size = 379620 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/91/f870a02f51be4a65987b45a7de4c2e1897dd0d01051e2b559a38fa634e3e/psycopg2_binary-2.9.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be9b840ac0525a283a96b556616f5b4820e0526addb8dcf6525a0fa162730be4", size = 3756603, upload-time = "2025-10-10T11:11:52.213Z" }, - { url = "https://files.pythonhosted.org/packages/27/fa/cae40e06849b6c9a95eb5c04d419942f00d9eaac8d81626107461e268821/psycopg2_binary-2.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f090b7ddd13ca842ebfe301cd587a76a4cf0913b1e429eb92c1be5dbeb1a19bc", size = 3864509, upload-time = "2025-10-10T11:11:56.452Z" }, - { url = "https://files.pythonhosted.org/packages/2d/75/364847b879eb630b3ac8293798e380e441a957c53657995053c5ec39a316/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a", size = 4411159, upload-time = "2025-10-10T11:12:00.49Z" }, - { url = "https://files.pythonhosted.org/packages/6f/a0/567f7ea38b6e1c62aafd58375665a547c00c608a471620c0edc364733e13/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e", size = 4468234, upload-time = "2025-10-10T11:12:04.892Z" }, - { url = "https://files.pythonhosted.org/packages/30/da/4e42788fb811bbbfd7b7f045570c062f49e350e1d1f3df056c3fb5763353/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db", size = 4166236, upload-time = "2025-10-10T11:12:11.674Z" }, - { url = "https://files.pythonhosted.org/packages/3c/94/c1777c355bc560992af848d98216148be5f1be001af06e06fc49cbded578/psycopg2_binary-2.9.11-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1cf393f1cdaf6a9b57c0a719a1068ba1069f022a59b8b1fe44b006745b59757", size = 3983083, upload-time = "2025-10-30T02:55:15.73Z" }, - { url = "https://files.pythonhosted.org/packages/bd/42/c9a21edf0e3daa7825ed04a4a8588686c6c14904344344a039556d78aa58/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3", size = 3652281, upload-time = "2025-10-10T11:12:17.713Z" }, - { url = "https://files.pythonhosted.org/packages/12/22/dedfbcfa97917982301496b6b5e5e6c5531d1f35dd2b488b08d1ebc52482/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a", size = 3298010, upload-time = "2025-10-10T11:12:22.671Z" }, - { url = "https://files.pythonhosted.org/packages/66/ea/d3390e6696276078bd01b2ece417deac954dfdd552d2edc3d03204416c0c/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:edcb3aeb11cb4bf13a2af3c53a15b3d612edeb6409047ea0b5d6a21a9d744b34", size = 3044641, upload-time = "2025-10-30T02:55:19.929Z" }, - { url = "https://files.pythonhosted.org/packages/12/9a/0402ded6cbd321da0c0ba7d34dc12b29b14f5764c2fc10750daa38e825fc/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d", size = 3347940, upload-time = "2025-10-10T11:12:26.529Z" }, - { url = "https://files.pythonhosted.org/packages/b1/d2/99b55e85832ccde77b211738ff3925a5d73ad183c0b37bcbbe5a8ff04978/psycopg2_binary-2.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:b33fabeb1fde21180479b2d4667e994de7bbf0eec22832ba5d9b5e4cf65b6c6d", size = 2714147, upload-time = "2025-10-10T11:12:29.535Z" }, - { url = "https://files.pythonhosted.org/packages/ff/a8/a2709681b3ac11b0b1786def10006b8995125ba268c9a54bea6f5ae8bd3e/psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c", size = 3756572, upload-time = "2025-10-10T11:12:32.873Z" }, - { url = "https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee", size = 3864529, upload-time = "2025-10-10T11:12:36.791Z" }, - { url = "https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0", size = 4411242, upload-time = "2025-10-10T11:12:42.388Z" }, - { url = "https://files.pythonhosted.org/packages/10/04/6ca7477e6160ae258dc96f67c371157776564679aefd247b66f4661501a2/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766", size = 4468258, upload-time = "2025-10-10T11:12:48.654Z" }, - { url = "https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3", size = 4166295, upload-time = "2025-10-10T11:12:52.525Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7d/c07374c501b45f3579a9eb761cbf2604ddef3d96ad48679112c2c5aa9c25/psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f", size = 3983133, upload-time = "2025-10-30T02:55:24.329Z" }, - { url = "https://files.pythonhosted.org/packages/82/56/993b7104cb8345ad7d4516538ccf8f0d0ac640b1ebd8c754a7b024e76878/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4", size = 3652383, upload-time = "2025-10-10T11:12:56.387Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ac/eaeb6029362fd8d454a27374d84c6866c82c33bfc24587b4face5a8e43ef/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c", size = 3298168, upload-time = "2025-10-10T11:13:00.403Z" }, - { url = "https://files.pythonhosted.org/packages/2b/39/50c3facc66bded9ada5cbc0de867499a703dc6bca6be03070b4e3b65da6c/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60", size = 3044712, upload-time = "2025-10-30T02:55:27.975Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8e/b7de019a1f562f72ada81081a12823d3c1590bedc48d7d2559410a2763fe/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1", size = 3347549, upload-time = "2025-10-10T11:13:03.971Z" }, - { url = "https://files.pythonhosted.org/packages/80/2d/1bb683f64737bbb1f86c82b7359db1eb2be4e2c0c13b947f80efefa7d3e5/psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa", size = 2714215, upload-time = "2025-10-10T11:13:07.14Z" }, - { url = "https://files.pythonhosted.org/packages/64/12/93ef0098590cf51d9732b4f139533732565704f45bdc1ffa741b7c95fb54/psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1", size = 3756567, upload-time = "2025-10-10T11:13:11.885Z" }, - { url = "https://files.pythonhosted.org/packages/7c/a9/9d55c614a891288f15ca4b5209b09f0f01e3124056924e17b81b9fa054cc/psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f", size = 3864755, upload-time = "2025-10-10T11:13:17.727Z" }, - { url = "https://files.pythonhosted.org/packages/13/1e/98874ce72fd29cbde93209977b196a2edae03f8490d1bd8158e7f1daf3a0/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5", size = 4411646, upload-time = "2025-10-10T11:13:24.432Z" }, - { url = "https://files.pythonhosted.org/packages/5a/bd/a335ce6645334fb8d758cc358810defca14a1d19ffbc8a10bd38a2328565/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8", size = 4468701, upload-time = "2025-10-10T11:13:29.266Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/c8b4f53f34e295e45709b7568bf9b9407a612ea30387d35eb9fa84f269b4/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c", size = 4166293, upload-time = "2025-10-10T11:13:33.336Z" }, - { url = "https://files.pythonhosted.org/packages/4b/e0/f8cc36eadd1b716ab36bb290618a3292e009867e5c97ce4aba908cb99644/psycopg2_binary-2.9.11-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e35b7abae2b0adab776add56111df1735ccc71406e56203515e228a8dc07089f", size = 3983184, upload-time = "2025-10-30T02:55:32.483Z" }, - { url = "https://files.pythonhosted.org/packages/53/3e/2a8fe18a4e61cfb3417da67b6318e12691772c0696d79434184a511906dc/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747", size = 3652650, upload-time = "2025-10-10T11:13:38.181Z" }, - { url = "https://files.pythonhosted.org/packages/76/36/03801461b31b29fe58d228c24388f999fe814dfc302856e0d17f97d7c54d/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f", size = 3298663, upload-time = "2025-10-10T11:13:44.878Z" }, - { url = "https://files.pythonhosted.org/packages/97/77/21b0ea2e1a73aa5fa9222b2a6b8ba325c43c3a8d54272839c991f2345656/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:32770a4d666fbdafab017086655bcddab791d7cb260a16679cc5a7338b64343b", size = 3044737, upload-time = "2025-10-30T02:55:35.69Z" }, - { url = "https://files.pythonhosted.org/packages/67/69/f36abe5f118c1dca6d3726ceae164b9356985805480731ac6712a63f24f0/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d", size = 3347643, upload-time = "2025-10-10T11:13:53.499Z" }, - { url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913, upload-time = "2025-10-10T11:13:57.058Z" }, + { url = "https://files.pythonhosted.org/packages/d8/91/f870a02f51be4a65987b45a7de4c2e1897dd0d01051e2b559a38fa634e3e/psycopg2_binary-2.9.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be9b840ac0525a283a96b556616f5b4820e0526addb8dcf6525a0fa162730be4", size = 3756603 }, + { url = "https://files.pythonhosted.org/packages/27/fa/cae40e06849b6c9a95eb5c04d419942f00d9eaac8d81626107461e268821/psycopg2_binary-2.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f090b7ddd13ca842ebfe301cd587a76a4cf0913b1e429eb92c1be5dbeb1a19bc", size = 3864509 }, + { url = "https://files.pythonhosted.org/packages/2d/75/364847b879eb630b3ac8293798e380e441a957c53657995053c5ec39a316/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a", size = 4411159 }, + { url = "https://files.pythonhosted.org/packages/6f/a0/567f7ea38b6e1c62aafd58375665a547c00c608a471620c0edc364733e13/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e", size = 4468234 }, + { url = "https://files.pythonhosted.org/packages/30/da/4e42788fb811bbbfd7b7f045570c062f49e350e1d1f3df056c3fb5763353/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db", size = 4166236 }, + { url = "https://files.pythonhosted.org/packages/3c/94/c1777c355bc560992af848d98216148be5f1be001af06e06fc49cbded578/psycopg2_binary-2.9.11-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1cf393f1cdaf6a9b57c0a719a1068ba1069f022a59b8b1fe44b006745b59757", size = 3983083 }, + { url = "https://files.pythonhosted.org/packages/bd/42/c9a21edf0e3daa7825ed04a4a8588686c6c14904344344a039556d78aa58/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3", size = 3652281 }, + { url = "https://files.pythonhosted.org/packages/12/22/dedfbcfa97917982301496b6b5e5e6c5531d1f35dd2b488b08d1ebc52482/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a", size = 3298010 }, + { url = "https://files.pythonhosted.org/packages/66/ea/d3390e6696276078bd01b2ece417deac954dfdd552d2edc3d03204416c0c/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:edcb3aeb11cb4bf13a2af3c53a15b3d612edeb6409047ea0b5d6a21a9d744b34", size = 3044641 }, + { url = "https://files.pythonhosted.org/packages/12/9a/0402ded6cbd321da0c0ba7d34dc12b29b14f5764c2fc10750daa38e825fc/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d", size = 3347940 }, + { url = "https://files.pythonhosted.org/packages/b1/d2/99b55e85832ccde77b211738ff3925a5d73ad183c0b37bcbbe5a8ff04978/psycopg2_binary-2.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:b33fabeb1fde21180479b2d4667e994de7bbf0eec22832ba5d9b5e4cf65b6c6d", size = 2714147 }, + { url = "https://files.pythonhosted.org/packages/ff/a8/a2709681b3ac11b0b1786def10006b8995125ba268c9a54bea6f5ae8bd3e/psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c", size = 3756572 }, + { url = "https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee", size = 3864529 }, + { url = "https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0", size = 4411242 }, + { url = "https://files.pythonhosted.org/packages/10/04/6ca7477e6160ae258dc96f67c371157776564679aefd247b66f4661501a2/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766", size = 4468258 }, + { url = "https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3", size = 4166295 }, + { url = "https://files.pythonhosted.org/packages/f2/7d/c07374c501b45f3579a9eb761cbf2604ddef3d96ad48679112c2c5aa9c25/psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f", size = 3983133 }, + { url = "https://files.pythonhosted.org/packages/82/56/993b7104cb8345ad7d4516538ccf8f0d0ac640b1ebd8c754a7b024e76878/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4", size = 3652383 }, + { url = "https://files.pythonhosted.org/packages/2d/ac/eaeb6029362fd8d454a27374d84c6866c82c33bfc24587b4face5a8e43ef/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c", size = 3298168 }, + { url = "https://files.pythonhosted.org/packages/2b/39/50c3facc66bded9ada5cbc0de867499a703dc6bca6be03070b4e3b65da6c/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60", size = 3044712 }, + { url = "https://files.pythonhosted.org/packages/9c/8e/b7de019a1f562f72ada81081a12823d3c1590bedc48d7d2559410a2763fe/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1", size = 3347549 }, + { url = "https://files.pythonhosted.org/packages/80/2d/1bb683f64737bbb1f86c82b7359db1eb2be4e2c0c13b947f80efefa7d3e5/psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa", size = 2714215 }, + { url = "https://files.pythonhosted.org/packages/64/12/93ef0098590cf51d9732b4f139533732565704f45bdc1ffa741b7c95fb54/psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1", size = 3756567 }, + { url = "https://files.pythonhosted.org/packages/7c/a9/9d55c614a891288f15ca4b5209b09f0f01e3124056924e17b81b9fa054cc/psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f", size = 3864755 }, + { url = "https://files.pythonhosted.org/packages/13/1e/98874ce72fd29cbde93209977b196a2edae03f8490d1bd8158e7f1daf3a0/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5", size = 4411646 }, + { url = "https://files.pythonhosted.org/packages/5a/bd/a335ce6645334fb8d758cc358810defca14a1d19ffbc8a10bd38a2328565/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8", size = 4468701 }, + { url = "https://files.pythonhosted.org/packages/44/d6/c8b4f53f34e295e45709b7568bf9b9407a612ea30387d35eb9fa84f269b4/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c", size = 4166293 }, + { url = "https://files.pythonhosted.org/packages/4b/e0/f8cc36eadd1b716ab36bb290618a3292e009867e5c97ce4aba908cb99644/psycopg2_binary-2.9.11-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e35b7abae2b0adab776add56111df1735ccc71406e56203515e228a8dc07089f", size = 3983184 }, + { url = "https://files.pythonhosted.org/packages/53/3e/2a8fe18a4e61cfb3417da67b6318e12691772c0696d79434184a511906dc/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747", size = 3652650 }, + { url = "https://files.pythonhosted.org/packages/76/36/03801461b31b29fe58d228c24388f999fe814dfc302856e0d17f97d7c54d/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f", size = 3298663 }, + { url = "https://files.pythonhosted.org/packages/97/77/21b0ea2e1a73aa5fa9222b2a6b8ba325c43c3a8d54272839c991f2345656/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:32770a4d666fbdafab017086655bcddab791d7cb260a16679cc5a7338b64343b", size = 3044737 }, + { url = "https://files.pythonhosted.org/packages/67/69/f36abe5f118c1dca6d3726ceae164b9356985805480731ac6712a63f24f0/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d", size = 3347643 }, + { url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913 }, ] [[package]] name = "pwdlib" version = "0.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5f/41/a7c0d8a003c36ce3828ae3ed0391fe6a15aad65f082dbd6bec817ea95c0b/pwdlib-0.3.0.tar.gz", hash = "sha256:6ca30f9642a1467d4f5d0a4d18619de1c77f17dfccb42dd200b144127d3c83fc", size = 215810, upload-time = "2025-10-25T12:44:24.395Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/41/a7c0d8a003c36ce3828ae3ed0391fe6a15aad65f082dbd6bec817ea95c0b/pwdlib-0.3.0.tar.gz", hash = "sha256:6ca30f9642a1467d4f5d0a4d18619de1c77f17dfccb42dd200b144127d3c83fc", size = 215810 } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/0c/9086a357d02a050fbb3270bf5043ac284dbfb845670e16c9389a41defc9e/pwdlib-0.3.0-py3-none-any.whl", hash = "sha256:f86c15c138858c09f3bba0a10984d4f9178158c55deaa72eac0210849b1a140d", size = 8633, upload-time = "2025-10-25T12:44:23.406Z" }, + { url = "https://files.pythonhosted.org/packages/62/0c/9086a357d02a050fbb3270bf5043ac284dbfb845670e16c9389a41defc9e/pwdlib-0.3.0-py3-none-any.whl", hash = "sha256:f86c15c138858c09f3bba0a10984d4f9178158c55deaa72eac0210849b1a140d", size = 8633 }, ] [package.optional-dependencies] @@ -6305,37 +6297,37 @@ bcrypt = [ name = "pyarrow" version = "18.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7f/7b/640785a9062bb00314caa8a387abce547d2a420cf09bd6c715fe659ccffb/pyarrow-18.1.0.tar.gz", hash = "sha256:9386d3ca9c145b5539a1cfc75df07757dff870168c959b473a0bccbc3abc8c73", size = 1118671, upload-time = "2024-11-26T02:01:48.62Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/7b/640785a9062bb00314caa8a387abce547d2a420cf09bd6c715fe659ccffb/pyarrow-18.1.0.tar.gz", hash = "sha256:9386d3ca9c145b5539a1cfc75df07757dff870168c959b473a0bccbc3abc8c73", size = 1118671 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/50/12829e7111b932581e51dda51d5cb39207a056c30fe31ef43f14c63c4d7e/pyarrow-18.1.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f3a76670b263dc41d0ae877f09124ab96ce10e4e48f3e3e4257273cee61ad0d", size = 29514620, upload-time = "2024-11-26T01:59:39.797Z" }, - { url = "https://files.pythonhosted.org/packages/d1/41/468c944eab157702e96abab3d07b48b8424927d4933541ab43788bb6964d/pyarrow-18.1.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:da31fbca07c435be88a0c321402c4e31a2ba61593ec7473630769de8346b54ee", size = 30856494, upload-time = "2024-11-26T01:59:44.725Z" }, - { url = "https://files.pythonhosted.org/packages/68/f9/29fb659b390312a7345aeb858a9d9c157552a8852522f2c8bad437c29c0a/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543ad8459bc438efc46d29a759e1079436290bd583141384c6f7a1068ed6f992", size = 39203624, upload-time = "2024-11-26T01:59:49.189Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f6/19360dae44200e35753c5c2889dc478154cd78e61b1f738514c9f131734d/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0743e503c55be0fdb5c08e7d44853da27f19dc854531c0570f9f394ec9671d54", size = 40139341, upload-time = "2024-11-26T01:59:54.849Z" }, - { url = "https://files.pythonhosted.org/packages/bb/e6/9b3afbbcf10cc724312e824af94a2e993d8ace22994d823f5c35324cebf5/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d4b3d2a34780645bed6414e22dda55a92e0fcd1b8a637fba86800ad737057e33", size = 38618629, upload-time = "2024-11-26T01:59:59.966Z" }, - { url = "https://files.pythonhosted.org/packages/3a/2e/3b99f8a3d9e0ccae0e961978a0d0089b25fb46ebbcfb5ebae3cca179a5b3/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c52f81aa6f6575058d8e2c782bf79d4f9fdc89887f16825ec3a66607a5dd8e30", size = 40078661, upload-time = "2024-11-26T02:00:04.55Z" }, - { url = "https://files.pythonhosted.org/packages/76/52/f8da04195000099d394012b8d42c503d7041b79f778d854f410e5f05049a/pyarrow-18.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ad4892617e1a6c7a551cfc827e072a633eaff758fa09f21c4ee548c30bcaf99", size = 25092330, upload-time = "2024-11-26T02:00:09.576Z" }, - { url = "https://files.pythonhosted.org/packages/cb/87/aa4d249732edef6ad88899399047d7e49311a55749d3c373007d034ee471/pyarrow-18.1.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:84e314d22231357d473eabec709d0ba285fa706a72377f9cc8e1cb3c8013813b", size = 29497406, upload-time = "2024-11-26T02:00:14.469Z" }, - { url = "https://files.pythonhosted.org/packages/3c/c7/ed6adb46d93a3177540e228b5ca30d99fc8ea3b13bdb88b6f8b6467e2cb7/pyarrow-18.1.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f591704ac05dfd0477bb8f8e0bd4b5dc52c1cadf50503858dce3a15db6e46ff2", size = 30835095, upload-time = "2024-11-26T02:00:19.347Z" }, - { url = "https://files.pythonhosted.org/packages/41/d7/ed85001edfb96200ff606943cff71d64f91926ab42828676c0fc0db98963/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acb7564204d3c40babf93a05624fc6a8ec1ab1def295c363afc40b0c9e66c191", size = 39194527, upload-time = "2024-11-26T02:00:24.085Z" }, - { url = "https://files.pythonhosted.org/packages/59/16/35e28eab126342fa391593415d79477e89582de411bb95232f28b131a769/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74de649d1d2ccb778f7c3afff6085bd5092aed4c23df9feeb45dd6b16f3811aa", size = 40131443, upload-time = "2024-11-26T02:00:29.483Z" }, - { url = "https://files.pythonhosted.org/packages/0c/95/e855880614c8da20f4cd74fa85d7268c725cf0013dc754048593a38896a0/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f96bd502cb11abb08efea6dab09c003305161cb6c9eafd432e35e76e7fa9b90c", size = 38608750, upload-time = "2024-11-26T02:00:34.069Z" }, - { url = "https://files.pythonhosted.org/packages/54/9d/f253554b1457d4fdb3831b7bd5f8f00f1795585a606eabf6fec0a58a9c38/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:36ac22d7782554754a3b50201b607d553a8d71b78cdf03b33c1125be4b52397c", size = 40066690, upload-time = "2024-11-26T02:00:39.603Z" }, - { url = "https://files.pythonhosted.org/packages/2f/58/8912a2563e6b8273e8aa7b605a345bba5a06204549826f6493065575ebc0/pyarrow-18.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:25dbacab8c5952df0ca6ca0af28f50d45bd31c1ff6fcf79e2d120b4a65ee7181", size = 25081054, upload-time = "2024-11-26T02:00:43.611Z" }, - { url = "https://files.pythonhosted.org/packages/82/f9/d06ddc06cab1ada0c2f2fd205ac8c25c2701182de1b9c4bf7a0a44844431/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a276190309aba7bc9d5bd2933230458b3521a4317acfefe69a354f2fe59f2bc", size = 29525542, upload-time = "2024-11-26T02:00:48.094Z" }, - { url = "https://files.pythonhosted.org/packages/ab/94/8917e3b961810587ecbdaa417f8ebac0abb25105ae667b7aa11c05876976/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ad514dbfcffe30124ce655d72771ae070f30bf850b48bc4d9d3b25993ee0e386", size = 30829412, upload-time = "2024-11-26T02:00:52.458Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e3/3b16c3190f3d71d3b10f6758d2d5f7779ef008c4fd367cedab3ed178a9f7/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aebc13a11ed3032d8dd6e7171eb6e86d40d67a5639d96c35142bd568b9299324", size = 39119106, upload-time = "2024-11-26T02:00:57.219Z" }, - { url = "https://files.pythonhosted.org/packages/1d/d6/5d704b0d25c3c79532f8c0639f253ec2803b897100f64bcb3f53ced236e5/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6cf5c05f3cee251d80e98726b5c7cc9f21bab9e9783673bac58e6dfab57ecc8", size = 40090940, upload-time = "2024-11-26T02:01:02.31Z" }, - { url = "https://files.pythonhosted.org/packages/37/29/366bc7e588220d74ec00e497ac6710c2833c9176f0372fe0286929b2d64c/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:11b676cd410cf162d3f6a70b43fb9e1e40affbc542a1e9ed3681895f2962d3d9", size = 38548177, upload-time = "2024-11-26T02:01:07.371Z" }, - { url = "https://files.pythonhosted.org/packages/c8/11/fabf6ecabb1fe5b7d96889228ca2a9158c4c3bb732e3b8ee3f7f6d40b703/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b76130d835261b38f14fc41fdfb39ad8d672afb84c447126b84d5472244cfaba", size = 40043567, upload-time = "2024-11-26T02:01:12.931Z" }, + { url = "https://files.pythonhosted.org/packages/6a/50/12829e7111b932581e51dda51d5cb39207a056c30fe31ef43f14c63c4d7e/pyarrow-18.1.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f3a76670b263dc41d0ae877f09124ab96ce10e4e48f3e3e4257273cee61ad0d", size = 29514620 }, + { url = "https://files.pythonhosted.org/packages/d1/41/468c944eab157702e96abab3d07b48b8424927d4933541ab43788bb6964d/pyarrow-18.1.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:da31fbca07c435be88a0c321402c4e31a2ba61593ec7473630769de8346b54ee", size = 30856494 }, + { url = "https://files.pythonhosted.org/packages/68/f9/29fb659b390312a7345aeb858a9d9c157552a8852522f2c8bad437c29c0a/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543ad8459bc438efc46d29a759e1079436290bd583141384c6f7a1068ed6f992", size = 39203624 }, + { url = "https://files.pythonhosted.org/packages/6e/f6/19360dae44200e35753c5c2889dc478154cd78e61b1f738514c9f131734d/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0743e503c55be0fdb5c08e7d44853da27f19dc854531c0570f9f394ec9671d54", size = 40139341 }, + { url = "https://files.pythonhosted.org/packages/bb/e6/9b3afbbcf10cc724312e824af94a2e993d8ace22994d823f5c35324cebf5/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d4b3d2a34780645bed6414e22dda55a92e0fcd1b8a637fba86800ad737057e33", size = 38618629 }, + { url = "https://files.pythonhosted.org/packages/3a/2e/3b99f8a3d9e0ccae0e961978a0d0089b25fb46ebbcfb5ebae3cca179a5b3/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c52f81aa6f6575058d8e2c782bf79d4f9fdc89887f16825ec3a66607a5dd8e30", size = 40078661 }, + { url = "https://files.pythonhosted.org/packages/76/52/f8da04195000099d394012b8d42c503d7041b79f778d854f410e5f05049a/pyarrow-18.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ad4892617e1a6c7a551cfc827e072a633eaff758fa09f21c4ee548c30bcaf99", size = 25092330 }, + { url = "https://files.pythonhosted.org/packages/cb/87/aa4d249732edef6ad88899399047d7e49311a55749d3c373007d034ee471/pyarrow-18.1.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:84e314d22231357d473eabec709d0ba285fa706a72377f9cc8e1cb3c8013813b", size = 29497406 }, + { url = "https://files.pythonhosted.org/packages/3c/c7/ed6adb46d93a3177540e228b5ca30d99fc8ea3b13bdb88b6f8b6467e2cb7/pyarrow-18.1.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f591704ac05dfd0477bb8f8e0bd4b5dc52c1cadf50503858dce3a15db6e46ff2", size = 30835095 }, + { url = "https://files.pythonhosted.org/packages/41/d7/ed85001edfb96200ff606943cff71d64f91926ab42828676c0fc0db98963/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acb7564204d3c40babf93a05624fc6a8ec1ab1def295c363afc40b0c9e66c191", size = 39194527 }, + { url = "https://files.pythonhosted.org/packages/59/16/35e28eab126342fa391593415d79477e89582de411bb95232f28b131a769/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74de649d1d2ccb778f7c3afff6085bd5092aed4c23df9feeb45dd6b16f3811aa", size = 40131443 }, + { url = "https://files.pythonhosted.org/packages/0c/95/e855880614c8da20f4cd74fa85d7268c725cf0013dc754048593a38896a0/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f96bd502cb11abb08efea6dab09c003305161cb6c9eafd432e35e76e7fa9b90c", size = 38608750 }, + { url = "https://files.pythonhosted.org/packages/54/9d/f253554b1457d4fdb3831b7bd5f8f00f1795585a606eabf6fec0a58a9c38/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:36ac22d7782554754a3b50201b607d553a8d71b78cdf03b33c1125be4b52397c", size = 40066690 }, + { url = "https://files.pythonhosted.org/packages/2f/58/8912a2563e6b8273e8aa7b605a345bba5a06204549826f6493065575ebc0/pyarrow-18.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:25dbacab8c5952df0ca6ca0af28f50d45bd31c1ff6fcf79e2d120b4a65ee7181", size = 25081054 }, + { url = "https://files.pythonhosted.org/packages/82/f9/d06ddc06cab1ada0c2f2fd205ac8c25c2701182de1b9c4bf7a0a44844431/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a276190309aba7bc9d5bd2933230458b3521a4317acfefe69a354f2fe59f2bc", size = 29525542 }, + { url = "https://files.pythonhosted.org/packages/ab/94/8917e3b961810587ecbdaa417f8ebac0abb25105ae667b7aa11c05876976/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ad514dbfcffe30124ce655d72771ae070f30bf850b48bc4d9d3b25993ee0e386", size = 30829412 }, + { url = "https://files.pythonhosted.org/packages/5e/e3/3b16c3190f3d71d3b10f6758d2d5f7779ef008c4fd367cedab3ed178a9f7/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aebc13a11ed3032d8dd6e7171eb6e86d40d67a5639d96c35142bd568b9299324", size = 39119106 }, + { url = "https://files.pythonhosted.org/packages/1d/d6/5d704b0d25c3c79532f8c0639f253ec2803b897100f64bcb3f53ced236e5/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6cf5c05f3cee251d80e98726b5c7cc9f21bab9e9783673bac58e6dfab57ecc8", size = 40090940 }, + { url = "https://files.pythonhosted.org/packages/37/29/366bc7e588220d74ec00e497ac6710c2833c9176f0372fe0286929b2d64c/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:11b676cd410cf162d3f6a70b43fb9e1e40affbc542a1e9ed3681895f2962d3d9", size = 38548177 }, + { url = "https://files.pythonhosted.org/packages/c8/11/fabf6ecabb1fe5b7d96889228ca2a9158c4c3bb732e3b8ee3f7f6d40b703/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b76130d835261b38f14fc41fdfb39ad8d672afb84c447126b84d5472244cfaba", size = 40043567 }, ] [[package]] name = "pyasn1" version = "0.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997 }, ] [[package]] @@ -6345,166 +6337,161 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892 } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259 }, ] [[package]] name = "pybase64" version = "1.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/b8/4ed5c7ad5ec15b08d35cc79ace6145d5c1ae426e46435f4987379439dfea/pybase64-1.4.3.tar.gz", hash = "sha256:c2ed274c9e0ba9c8f9c4083cfe265e66dd679126cd9c2027965d807352f3f053", size = 137272, upload-time = "2025-12-06T13:27:04.013Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/b8/4ed5c7ad5ec15b08d35cc79ace6145d5c1ae426e46435f4987379439dfea/pybase64-1.4.3.tar.gz", hash = "sha256:c2ed274c9e0ba9c8f9c4083cfe265e66dd679126cd9c2027965d807352f3f053", size = 137272 } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/a7/efcaa564f091a2af7f18a83c1c4875b1437db56ba39540451dc85d56f653/pybase64-1.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:18d85e5ab8b986bb32d8446aca6258ed80d1bafe3603c437690b352c648f5967", size = 38167, upload-time = "2025-12-06T13:23:16.821Z" }, - { url = "https://files.pythonhosted.org/packages/db/c7/c7ad35adff2d272bf2930132db2b3eea8c44bb1b1f64eb9b2b8e57cde7b4/pybase64-1.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f5791a3491d116d0deaf4d83268f48792998519698f8751efb191eac84320e9", size = 31673, upload-time = "2025-12-06T13:23:17.835Z" }, - { url = "https://files.pythonhosted.org/packages/43/1b/9a8cab0042b464e9a876d5c65fe5127445a2436da36fda64899b119b1a1b/pybase64-1.4.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f0b3f200c3e06316f6bebabd458b4e4bcd4c2ca26af7c0c766614d91968dee27", size = 68210, upload-time = "2025-12-06T13:23:18.813Z" }, - { url = "https://files.pythonhosted.org/packages/62/f7/965b79ff391ad208b50e412b5d3205ccce372a2d27b7218ae86d5295b105/pybase64-1.4.3-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb632edfd132b3eaf90c39c89aa314beec4e946e210099b57d40311f704e11d4", size = 71599, upload-time = "2025-12-06T13:23:20.195Z" }, - { url = "https://files.pythonhosted.org/packages/03/4b/a3b5175130b3810bbb8ccfa1edaadbd3afddb9992d877c8a1e2f274b476e/pybase64-1.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:356ef1d74648ce997f5a777cf8f1aefecc1c0b4fe6201e0ef3ec8a08170e1b54", size = 59922, upload-time = "2025-12-06T13:23:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/da/5d/c38d1572027fc601b62d7a407721688b04b4d065d60ca489912d6893e6cf/pybase64-1.4.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:c48361f90db32bacaa5518419d4eb9066ba558013aaf0c7781620279ecddaeb9", size = 56712, upload-time = "2025-12-06T13:23:22.77Z" }, - { url = "https://files.pythonhosted.org/packages/e7/d4/4e04472fef485caa8f561d904d4d69210a8f8fc1608ea15ebd9012b92655/pybase64-1.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:702bcaa16ae02139d881aeaef5b1c8ffb4a3fae062fe601d1e3835e10310a517", size = 59300, upload-time = "2025-12-06T13:23:24.543Z" }, - { url = "https://files.pythonhosted.org/packages/86/e7/16e29721b86734b881d09b7e23dfd7c8408ad01a4f4c7525f3b1088e25ec/pybase64-1.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:53d0ffe1847b16b647c6413d34d1de08942b7724273dd57e67dcbdb10c574045", size = 60278, upload-time = "2025-12-06T13:23:25.608Z" }, - { url = "https://files.pythonhosted.org/packages/b1/02/18515f211d7c046be32070709a8efeeef8a0203de4fd7521e6b56404731b/pybase64-1.4.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9a1792e8b830a92736dae58f0c386062eb038dfe8004fb03ba33b6083d89cd43", size = 54817, upload-time = "2025-12-06T13:23:26.633Z" }, - { url = "https://files.pythonhosted.org/packages/e7/be/14e29d8e1a481dbff151324c96dd7b5d2688194bb65dc8a00ca0e1ad1e86/pybase64-1.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d468b1b1ac5ad84875a46eaa458663c3721e8be5f155ade356406848d3701f6", size = 58611, upload-time = "2025-12-06T13:23:27.684Z" }, - { url = "https://files.pythonhosted.org/packages/b4/8a/a2588dfe24e1bbd742a554553778ab0d65fdf3d1c9a06d10b77047d142aa/pybase64-1.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e97b7bdbd62e71898cd542a6a9e320d9da754ff3ebd02cb802d69087ee94d468", size = 52404, upload-time = "2025-12-06T13:23:28.714Z" }, - { url = "https://files.pythonhosted.org/packages/27/fc/afcda7445bebe0cbc38cafdd7813234cdd4fc5573ff067f1abf317bb0cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b33aeaa780caaa08ffda87fc584d5eab61e3d3bbb5d86ead02161dc0c20d04bc", size = 68817, upload-time = "2025-12-06T13:23:30.079Z" }, - { url = "https://files.pythonhosted.org/packages/d3/3a/87c3201e555ed71f73e961a787241a2438c2bbb2ca8809c29ddf938a3157/pybase64-1.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c0efcf78f11cf866bed49caa7b97552bc4855a892f9cc2372abcd3ed0056f0d", size = 57854, upload-time = "2025-12-06T13:23:31.17Z" }, - { url = "https://files.pythonhosted.org/packages/fd/7d/931c2539b31a7b375e7d595b88401eeb5bd6c5ce1059c9123f9b608aaa14/pybase64-1.4.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:66e3791f2ed725a46593f8bd2761ff37d01e2cdad065b1dceb89066f476e50c6", size = 54333, upload-time = "2025-12-06T13:23:32.422Z" }, - { url = "https://files.pythonhosted.org/packages/de/5e/537601e02cc01f27e9d75f440f1a6095b8df44fc28b1eef2cd739aea8cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:72bb0b6bddadab26e1b069bb78e83092711a111a80a0d6b9edcb08199ad7299b", size = 56492, upload-time = "2025-12-06T13:23:33.515Z" }, - { url = "https://files.pythonhosted.org/packages/96/97/2a2e57acf8f5c9258d22aba52e71f8050e167b29ed2ee1113677c1b600c1/pybase64-1.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5b3365dbcbcdb0a294f0f50af0c0a16b27a232eddeeb0bceeefd844ef30d2a23", size = 70974, upload-time = "2025-12-06T13:23:36.27Z" }, - { url = "https://files.pythonhosted.org/packages/75/2e/a9e28941c6dab6f06e6d3f6783d3373044be9b0f9a9d3492c3d8d2260ac0/pybase64-1.4.3-cp312-cp312-win32.whl", hash = "sha256:7bca1ed3a5df53305c629ca94276966272eda33c0d71f862d2d3d043f1e1b91a", size = 33686, upload-time = "2025-12-06T13:23:37.848Z" }, - { url = "https://files.pythonhosted.org/packages/83/e3/507ab649d8c3512c258819c51d25c45d6e29d9ca33992593059e7b646a33/pybase64-1.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:9f2da8f56d9b891b18b4daf463a0640eae45a80af548ce435be86aa6eff3603b", size = 35833, upload-time = "2025-12-06T13:23:38.877Z" }, - { url = "https://files.pythonhosted.org/packages/bc/8a/6eba66cd549a2fc74bb4425fd61b839ba0ab3022d3c401b8a8dc2cc00c7a/pybase64-1.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:0631d8a2d035de03aa9bded029b9513e1fee8ed80b7ddef6b8e9389ffc445da0", size = 31185, upload-time = "2025-12-06T13:23:39.908Z" }, - { url = "https://files.pythonhosted.org/packages/3a/50/b7170cb2c631944388fe2519507fe3835a4054a6a12a43f43781dae82be1/pybase64-1.4.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:ea4b785b0607d11950b66ce7c328f452614aefc9c6d3c9c28bae795dc7f072e1", size = 33901, upload-time = "2025-12-06T13:23:40.951Z" }, - { url = "https://files.pythonhosted.org/packages/48/8b/69f50578e49c25e0a26e3ee72c39884ff56363344b79fc3967f5af420ed6/pybase64-1.4.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:6a10b6330188c3026a8b9c10e6b9b3f2e445779cf16a4c453d51a072241c65a2", size = 40807, upload-time = "2025-12-06T13:23:42.006Z" }, - { url = "https://files.pythonhosted.org/packages/5c/8d/20b68f11adfc4c22230e034b65c71392e3e338b413bf713c8945bd2ccfb3/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:27fdff227a0c0e182e0ba37a99109645188978b920dfb20d8b9c17eeee370d0d", size = 30932, upload-time = "2025-12-06T13:23:43.348Z" }, - { url = "https://files.pythonhosted.org/packages/f7/79/b1b550ac6bff51a4880bf6e089008b2e1ca16f2c98db5e039a08ac3ad157/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2a8204f1fdfec5aa4184249b51296c0de95445869920c88123978304aad42df1", size = 31394, upload-time = "2025-12-06T13:23:44.317Z" }, - { url = "https://files.pythonhosted.org/packages/82/70/b5d7c5932bf64ee1ec5da859fbac981930b6a55d432a603986c7f509c838/pybase64-1.4.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:874fc2a3777de6baf6aa921a7aa73b3be98295794bea31bd80568a963be30767", size = 38078, upload-time = "2025-12-06T13:23:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/56/fe/e66fe373bce717c6858427670736d54297938dad61c5907517ab4106bd90/pybase64-1.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2dc64a94a9d936b8e3449c66afabbaa521d3cc1a563d6bbaaa6ffa4535222e4b", size = 38158, upload-time = "2025-12-06T13:23:46.872Z" }, - { url = "https://files.pythonhosted.org/packages/80/a9/b806ed1dcc7aed2ea3dd4952286319e6f3a8b48615c8118f453948e01999/pybase64-1.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e48f86de1c145116ccf369a6e11720ce696c2ec02d285f440dfb57ceaa0a6cb4", size = 31672, upload-time = "2025-12-06T13:23:47.88Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c9/24b3b905cf75e23a9a4deaf203b35ffcb9f473ac0e6d8257f91a05dfce62/pybase64-1.4.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:1d45c8fe8fe82b65c36b227bb4a2cf623d9ada16bed602ce2d3e18c35285b72a", size = 68244, upload-time = "2025-12-06T13:23:49.026Z" }, - { url = "https://files.pythonhosted.org/packages/f8/cd/d15b0c3e25e5859fab0416dc5b96d34d6bd2603c1c96a07bb2202b68ab92/pybase64-1.4.3-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ad70c26ba091d8f5167e9d4e1e86a0483a5414805cdb598a813db635bd3be8b8", size = 71620, upload-time = "2025-12-06T13:23:50.081Z" }, - { url = "https://files.pythonhosted.org/packages/0d/31/4ca953cc3dcde2b3711d6bfd70a6f4ad2ca95a483c9698076ba605f1520f/pybase64-1.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e98310b7c43145221e7194ac9fa7fffc84763c87bfc5e2f59f9f92363475bdc1", size = 59930, upload-time = "2025-12-06T13:23:51.68Z" }, - { url = "https://files.pythonhosted.org/packages/60/55/e7f7bdcd0fd66e61dda08db158ffda5c89a306bbdaaf5a062fbe4e48f4a1/pybase64-1.4.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:398685a76034e91485a28aeebcb49e64cd663212fd697b2497ac6dfc1df5e671", size = 56425, upload-time = "2025-12-06T13:23:52.732Z" }, - { url = "https://files.pythonhosted.org/packages/cb/65/b592c7f921e51ca1aca3af5b0d201a98666d0a36b930ebb67e7c2ed27395/pybase64-1.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7e46400a6461187ccb52ed75b0045d937529e801a53a9cd770b350509f9e4d50", size = 59327, upload-time = "2025-12-06T13:23:53.856Z" }, - { url = "https://files.pythonhosted.org/packages/23/95/1613d2fb82dbb1548595ad4179f04e9a8451bfa18635efce18b631eabe3f/pybase64-1.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1b62b9f2f291d94f5e0b76ab499790b7dcc78a009d4ceea0b0428770267484b6", size = 60294, upload-time = "2025-12-06T13:23:54.937Z" }, - { url = "https://files.pythonhosted.org/packages/9d/73/40431f37f7d1b3eab4673e7946ff1e8f5d6bd425ec257e834dae8a6fc7b0/pybase64-1.4.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:f30ceb5fa4327809dede614be586efcbc55404406d71e1f902a6fdcf322b93b2", size = 54858, upload-time = "2025-12-06T13:23:56.031Z" }, - { url = "https://files.pythonhosted.org/packages/a7/84/f6368bcaf9f743732e002a9858646fd7a54f428490d427dd6847c5cfe89e/pybase64-1.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0d5f18ed53dfa1d4cf8b39ee542fdda8e66d365940e11f1710989b3cf4a2ed66", size = 58629, upload-time = "2025-12-06T13:23:57.12Z" }, - { url = "https://files.pythonhosted.org/packages/43/75/359532f9adb49c6b546cafc65c46ed75e2ccc220d514ba81c686fbd83965/pybase64-1.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:119d31aa4b58b85a8ebd12b63c07681a138c08dfc2fe5383459d42238665d3eb", size = 52448, upload-time = "2025-12-06T13:23:58.298Z" }, - { url = "https://files.pythonhosted.org/packages/92/6c/ade2ba244c3f33ed920a7ed572ad772eb0b5f14480b72d629d0c9e739a40/pybase64-1.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3cf0218b0e2f7988cf7d738a73b6a1d14f3be6ce249d7c0f606e768366df2cce", size = 68841, upload-time = "2025-12-06T13:23:59.886Z" }, - { url = "https://files.pythonhosted.org/packages/a0/51/b345139cd236be382f2d4d4453c21ee6299e14d2f759b668e23080f8663f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:12f4ee5e988bc5c0c1106b0d8fc37fb0508f12dab76bac1b098cb500d148da9d", size = 57910, upload-time = "2025-12-06T13:24:00.994Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b8/9f84bdc4f1c4f0052489396403c04be2f9266a66b70c776001eaf0d78c1f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:937826bc7b6b95b594a45180e81dd4d99bd4dd4814a443170e399163f7ff3fb6", size = 54335, upload-time = "2025-12-06T13:24:02.046Z" }, - { url = "https://files.pythonhosted.org/packages/d0/c7/be63b617d284de46578a366da77ede39c8f8e815ed0d82c7c2acca560fab/pybase64-1.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:88995d1460971ef80b13e3e007afbe4b27c62db0508bc7250a2ab0a0b4b91362", size = 56486, upload-time = "2025-12-06T13:24:03.141Z" }, - { url = "https://files.pythonhosted.org/packages/5e/96/f252c8f9abd6ded3ef1ccd3cdbb8393a33798007f761b23df8de1a2480e6/pybase64-1.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:72326fe163385ed3e1e806dd579d47fde5d8a59e51297a60fc4e6cbc1b4fc4ed", size = 70978, upload-time = "2025-12-06T13:24:04.221Z" }, - { url = "https://files.pythonhosted.org/packages/af/51/0f5714af7aeef96e30f968e4371d75ad60558aaed3579d7c6c8f1c43c18a/pybase64-1.4.3-cp313-cp313-win32.whl", hash = "sha256:b1623730c7892cf5ed0d6355e375416be6ef8d53ab9b284f50890443175c0ac3", size = 33684, upload-time = "2025-12-06T13:24:05.29Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ad/0cea830a654eb08563fb8214150ef57546ece1cc421c09035f0e6b0b5ea9/pybase64-1.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:8369887590f1646a5182ca2fb29252509da7ae31d4923dbb55d3e09da8cc4749", size = 35832, upload-time = "2025-12-06T13:24:06.35Z" }, - { url = "https://files.pythonhosted.org/packages/b4/0d/eec2a8214989c751bc7b4cad1860eb2c6abf466e76b77508c0f488c96a37/pybase64-1.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:860b86bca71e5f0237e2ab8b2d9c4c56681f3513b1bf3e2117290c1963488390", size = 31175, upload-time = "2025-12-06T13:24:07.419Z" }, - { url = "https://files.pythonhosted.org/packages/db/c9/e23463c1a2913686803ef76b1a5ae7e6fac868249a66e48253d17ad7232c/pybase64-1.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eb51db4a9c93215135dccd1895dca078e8785c357fabd983c9f9a769f08989a9", size = 38497, upload-time = "2025-12-06T13:24:08.873Z" }, - { url = "https://files.pythonhosted.org/packages/71/83/343f446b4b7a7579bf6937d2d013d82f1a63057cf05558e391ab6039d7db/pybase64-1.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a03ef3f529d85fd46b89971dfb00c634d53598d20ad8908fb7482955c710329d", size = 32076, upload-time = "2025-12-06T13:24:09.975Z" }, - { url = "https://files.pythonhosted.org/packages/46/fc/cb64964c3b29b432f54d1bce5e7691d693e33bbf780555151969ffd95178/pybase64-1.4.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2e745f2ce760c6cf04d8a72198ef892015ddb89f6ceba489e383518ecbdb13ab", size = 72317, upload-time = "2025-12-06T13:24:11.129Z" }, - { url = "https://files.pythonhosted.org/packages/0a/b7/fab2240da6f4e1ad46f71fa56ec577613cf5df9dce2d5b4cfaa4edd0e365/pybase64-1.4.3-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fac217cd9de8581a854b0ac734c50fd1fa4b8d912396c1fc2fce7c230efe3a7", size = 75534, upload-time = "2025-12-06T13:24:12.433Z" }, - { url = "https://files.pythonhosted.org/packages/91/3b/3e2f2b6e68e3d83ddb9fa799f3548fb7449765daec9bbd005a9fbe296d7f/pybase64-1.4.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:da1ee8fa04b283873de2d6e8fa5653e827f55b86bdf1a929c5367aaeb8d26f8a", size = 65399, upload-time = "2025-12-06T13:24:13.928Z" }, - { url = "https://files.pythonhosted.org/packages/6b/08/476ac5914c3b32e0274a2524fc74f01cbf4f4af4513d054e41574eb018f6/pybase64-1.4.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:b0bf8e884ee822ca7b1448eeb97fa131628fe0ff42f60cae9962789bd562727f", size = 60487, upload-time = "2025-12-06T13:24:15.177Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b8/618a92915330cc9cba7880299b546a1d9dab1a21fd6c0292ee44a4fe608c/pybase64-1.4.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1bf749300382a6fd1f4f255b183146ef58f8e9cb2f44a077b3a9200dfb473a77", size = 63959, upload-time = "2025-12-06T13:24:16.854Z" }, - { url = "https://files.pythonhosted.org/packages/a5/52/af9d8d051652c3051862c442ec3861259c5cdb3fc69774bc701470bd2a59/pybase64-1.4.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:153a0e42329b92337664cfc356f2065248e6c9a1bd651bbcd6dcaf15145d3f06", size = 64874, upload-time = "2025-12-06T13:24:18.328Z" }, - { url = "https://files.pythonhosted.org/packages/e4/51/5381a7adf1f381bd184d33203692d3c57cf8ae9f250f380c3fecbdbe554b/pybase64-1.4.3-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:86ee56ac7f2184ca10217ed1c655c1a060273e233e692e9086da29d1ae1768db", size = 58572, upload-time = "2025-12-06T13:24:19.417Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f0/578ee4ffce5818017de4fdf544e066c225bc435e73eb4793cde28a689d0b/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0e71a4db76726bf830b47477e7d830a75c01b2e9b01842e787a0836b0ba741e3", size = 63636, upload-time = "2025-12-06T13:24:20.497Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ad/8ae94814bf20159ea06310b742433e53d5820aa564c9fdf65bf2d79f8799/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2ba7799ec88540acd9861b10551d24656ca3c2888ecf4dba2ee0a71544a8923f", size = 56193, upload-time = "2025-12-06T13:24:21.559Z" }, - { url = "https://files.pythonhosted.org/packages/d1/31/6438cfcc3d3f0fa84d229fa125c243d5094e72628e525dfefadf3bcc6761/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2860299e4c74315f5951f0cf3e72ba0f201c3356c8a68f95a3ab4e620baf44e9", size = 72655, upload-time = "2025-12-06T13:24:22.673Z" }, - { url = "https://files.pythonhosted.org/packages/a3/0d/2bbc9e9c3fc12ba8a6e261482f03a544aca524f92eae0b4908c0a10ba481/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:bb06015db9151f0c66c10aae8e3603adab6b6cd7d1f7335a858161d92fc29618", size = 62471, upload-time = "2025-12-06T13:24:23.8Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0b/34d491e7f49c1dbdb322ea8da6adecda7c7cd70b6644557c6e4ca5c6f7c7/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:242512a070817272865d37c8909059f43003b81da31f616bb0c391ceadffe067", size = 58119, upload-time = "2025-12-06T13:24:24.994Z" }, - { url = "https://files.pythonhosted.org/packages/ce/17/c21d0cde2a6c766923ae388fc1f78291e1564b0d38c814b5ea8a0e5e081c/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5d8277554a12d3e3eed6180ebda62786bf9fc8d7bb1ee00244258f4a87ca8d20", size = 60791, upload-time = "2025-12-06T13:24:26.046Z" }, - { url = "https://files.pythonhosted.org/packages/92/b2/eaa67038916a48de12b16f4c384bcc1b84b7ec731b23613cb05f27673294/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f40b7ddd698fc1e13a4b64fbe405e4e0e1279e8197e37050e24154655f5f7c4e", size = 74701, upload-time = "2025-12-06T13:24:27.466Z" }, - { url = "https://files.pythonhosted.org/packages/42/10/abb7757c330bb869ebb95dab0c57edf5961ffbd6c095c8209cbbf75d117d/pybase64-1.4.3-cp313-cp313t-win32.whl", hash = "sha256:46d75c9387f354c5172582a9eaae153b53a53afeb9c19fcf764ea7038be3bd8b", size = 33965, upload-time = "2025-12-06T13:24:28.548Z" }, - { url = "https://files.pythonhosted.org/packages/63/a0/2d4e5a59188e9e6aed0903d580541aaea72dcbbab7bf50fb8b83b490b6c3/pybase64-1.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:d7344625591d281bec54e85cbfdab9e970f6219cac1570f2aa140b8c942ccb81", size = 36207, upload-time = "2025-12-06T13:24:29.646Z" }, - { url = "https://files.pythonhosted.org/packages/1f/05/95b902e8f567b4d4b41df768ccc438af618f8d111e54deaf57d2df46bd76/pybase64-1.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:28a3c60c55138e0028313f2eccd321fec3c4a0be75e57a8d3eb883730b1b0880", size = 31505, upload-time = "2025-12-06T13:24:30.687Z" }, - { url = "https://files.pythonhosted.org/packages/e4/80/4bd3dff423e5a91f667ca41982dc0b79495b90ec0c0f5d59aca513e50f8c/pybase64-1.4.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:015bb586a1ea1467f69d57427abe587469392215f59db14f1f5c39b52fdafaf5", size = 33835, upload-time = "2025-12-06T13:24:31.767Z" }, - { url = "https://files.pythonhosted.org/packages/45/60/a94d94cc1e3057f602e0b483c9ebdaef40911d84a232647a2fe593ab77bb/pybase64-1.4.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:d101e3a516f837c3dcc0e5a0b7db09582ebf99ed670865223123fb2e5839c6c0", size = 40673, upload-time = "2025-12-06T13:24:32.82Z" }, - { url = "https://files.pythonhosted.org/packages/e3/71/cf62b261d431857e8e054537a5c3c24caafa331de30daede7b2c6c558501/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8f183ac925a48046abe047360fe3a1b28327afb35309892132fe1915d62fb282", size = 30939, upload-time = "2025-12-06T13:24:34.001Z" }, - { url = "https://files.pythonhosted.org/packages/24/3e/d12f92a3c1f7c6ab5d53c155bff9f1084ba997a37a39a4f781ccba9455f3/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30bf3558e24dcce4da5248dcf6d73792adfcf4f504246967e9db155be4c439ad", size = 31401, upload-time = "2025-12-06T13:24:35.11Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3d/9c27440031fea0d05146f8b70a460feb95d8b4e3d9ca8f45c972efb4c3d3/pybase64-1.4.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a674b419de318d2ce54387dd62646731efa32b4b590907800f0bd40675c1771d", size = 38075, upload-time = "2025-12-06T13:24:36.53Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d4/6c0e0cf0efd53c254173fbcd84a3d8fcbf5e0f66622473da425becec32a5/pybase64-1.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:720104fd7303d07bac302be0ff8f7f9f126f2f45c1edb4f48fdb0ff267e69fe1", size = 38257, upload-time = "2025-12-06T13:24:38.049Z" }, - { url = "https://files.pythonhosted.org/packages/50/eb/27cb0b610d5cd70f5ad0d66c14ad21c04b8db930f7139818e8fbdc14df4d/pybase64-1.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:83f1067f73fa5afbc3efc0565cecc6ed53260eccddef2ebe43a8ce2b99ea0e0a", size = 31685, upload-time = "2025-12-06T13:24:40.327Z" }, - { url = "https://files.pythonhosted.org/packages/db/26/b136a4b65e5c94ff06217f7726478df3f31ab1c777c2c02cf698e748183f/pybase64-1.4.3-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b51204d349a4b208287a8aa5b5422be3baa88abf6cc8ff97ccbda34919bbc857", size = 68460, upload-time = "2025-12-06T13:24:41.735Z" }, - { url = "https://files.pythonhosted.org/packages/68/6d/84ce50e7ee1ae79984d689e05a9937b2460d4efa1e5b202b46762fb9036c/pybase64-1.4.3-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:30f2fd53efecbdde4bdca73a872a68dcb0d1bf8a4560c70a3e7746df973e1ef3", size = 71688, upload-time = "2025-12-06T13:24:42.908Z" }, - { url = "https://files.pythonhosted.org/packages/e3/57/6743e420416c3ff1b004041c85eb0ebd9c50e9cf05624664bfa1dc8b5625/pybase64-1.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0932b0c5cfa617091fd74f17d24549ce5de3628791998c94ba57be808078eeaf", size = 60040, upload-time = "2025-12-06T13:24:44.37Z" }, - { url = "https://files.pythonhosted.org/packages/3b/68/733324e28068a89119af2921ce548e1c607cc5c17d354690fc51c302e326/pybase64-1.4.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:acb61f5ab72bec808eb0d4ce8b87ec9f38d7d750cb89b1371c35eb8052a29f11", size = 56478, upload-time = "2025-12-06T13:24:45.815Z" }, - { url = "https://files.pythonhosted.org/packages/b5/9e/f3f4aa8cfe3357a3cdb0535b78eb032b671519d3ecc08c58c4c6b72b5a91/pybase64-1.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:2bc2d5bc15168f5c04c53bdfe5a1e543b2155f456ed1e16d7edce9ce73842021", size = 59463, upload-time = "2025-12-06T13:24:46.938Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d1/53286038e1f0df1cf58abcf4a4a91b0f74ab44539c2547b6c31001ddd054/pybase64-1.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8a7bc3cd23880bdca59758bcdd6f4ef0674f2393782763910a7466fab35ccb98", size = 60360, upload-time = "2025-12-06T13:24:48.039Z" }, - { url = "https://files.pythonhosted.org/packages/00/9a/5cc6ce95db2383d27ff4d790b8f8b46704d360d701ab77c4f655bcfaa6a7/pybase64-1.4.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ad15acf618880d99792d71e3905b0e2508e6e331b76a1b34212fa0f11e01ad28", size = 54999, upload-time = "2025-12-06T13:24:49.547Z" }, - { url = "https://files.pythonhosted.org/packages/64/e7/c3c1d09c3d7ae79e3aa1358c6d912d6b85f29281e47aa94fc0122a415a2f/pybase64-1.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448158d417139cb4851200e5fee62677ae51f56a865d50cda9e0d61bda91b116", size = 58736, upload-time = "2025-12-06T13:24:50.641Z" }, - { url = "https://files.pythonhosted.org/packages/db/d5/0baa08e3d8119b15b588c39f0d39fd10472f0372e3c54ca44649cbefa256/pybase64-1.4.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9058c49b5a2f3e691b9db21d37eb349e62540f9f5fc4beabf8cbe3c732bead86", size = 52298, upload-time = "2025-12-06T13:24:51.791Z" }, - { url = "https://files.pythonhosted.org/packages/00/87/fc6f11474a1de7e27cd2acbb8d0d7508bda3efa73dfe91c63f968728b2a3/pybase64-1.4.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ce561724f6522907a66303aca27dce252d363fcd85884972d348f4403ba3011a", size = 69049, upload-time = "2025-12-06T13:24:53.253Z" }, - { url = "https://files.pythonhosted.org/packages/69/9d/7fb5566f669ac18b40aa5fc1c438e24df52b843c1bdc5da47d46d4c1c630/pybase64-1.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:63316560a94ac449fe86cb8b9e0a13714c659417e92e26a5cbf085cd0a0c838d", size = 57952, upload-time = "2025-12-06T13:24:54.342Z" }, - { url = "https://files.pythonhosted.org/packages/de/cc/ceb949232dbbd3ec4ee0190d1df4361296beceee9840390a63df8bc31784/pybase64-1.4.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7ecd796f2ac0be7b73e7e4e232b8c16422014de3295d43e71d2b19fd4a4f5368", size = 54484, upload-time = "2025-12-06T13:24:55.774Z" }, - { url = "https://files.pythonhosted.org/packages/a7/69/659f3c8e6a5d7b753b9c42a4bd9c42892a0f10044e9c7351a4148d413a33/pybase64-1.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d01e102a12fb2e1ed3dc11611c2818448626637857ec3994a9cf4809dfd23477", size = 56542, upload-time = "2025-12-06T13:24:57Z" }, - { url = "https://files.pythonhosted.org/packages/85/2c/29c9e6c9c82b72025f9676f9e82eb1fd2339ad038cbcbf8b9e2ac02798fc/pybase64-1.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ebff797a93c2345f22183f454fd8607a34d75eca5a3a4a969c1c75b304cee39d", size = 71045, upload-time = "2025-12-06T13:24:58.179Z" }, - { url = "https://files.pythonhosted.org/packages/b9/84/5a3dce8d7a0040a5c0c14f0fe1311cd8db872913fa04438071b26b0dac04/pybase64-1.4.3-cp314-cp314-win32.whl", hash = "sha256:28b2a1bb0828c0595dc1ea3336305cd97ff85b01c00d81cfce4f92a95fb88f56", size = 34200, upload-time = "2025-12-06T13:24:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/57/bc/ce7427c12384adee115b347b287f8f3cf65860b824d74fe2c43e37e81c1f/pybase64-1.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:33338d3888700ff68c3dedfcd49f99bfc3b887570206130926791e26b316b029", size = 36323, upload-time = "2025-12-06T13:25:01.708Z" }, - { url = "https://files.pythonhosted.org/packages/9a/1b/2b8ffbe9a96eef7e3f6a5a7be75995eebfb6faaedc85b6da6b233e50c778/pybase64-1.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:62725669feb5acb186458da2f9353e88ae28ef66bb9c4c8d1568b12a790dfa94", size = 31584, upload-time = "2025-12-06T13:25:02.801Z" }, - { url = "https://files.pythonhosted.org/packages/ac/d8/6824c2e6fb45b8fa4e7d92e3c6805432d5edc7b855e3e8e1eedaaf6efb7c/pybase64-1.4.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:153fe29be038948d9372c3e77ae7d1cab44e4ba7d9aaf6f064dbeea36e45b092", size = 38601, upload-time = "2025-12-06T13:25:04.222Z" }, - { url = "https://files.pythonhosted.org/packages/ea/e5/10d2b3a4ad3a4850be2704a2f70cd9c0cf55725c8885679872d3bc846c67/pybase64-1.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f7fe3decaa7c4a9e162327ec7bd81ce183d2b16f23c6d53b606649c6e0203e9e", size = 32078, upload-time = "2025-12-06T13:25:05.362Z" }, - { url = "https://files.pythonhosted.org/packages/43/04/8b15c34d3c2282f1c1b0850f1113a249401b618a382646a895170bc9b5e7/pybase64-1.4.3-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a5ae04ea114c86eb1da1f6e18d75f19e3b5ae39cb1d8d3cd87c29751a6a22780", size = 72474, upload-time = "2025-12-06T13:25:06.434Z" }, - { url = "https://files.pythonhosted.org/packages/42/00/f34b4d11278f8fdc68bc38f694a91492aa318f7c6f1bd7396197ac0f8b12/pybase64-1.4.3-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1755b3dce3a2a5c7d17ff6d4115e8bee4a1d5aeae74469db02e47c8f477147da", size = 75706, upload-time = "2025-12-06T13:25:07.636Z" }, - { url = "https://files.pythonhosted.org/packages/bb/5d/71747d4ad7fe16df4c4c852bdbdeb1f2cf35677b48d7c34d3011a7a6ad3a/pybase64-1.4.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb852f900e27ffc4ec1896817535a0fa19610ef8875a096b59f21d0aa42ff172", size = 65589, upload-time = "2025-12-06T13:25:08.809Z" }, - { url = "https://files.pythonhosted.org/packages/49/b1/d1e82bd58805bb5a3a662864800bab83a83a36ba56e7e3b1706c708002a5/pybase64-1.4.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:9cf21ea8c70c61eddab3421fbfce061fac4f2fb21f7031383005a1efdb13d0b9", size = 60670, upload-time = "2025-12-06T13:25:10.04Z" }, - { url = "https://files.pythonhosted.org/packages/15/67/16c609b7a13d1d9fc87eca12ba2dce5e67f949eeaab61a41bddff843cbb0/pybase64-1.4.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:afff11b331fdc27692fc75e85ae083340a35105cea1a3c4552139e2f0e0d174f", size = 64194, upload-time = "2025-12-06T13:25:11.48Z" }, - { url = "https://files.pythonhosted.org/packages/3c/11/37bc724e42960f0106c2d33dc957dcec8f760c91a908cc6c0df7718bc1a8/pybase64-1.4.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9a5143df542c1ce5c1f423874b948c4d689b3f05ec571f8792286197a39ba02", size = 64984, upload-time = "2025-12-06T13:25:12.645Z" }, - { url = "https://files.pythonhosted.org/packages/6e/66/b2b962a6a480dd5dae3029becf03ea1a650d326e39bf1c44ea3db78bb010/pybase64-1.4.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:d62e9861019ad63624b4a7914dff155af1cc5d6d79df3be14edcaedb5fdad6f9", size = 58750, upload-time = "2025-12-06T13:25:13.848Z" }, - { url = "https://files.pythonhosted.org/packages/2b/15/9b6d711035e29b18b2e1c03d47f41396d803d06ef15b6c97f45b75f73f04/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:84cfd4d92668ef5766cc42a9c9474b88960ac2b860767e6e7be255c6fddbd34a", size = 63816, upload-time = "2025-12-06T13:25:15.356Z" }, - { url = "https://files.pythonhosted.org/packages/b4/21/e2901381ed0df62e2308380f30d9c4d87d6b74e33a84faed3478d33a7197/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:60fc025437f9a7c2cc45e0c19ed68ed08ba672be2c5575fd9d98bdd8f01dd61f", size = 56348, upload-time = "2025-12-06T13:25:16.559Z" }, - { url = "https://files.pythonhosted.org/packages/c4/16/3d788388a178a0407aa814b976fe61bfa4af6760d9aac566e59da6e4a8b4/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:edc8446196f04b71d3af76c0bd1fe0a45066ac5bffecca88adb9626ee28c266f", size = 72842, upload-time = "2025-12-06T13:25:18.055Z" }, - { url = "https://files.pythonhosted.org/packages/a6/63/c15b1f8bd47ea48a5a2d52a4ec61f037062932ea6434ab916107b58e861e/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e99f6fa6509c037794da57f906ade271f52276c956d00f748e5b118462021d48", size = 62651, upload-time = "2025-12-06T13:25:19.191Z" }, - { url = "https://files.pythonhosted.org/packages/bd/b8/f544a2e37c778d59208966d4ef19742a0be37c12fc8149ff34483c176616/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d94020ef09f624d841aa9a3a6029df8cf65d60d7a6d5c8687579fa68bd679b65", size = 58295, upload-time = "2025-12-06T13:25:20.822Z" }, - { url = "https://files.pythonhosted.org/packages/03/99/1fae8a3b7ac181e36f6e7864a62d42d5b1f4fa7edf408c6711e28fba6b4d/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f64ce70d89942a23602dee910dec9b48e5edf94351e1b378186b74fcc00d7f66", size = 60960, upload-time = "2025-12-06T13:25:22.099Z" }, - { url = "https://files.pythonhosted.org/packages/9d/9e/cd4c727742345ad8384569a4466f1a1428f4e5cc94d9c2ab2f53d30be3fe/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8ea99f56e45c469818b9781903be86ba4153769f007ba0655fa3b46dc332803d", size = 74863, upload-time = "2025-12-06T13:25:23.442Z" }, - { url = "https://files.pythonhosted.org/packages/28/86/a236ecfc5b494e1e922da149689f690abc84248c7c1358f5605b8c9fdd60/pybase64-1.4.3-cp314-cp314t-win32.whl", hash = "sha256:343b1901103cc72362fd1f842524e3bb24978e31aea7ff11e033af7f373f66ab", size = 34513, upload-time = "2025-12-06T13:25:24.592Z" }, - { url = "https://files.pythonhosted.org/packages/56/ce/ca8675f8d1352e245eb012bfc75429ee9cf1f21c3256b98d9a329d44bf0f/pybase64-1.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:57aff6f7f9dea6705afac9d706432049642de5b01080d3718acc23af87c5af76", size = 36702, upload-time = "2025-12-06T13:25:25.72Z" }, - { url = "https://files.pythonhosted.org/packages/3b/30/4a675864877397179b09b720ee5fcb1cf772cf7bebc831989aff0a5f79c1/pybase64-1.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e906aa08d4331e799400829e0f5e4177e76a3281e8a4bc82ba114c6b30e405c9", size = 31904, upload-time = "2025-12-06T13:25:26.826Z" }, - { url = "https://files.pythonhosted.org/packages/17/45/92322aec1b6979e789b5710f73c59f2172bc37c8ce835305434796824b7b/pybase64-1.4.3-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:2baaa092f3475f3a9c87ac5198023918ea8b6c125f4c930752ab2cbe3cd1d520", size = 38746, upload-time = "2025-12-06T13:26:25.869Z" }, - { url = "https://files.pythonhosted.org/packages/11/94/f1a07402870388fdfc2ecec0c718111189732f7d0f2d7fe1386e19e8fad0/pybase64-1.4.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:cde13c0764b1af07a631729f26df019070dad759981d6975527b7e8ecb465b6c", size = 32573, upload-time = "2025-12-06T13:26:27.792Z" }, - { url = "https://files.pythonhosted.org/packages/fa/8f/43c3bb11ca9bacf81cb0b7a71500bb65b2eda6d5fe07433c09b543de97f3/pybase64-1.4.3-graalpy312-graalpy250_312_native-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5c29a582b0ea3936d02bd6fe9bf674ab6059e6e45ab71c78404ab2c913224414", size = 43461, upload-time = "2025-12-06T13:26:28.906Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4c/2a5258329200be57497d3972b5308558c6de42e3749c6cc2aa1cbe34b25a/pybase64-1.4.3-graalpy312-graalpy250_312_native-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6b664758c804fa919b4f1257aa8cf68e95db76fc331de5f70bfc3a34655afe1", size = 36058, upload-time = "2025-12-06T13:26:30.092Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6d/41faa414cde66ec023b0ca8402a8f11cb61731c3dc27c082909cbbd1f929/pybase64-1.4.3-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:f7537fa22ae56a0bf51e4b0ffc075926ad91c618e1416330939f7ef366b58e3b", size = 36231, upload-time = "2025-12-06T13:26:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/86/a7/efcaa564f091a2af7f18a83c1c4875b1437db56ba39540451dc85d56f653/pybase64-1.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:18d85e5ab8b986bb32d8446aca6258ed80d1bafe3603c437690b352c648f5967", size = 38167 }, + { url = "https://files.pythonhosted.org/packages/db/c7/c7ad35adff2d272bf2930132db2b3eea8c44bb1b1f64eb9b2b8e57cde7b4/pybase64-1.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f5791a3491d116d0deaf4d83268f48792998519698f8751efb191eac84320e9", size = 31673 }, + { url = "https://files.pythonhosted.org/packages/43/1b/9a8cab0042b464e9a876d5c65fe5127445a2436da36fda64899b119b1a1b/pybase64-1.4.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f0b3f200c3e06316f6bebabd458b4e4bcd4c2ca26af7c0c766614d91968dee27", size = 68210 }, + { url = "https://files.pythonhosted.org/packages/62/f7/965b79ff391ad208b50e412b5d3205ccce372a2d27b7218ae86d5295b105/pybase64-1.4.3-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb632edfd132b3eaf90c39c89aa314beec4e946e210099b57d40311f704e11d4", size = 71599 }, + { url = "https://files.pythonhosted.org/packages/03/4b/a3b5175130b3810bbb8ccfa1edaadbd3afddb9992d877c8a1e2f274b476e/pybase64-1.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:356ef1d74648ce997f5a777cf8f1aefecc1c0b4fe6201e0ef3ec8a08170e1b54", size = 59922 }, + { url = "https://files.pythonhosted.org/packages/da/5d/c38d1572027fc601b62d7a407721688b04b4d065d60ca489912d6893e6cf/pybase64-1.4.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:c48361f90db32bacaa5518419d4eb9066ba558013aaf0c7781620279ecddaeb9", size = 56712 }, + { url = "https://files.pythonhosted.org/packages/e7/d4/4e04472fef485caa8f561d904d4d69210a8f8fc1608ea15ebd9012b92655/pybase64-1.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:702bcaa16ae02139d881aeaef5b1c8ffb4a3fae062fe601d1e3835e10310a517", size = 59300 }, + { url = "https://files.pythonhosted.org/packages/86/e7/16e29721b86734b881d09b7e23dfd7c8408ad01a4f4c7525f3b1088e25ec/pybase64-1.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:53d0ffe1847b16b647c6413d34d1de08942b7724273dd57e67dcbdb10c574045", size = 60278 }, + { url = "https://files.pythonhosted.org/packages/b1/02/18515f211d7c046be32070709a8efeeef8a0203de4fd7521e6b56404731b/pybase64-1.4.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9a1792e8b830a92736dae58f0c386062eb038dfe8004fb03ba33b6083d89cd43", size = 54817 }, + { url = "https://files.pythonhosted.org/packages/e7/be/14e29d8e1a481dbff151324c96dd7b5d2688194bb65dc8a00ca0e1ad1e86/pybase64-1.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d468b1b1ac5ad84875a46eaa458663c3721e8be5f155ade356406848d3701f6", size = 58611 }, + { url = "https://files.pythonhosted.org/packages/b4/8a/a2588dfe24e1bbd742a554553778ab0d65fdf3d1c9a06d10b77047d142aa/pybase64-1.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e97b7bdbd62e71898cd542a6a9e320d9da754ff3ebd02cb802d69087ee94d468", size = 52404 }, + { url = "https://files.pythonhosted.org/packages/27/fc/afcda7445bebe0cbc38cafdd7813234cdd4fc5573ff067f1abf317bb0cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b33aeaa780caaa08ffda87fc584d5eab61e3d3bbb5d86ead02161dc0c20d04bc", size = 68817 }, + { url = "https://files.pythonhosted.org/packages/d3/3a/87c3201e555ed71f73e961a787241a2438c2bbb2ca8809c29ddf938a3157/pybase64-1.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c0efcf78f11cf866bed49caa7b97552bc4855a892f9cc2372abcd3ed0056f0d", size = 57854 }, + { url = "https://files.pythonhosted.org/packages/fd/7d/931c2539b31a7b375e7d595b88401eeb5bd6c5ce1059c9123f9b608aaa14/pybase64-1.4.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:66e3791f2ed725a46593f8bd2761ff37d01e2cdad065b1dceb89066f476e50c6", size = 54333 }, + { url = "https://files.pythonhosted.org/packages/de/5e/537601e02cc01f27e9d75f440f1a6095b8df44fc28b1eef2cd739aea8cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:72bb0b6bddadab26e1b069bb78e83092711a111a80a0d6b9edcb08199ad7299b", size = 56492 }, + { url = "https://files.pythonhosted.org/packages/96/97/2a2e57acf8f5c9258d22aba52e71f8050e167b29ed2ee1113677c1b600c1/pybase64-1.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5b3365dbcbcdb0a294f0f50af0c0a16b27a232eddeeb0bceeefd844ef30d2a23", size = 70974 }, + { url = "https://files.pythonhosted.org/packages/75/2e/a9e28941c6dab6f06e6d3f6783d3373044be9b0f9a9d3492c3d8d2260ac0/pybase64-1.4.3-cp312-cp312-win32.whl", hash = "sha256:7bca1ed3a5df53305c629ca94276966272eda33c0d71f862d2d3d043f1e1b91a", size = 33686 }, + { url = "https://files.pythonhosted.org/packages/83/e3/507ab649d8c3512c258819c51d25c45d6e29d9ca33992593059e7b646a33/pybase64-1.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:9f2da8f56d9b891b18b4daf463a0640eae45a80af548ce435be86aa6eff3603b", size = 35833 }, + { url = "https://files.pythonhosted.org/packages/bc/8a/6eba66cd549a2fc74bb4425fd61b839ba0ab3022d3c401b8a8dc2cc00c7a/pybase64-1.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:0631d8a2d035de03aa9bded029b9513e1fee8ed80b7ddef6b8e9389ffc445da0", size = 31185 }, + { url = "https://files.pythonhosted.org/packages/3a/50/b7170cb2c631944388fe2519507fe3835a4054a6a12a43f43781dae82be1/pybase64-1.4.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:ea4b785b0607d11950b66ce7c328f452614aefc9c6d3c9c28bae795dc7f072e1", size = 33901 }, + { url = "https://files.pythonhosted.org/packages/48/8b/69f50578e49c25e0a26e3ee72c39884ff56363344b79fc3967f5af420ed6/pybase64-1.4.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:6a10b6330188c3026a8b9c10e6b9b3f2e445779cf16a4c453d51a072241c65a2", size = 40807 }, + { url = "https://files.pythonhosted.org/packages/5c/8d/20b68f11adfc4c22230e034b65c71392e3e338b413bf713c8945bd2ccfb3/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:27fdff227a0c0e182e0ba37a99109645188978b920dfb20d8b9c17eeee370d0d", size = 30932 }, + { url = "https://files.pythonhosted.org/packages/f7/79/b1b550ac6bff51a4880bf6e089008b2e1ca16f2c98db5e039a08ac3ad157/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2a8204f1fdfec5aa4184249b51296c0de95445869920c88123978304aad42df1", size = 31394 }, + { url = "https://files.pythonhosted.org/packages/82/70/b5d7c5932bf64ee1ec5da859fbac981930b6a55d432a603986c7f509c838/pybase64-1.4.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:874fc2a3777de6baf6aa921a7aa73b3be98295794bea31bd80568a963be30767", size = 38078 }, + { url = "https://files.pythonhosted.org/packages/56/fe/e66fe373bce717c6858427670736d54297938dad61c5907517ab4106bd90/pybase64-1.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2dc64a94a9d936b8e3449c66afabbaa521d3cc1a563d6bbaaa6ffa4535222e4b", size = 38158 }, + { url = "https://files.pythonhosted.org/packages/80/a9/b806ed1dcc7aed2ea3dd4952286319e6f3a8b48615c8118f453948e01999/pybase64-1.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e48f86de1c145116ccf369a6e11720ce696c2ec02d285f440dfb57ceaa0a6cb4", size = 31672 }, + { url = "https://files.pythonhosted.org/packages/1c/c9/24b3b905cf75e23a9a4deaf203b35ffcb9f473ac0e6d8257f91a05dfce62/pybase64-1.4.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:1d45c8fe8fe82b65c36b227bb4a2cf623d9ada16bed602ce2d3e18c35285b72a", size = 68244 }, + { url = "https://files.pythonhosted.org/packages/f8/cd/d15b0c3e25e5859fab0416dc5b96d34d6bd2603c1c96a07bb2202b68ab92/pybase64-1.4.3-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ad70c26ba091d8f5167e9d4e1e86a0483a5414805cdb598a813db635bd3be8b8", size = 71620 }, + { url = "https://files.pythonhosted.org/packages/0d/31/4ca953cc3dcde2b3711d6bfd70a6f4ad2ca95a483c9698076ba605f1520f/pybase64-1.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e98310b7c43145221e7194ac9fa7fffc84763c87bfc5e2f59f9f92363475bdc1", size = 59930 }, + { url = "https://files.pythonhosted.org/packages/60/55/e7f7bdcd0fd66e61dda08db158ffda5c89a306bbdaaf5a062fbe4e48f4a1/pybase64-1.4.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:398685a76034e91485a28aeebcb49e64cd663212fd697b2497ac6dfc1df5e671", size = 56425 }, + { url = "https://files.pythonhosted.org/packages/cb/65/b592c7f921e51ca1aca3af5b0d201a98666d0a36b930ebb67e7c2ed27395/pybase64-1.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7e46400a6461187ccb52ed75b0045d937529e801a53a9cd770b350509f9e4d50", size = 59327 }, + { url = "https://files.pythonhosted.org/packages/23/95/1613d2fb82dbb1548595ad4179f04e9a8451bfa18635efce18b631eabe3f/pybase64-1.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1b62b9f2f291d94f5e0b76ab499790b7dcc78a009d4ceea0b0428770267484b6", size = 60294 }, + { url = "https://files.pythonhosted.org/packages/9d/73/40431f37f7d1b3eab4673e7946ff1e8f5d6bd425ec257e834dae8a6fc7b0/pybase64-1.4.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:f30ceb5fa4327809dede614be586efcbc55404406d71e1f902a6fdcf322b93b2", size = 54858 }, + { url = "https://files.pythonhosted.org/packages/a7/84/f6368bcaf9f743732e002a9858646fd7a54f428490d427dd6847c5cfe89e/pybase64-1.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0d5f18ed53dfa1d4cf8b39ee542fdda8e66d365940e11f1710989b3cf4a2ed66", size = 58629 }, + { url = "https://files.pythonhosted.org/packages/43/75/359532f9adb49c6b546cafc65c46ed75e2ccc220d514ba81c686fbd83965/pybase64-1.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:119d31aa4b58b85a8ebd12b63c07681a138c08dfc2fe5383459d42238665d3eb", size = 52448 }, + { url = "https://files.pythonhosted.org/packages/92/6c/ade2ba244c3f33ed920a7ed572ad772eb0b5f14480b72d629d0c9e739a40/pybase64-1.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3cf0218b0e2f7988cf7d738a73b6a1d14f3be6ce249d7c0f606e768366df2cce", size = 68841 }, + { url = "https://files.pythonhosted.org/packages/a0/51/b345139cd236be382f2d4d4453c21ee6299e14d2f759b668e23080f8663f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:12f4ee5e988bc5c0c1106b0d8fc37fb0508f12dab76bac1b098cb500d148da9d", size = 57910 }, + { url = "https://files.pythonhosted.org/packages/1a/b8/9f84bdc4f1c4f0052489396403c04be2f9266a66b70c776001eaf0d78c1f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:937826bc7b6b95b594a45180e81dd4d99bd4dd4814a443170e399163f7ff3fb6", size = 54335 }, + { url = "https://files.pythonhosted.org/packages/d0/c7/be63b617d284de46578a366da77ede39c8f8e815ed0d82c7c2acca560fab/pybase64-1.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:88995d1460971ef80b13e3e007afbe4b27c62db0508bc7250a2ab0a0b4b91362", size = 56486 }, + { url = "https://files.pythonhosted.org/packages/5e/96/f252c8f9abd6ded3ef1ccd3cdbb8393a33798007f761b23df8de1a2480e6/pybase64-1.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:72326fe163385ed3e1e806dd579d47fde5d8a59e51297a60fc4e6cbc1b4fc4ed", size = 70978 }, + { url = "https://files.pythonhosted.org/packages/af/51/0f5714af7aeef96e30f968e4371d75ad60558aaed3579d7c6c8f1c43c18a/pybase64-1.4.3-cp313-cp313-win32.whl", hash = "sha256:b1623730c7892cf5ed0d6355e375416be6ef8d53ab9b284f50890443175c0ac3", size = 33684 }, + { url = "https://files.pythonhosted.org/packages/b6/ad/0cea830a654eb08563fb8214150ef57546ece1cc421c09035f0e6b0b5ea9/pybase64-1.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:8369887590f1646a5182ca2fb29252509da7ae31d4923dbb55d3e09da8cc4749", size = 35832 }, + { url = "https://files.pythonhosted.org/packages/b4/0d/eec2a8214989c751bc7b4cad1860eb2c6abf466e76b77508c0f488c96a37/pybase64-1.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:860b86bca71e5f0237e2ab8b2d9c4c56681f3513b1bf3e2117290c1963488390", size = 31175 }, + { url = "https://files.pythonhosted.org/packages/db/c9/e23463c1a2913686803ef76b1a5ae7e6fac868249a66e48253d17ad7232c/pybase64-1.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eb51db4a9c93215135dccd1895dca078e8785c357fabd983c9f9a769f08989a9", size = 38497 }, + { url = "https://files.pythonhosted.org/packages/71/83/343f446b4b7a7579bf6937d2d013d82f1a63057cf05558e391ab6039d7db/pybase64-1.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a03ef3f529d85fd46b89971dfb00c634d53598d20ad8908fb7482955c710329d", size = 32076 }, + { url = "https://files.pythonhosted.org/packages/46/fc/cb64964c3b29b432f54d1bce5e7691d693e33bbf780555151969ffd95178/pybase64-1.4.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2e745f2ce760c6cf04d8a72198ef892015ddb89f6ceba489e383518ecbdb13ab", size = 72317 }, + { url = "https://files.pythonhosted.org/packages/0a/b7/fab2240da6f4e1ad46f71fa56ec577613cf5df9dce2d5b4cfaa4edd0e365/pybase64-1.4.3-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fac217cd9de8581a854b0ac734c50fd1fa4b8d912396c1fc2fce7c230efe3a7", size = 75534 }, + { url = "https://files.pythonhosted.org/packages/91/3b/3e2f2b6e68e3d83ddb9fa799f3548fb7449765daec9bbd005a9fbe296d7f/pybase64-1.4.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:da1ee8fa04b283873de2d6e8fa5653e827f55b86bdf1a929c5367aaeb8d26f8a", size = 65399 }, + { url = "https://files.pythonhosted.org/packages/6b/08/476ac5914c3b32e0274a2524fc74f01cbf4f4af4513d054e41574eb018f6/pybase64-1.4.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:b0bf8e884ee822ca7b1448eeb97fa131628fe0ff42f60cae9962789bd562727f", size = 60487 }, + { url = "https://files.pythonhosted.org/packages/f1/b8/618a92915330cc9cba7880299b546a1d9dab1a21fd6c0292ee44a4fe608c/pybase64-1.4.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1bf749300382a6fd1f4f255b183146ef58f8e9cb2f44a077b3a9200dfb473a77", size = 63959 }, + { url = "https://files.pythonhosted.org/packages/a5/52/af9d8d051652c3051862c442ec3861259c5cdb3fc69774bc701470bd2a59/pybase64-1.4.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:153a0e42329b92337664cfc356f2065248e6c9a1bd651bbcd6dcaf15145d3f06", size = 64874 }, + { url = "https://files.pythonhosted.org/packages/e4/51/5381a7adf1f381bd184d33203692d3c57cf8ae9f250f380c3fecbdbe554b/pybase64-1.4.3-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:86ee56ac7f2184ca10217ed1c655c1a060273e233e692e9086da29d1ae1768db", size = 58572 }, + { url = "https://files.pythonhosted.org/packages/e0/f0/578ee4ffce5818017de4fdf544e066c225bc435e73eb4793cde28a689d0b/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0e71a4db76726bf830b47477e7d830a75c01b2e9b01842e787a0836b0ba741e3", size = 63636 }, + { url = "https://files.pythonhosted.org/packages/b9/ad/8ae94814bf20159ea06310b742433e53d5820aa564c9fdf65bf2d79f8799/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2ba7799ec88540acd9861b10551d24656ca3c2888ecf4dba2ee0a71544a8923f", size = 56193 }, + { url = "https://files.pythonhosted.org/packages/d1/31/6438cfcc3d3f0fa84d229fa125c243d5094e72628e525dfefadf3bcc6761/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2860299e4c74315f5951f0cf3e72ba0f201c3356c8a68f95a3ab4e620baf44e9", size = 72655 }, + { url = "https://files.pythonhosted.org/packages/a3/0d/2bbc9e9c3fc12ba8a6e261482f03a544aca524f92eae0b4908c0a10ba481/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:bb06015db9151f0c66c10aae8e3603adab6b6cd7d1f7335a858161d92fc29618", size = 62471 }, + { url = "https://files.pythonhosted.org/packages/2c/0b/34d491e7f49c1dbdb322ea8da6adecda7c7cd70b6644557c6e4ca5c6f7c7/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:242512a070817272865d37c8909059f43003b81da31f616bb0c391ceadffe067", size = 58119 }, + { url = "https://files.pythonhosted.org/packages/ce/17/c21d0cde2a6c766923ae388fc1f78291e1564b0d38c814b5ea8a0e5e081c/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5d8277554a12d3e3eed6180ebda62786bf9fc8d7bb1ee00244258f4a87ca8d20", size = 60791 }, + { url = "https://files.pythonhosted.org/packages/92/b2/eaa67038916a48de12b16f4c384bcc1b84b7ec731b23613cb05f27673294/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f40b7ddd698fc1e13a4b64fbe405e4e0e1279e8197e37050e24154655f5f7c4e", size = 74701 }, + { url = "https://files.pythonhosted.org/packages/42/10/abb7757c330bb869ebb95dab0c57edf5961ffbd6c095c8209cbbf75d117d/pybase64-1.4.3-cp313-cp313t-win32.whl", hash = "sha256:46d75c9387f354c5172582a9eaae153b53a53afeb9c19fcf764ea7038be3bd8b", size = 33965 }, + { url = "https://files.pythonhosted.org/packages/63/a0/2d4e5a59188e9e6aed0903d580541aaea72dcbbab7bf50fb8b83b490b6c3/pybase64-1.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:d7344625591d281bec54e85cbfdab9e970f6219cac1570f2aa140b8c942ccb81", size = 36207 }, + { url = "https://files.pythonhosted.org/packages/1f/05/95b902e8f567b4d4b41df768ccc438af618f8d111e54deaf57d2df46bd76/pybase64-1.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:28a3c60c55138e0028313f2eccd321fec3c4a0be75e57a8d3eb883730b1b0880", size = 31505 }, + { url = "https://files.pythonhosted.org/packages/e4/80/4bd3dff423e5a91f667ca41982dc0b79495b90ec0c0f5d59aca513e50f8c/pybase64-1.4.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:015bb586a1ea1467f69d57427abe587469392215f59db14f1f5c39b52fdafaf5", size = 33835 }, + { url = "https://files.pythonhosted.org/packages/45/60/a94d94cc1e3057f602e0b483c9ebdaef40911d84a232647a2fe593ab77bb/pybase64-1.4.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:d101e3a516f837c3dcc0e5a0b7db09582ebf99ed670865223123fb2e5839c6c0", size = 40673 }, + { url = "https://files.pythonhosted.org/packages/e3/71/cf62b261d431857e8e054537a5c3c24caafa331de30daede7b2c6c558501/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8f183ac925a48046abe047360fe3a1b28327afb35309892132fe1915d62fb282", size = 30939 }, + { url = "https://files.pythonhosted.org/packages/24/3e/d12f92a3c1f7c6ab5d53c155bff9f1084ba997a37a39a4f781ccba9455f3/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30bf3558e24dcce4da5248dcf6d73792adfcf4f504246967e9db155be4c439ad", size = 31401 }, + { url = "https://files.pythonhosted.org/packages/9b/3d/9c27440031fea0d05146f8b70a460feb95d8b4e3d9ca8f45c972efb4c3d3/pybase64-1.4.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a674b419de318d2ce54387dd62646731efa32b4b590907800f0bd40675c1771d", size = 38075 }, + { url = "https://files.pythonhosted.org/packages/4b/d4/6c0e0cf0efd53c254173fbcd84a3d8fcbf5e0f66622473da425becec32a5/pybase64-1.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:720104fd7303d07bac302be0ff8f7f9f126f2f45c1edb4f48fdb0ff267e69fe1", size = 38257 }, + { url = "https://files.pythonhosted.org/packages/50/eb/27cb0b610d5cd70f5ad0d66c14ad21c04b8db930f7139818e8fbdc14df4d/pybase64-1.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:83f1067f73fa5afbc3efc0565cecc6ed53260eccddef2ebe43a8ce2b99ea0e0a", size = 31685 }, + { url = "https://files.pythonhosted.org/packages/db/26/b136a4b65e5c94ff06217f7726478df3f31ab1c777c2c02cf698e748183f/pybase64-1.4.3-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b51204d349a4b208287a8aa5b5422be3baa88abf6cc8ff97ccbda34919bbc857", size = 68460 }, + { url = "https://files.pythonhosted.org/packages/68/6d/84ce50e7ee1ae79984d689e05a9937b2460d4efa1e5b202b46762fb9036c/pybase64-1.4.3-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:30f2fd53efecbdde4bdca73a872a68dcb0d1bf8a4560c70a3e7746df973e1ef3", size = 71688 }, + { url = "https://files.pythonhosted.org/packages/e3/57/6743e420416c3ff1b004041c85eb0ebd9c50e9cf05624664bfa1dc8b5625/pybase64-1.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0932b0c5cfa617091fd74f17d24549ce5de3628791998c94ba57be808078eeaf", size = 60040 }, + { url = "https://files.pythonhosted.org/packages/3b/68/733324e28068a89119af2921ce548e1c607cc5c17d354690fc51c302e326/pybase64-1.4.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:acb61f5ab72bec808eb0d4ce8b87ec9f38d7d750cb89b1371c35eb8052a29f11", size = 56478 }, + { url = "https://files.pythonhosted.org/packages/b5/9e/f3f4aa8cfe3357a3cdb0535b78eb032b671519d3ecc08c58c4c6b72b5a91/pybase64-1.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:2bc2d5bc15168f5c04c53bdfe5a1e543b2155f456ed1e16d7edce9ce73842021", size = 59463 }, + { url = "https://files.pythonhosted.org/packages/aa/d1/53286038e1f0df1cf58abcf4a4a91b0f74ab44539c2547b6c31001ddd054/pybase64-1.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8a7bc3cd23880bdca59758bcdd6f4ef0674f2393782763910a7466fab35ccb98", size = 60360 }, + { url = "https://files.pythonhosted.org/packages/00/9a/5cc6ce95db2383d27ff4d790b8f8b46704d360d701ab77c4f655bcfaa6a7/pybase64-1.4.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ad15acf618880d99792d71e3905b0e2508e6e331b76a1b34212fa0f11e01ad28", size = 54999 }, + { url = "https://files.pythonhosted.org/packages/64/e7/c3c1d09c3d7ae79e3aa1358c6d912d6b85f29281e47aa94fc0122a415a2f/pybase64-1.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448158d417139cb4851200e5fee62677ae51f56a865d50cda9e0d61bda91b116", size = 58736 }, + { url = "https://files.pythonhosted.org/packages/db/d5/0baa08e3d8119b15b588c39f0d39fd10472f0372e3c54ca44649cbefa256/pybase64-1.4.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9058c49b5a2f3e691b9db21d37eb349e62540f9f5fc4beabf8cbe3c732bead86", size = 52298 }, + { url = "https://files.pythonhosted.org/packages/00/87/fc6f11474a1de7e27cd2acbb8d0d7508bda3efa73dfe91c63f968728b2a3/pybase64-1.4.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ce561724f6522907a66303aca27dce252d363fcd85884972d348f4403ba3011a", size = 69049 }, + { url = "https://files.pythonhosted.org/packages/69/9d/7fb5566f669ac18b40aa5fc1c438e24df52b843c1bdc5da47d46d4c1c630/pybase64-1.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:63316560a94ac449fe86cb8b9e0a13714c659417e92e26a5cbf085cd0a0c838d", size = 57952 }, + { url = "https://files.pythonhosted.org/packages/de/cc/ceb949232dbbd3ec4ee0190d1df4361296beceee9840390a63df8bc31784/pybase64-1.4.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7ecd796f2ac0be7b73e7e4e232b8c16422014de3295d43e71d2b19fd4a4f5368", size = 54484 }, + { url = "https://files.pythonhosted.org/packages/a7/69/659f3c8e6a5d7b753b9c42a4bd9c42892a0f10044e9c7351a4148d413a33/pybase64-1.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d01e102a12fb2e1ed3dc11611c2818448626637857ec3994a9cf4809dfd23477", size = 56542 }, + { url = "https://files.pythonhosted.org/packages/85/2c/29c9e6c9c82b72025f9676f9e82eb1fd2339ad038cbcbf8b9e2ac02798fc/pybase64-1.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ebff797a93c2345f22183f454fd8607a34d75eca5a3a4a969c1c75b304cee39d", size = 71045 }, + { url = "https://files.pythonhosted.org/packages/b9/84/5a3dce8d7a0040a5c0c14f0fe1311cd8db872913fa04438071b26b0dac04/pybase64-1.4.3-cp314-cp314-win32.whl", hash = "sha256:28b2a1bb0828c0595dc1ea3336305cd97ff85b01c00d81cfce4f92a95fb88f56", size = 34200 }, + { url = "https://files.pythonhosted.org/packages/57/bc/ce7427c12384adee115b347b287f8f3cf65860b824d74fe2c43e37e81c1f/pybase64-1.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:33338d3888700ff68c3dedfcd49f99bfc3b887570206130926791e26b316b029", size = 36323 }, + { url = "https://files.pythonhosted.org/packages/9a/1b/2b8ffbe9a96eef7e3f6a5a7be75995eebfb6faaedc85b6da6b233e50c778/pybase64-1.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:62725669feb5acb186458da2f9353e88ae28ef66bb9c4c8d1568b12a790dfa94", size = 31584 }, + { url = "https://files.pythonhosted.org/packages/ac/d8/6824c2e6fb45b8fa4e7d92e3c6805432d5edc7b855e3e8e1eedaaf6efb7c/pybase64-1.4.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:153fe29be038948d9372c3e77ae7d1cab44e4ba7d9aaf6f064dbeea36e45b092", size = 38601 }, + { url = "https://files.pythonhosted.org/packages/ea/e5/10d2b3a4ad3a4850be2704a2f70cd9c0cf55725c8885679872d3bc846c67/pybase64-1.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f7fe3decaa7c4a9e162327ec7bd81ce183d2b16f23c6d53b606649c6e0203e9e", size = 32078 }, + { url = "https://files.pythonhosted.org/packages/43/04/8b15c34d3c2282f1c1b0850f1113a249401b618a382646a895170bc9b5e7/pybase64-1.4.3-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a5ae04ea114c86eb1da1f6e18d75f19e3b5ae39cb1d8d3cd87c29751a6a22780", size = 72474 }, + { url = "https://files.pythonhosted.org/packages/42/00/f34b4d11278f8fdc68bc38f694a91492aa318f7c6f1bd7396197ac0f8b12/pybase64-1.4.3-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1755b3dce3a2a5c7d17ff6d4115e8bee4a1d5aeae74469db02e47c8f477147da", size = 75706 }, + { url = "https://files.pythonhosted.org/packages/bb/5d/71747d4ad7fe16df4c4c852bdbdeb1f2cf35677b48d7c34d3011a7a6ad3a/pybase64-1.4.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb852f900e27ffc4ec1896817535a0fa19610ef8875a096b59f21d0aa42ff172", size = 65589 }, + { url = "https://files.pythonhosted.org/packages/49/b1/d1e82bd58805bb5a3a662864800bab83a83a36ba56e7e3b1706c708002a5/pybase64-1.4.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:9cf21ea8c70c61eddab3421fbfce061fac4f2fb21f7031383005a1efdb13d0b9", size = 60670 }, + { url = "https://files.pythonhosted.org/packages/15/67/16c609b7a13d1d9fc87eca12ba2dce5e67f949eeaab61a41bddff843cbb0/pybase64-1.4.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:afff11b331fdc27692fc75e85ae083340a35105cea1a3c4552139e2f0e0d174f", size = 64194 }, + { url = "https://files.pythonhosted.org/packages/3c/11/37bc724e42960f0106c2d33dc957dcec8f760c91a908cc6c0df7718bc1a8/pybase64-1.4.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9a5143df542c1ce5c1f423874b948c4d689b3f05ec571f8792286197a39ba02", size = 64984 }, + { url = "https://files.pythonhosted.org/packages/6e/66/b2b962a6a480dd5dae3029becf03ea1a650d326e39bf1c44ea3db78bb010/pybase64-1.4.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:d62e9861019ad63624b4a7914dff155af1cc5d6d79df3be14edcaedb5fdad6f9", size = 58750 }, + { url = "https://files.pythonhosted.org/packages/2b/15/9b6d711035e29b18b2e1c03d47f41396d803d06ef15b6c97f45b75f73f04/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:84cfd4d92668ef5766cc42a9c9474b88960ac2b860767e6e7be255c6fddbd34a", size = 63816 }, + { url = "https://files.pythonhosted.org/packages/b4/21/e2901381ed0df62e2308380f30d9c4d87d6b74e33a84faed3478d33a7197/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:60fc025437f9a7c2cc45e0c19ed68ed08ba672be2c5575fd9d98bdd8f01dd61f", size = 56348 }, + { url = "https://files.pythonhosted.org/packages/c4/16/3d788388a178a0407aa814b976fe61bfa4af6760d9aac566e59da6e4a8b4/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:edc8446196f04b71d3af76c0bd1fe0a45066ac5bffecca88adb9626ee28c266f", size = 72842 }, + { url = "https://files.pythonhosted.org/packages/a6/63/c15b1f8bd47ea48a5a2d52a4ec61f037062932ea6434ab916107b58e861e/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e99f6fa6509c037794da57f906ade271f52276c956d00f748e5b118462021d48", size = 62651 }, + { url = "https://files.pythonhosted.org/packages/bd/b8/f544a2e37c778d59208966d4ef19742a0be37c12fc8149ff34483c176616/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d94020ef09f624d841aa9a3a6029df8cf65d60d7a6d5c8687579fa68bd679b65", size = 58295 }, + { url = "https://files.pythonhosted.org/packages/03/99/1fae8a3b7ac181e36f6e7864a62d42d5b1f4fa7edf408c6711e28fba6b4d/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f64ce70d89942a23602dee910dec9b48e5edf94351e1b378186b74fcc00d7f66", size = 60960 }, + { url = "https://files.pythonhosted.org/packages/9d/9e/cd4c727742345ad8384569a4466f1a1428f4e5cc94d9c2ab2f53d30be3fe/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8ea99f56e45c469818b9781903be86ba4153769f007ba0655fa3b46dc332803d", size = 74863 }, + { url = "https://files.pythonhosted.org/packages/28/86/a236ecfc5b494e1e922da149689f690abc84248c7c1358f5605b8c9fdd60/pybase64-1.4.3-cp314-cp314t-win32.whl", hash = "sha256:343b1901103cc72362fd1f842524e3bb24978e31aea7ff11e033af7f373f66ab", size = 34513 }, + { url = "https://files.pythonhosted.org/packages/56/ce/ca8675f8d1352e245eb012bfc75429ee9cf1f21c3256b98d9a329d44bf0f/pybase64-1.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:57aff6f7f9dea6705afac9d706432049642de5b01080d3718acc23af87c5af76", size = 36702 }, + { url = "https://files.pythonhosted.org/packages/3b/30/4a675864877397179b09b720ee5fcb1cf772cf7bebc831989aff0a5f79c1/pybase64-1.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e906aa08d4331e799400829e0f5e4177e76a3281e8a4bc82ba114c6b30e405c9", size = 31904 }, ] [[package]] name = "pyclipper" version = "1.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/21/3c06205bb407e1f79b73b7b4dfb3950bd9537c4f625a68ab5cc41177f5bc/pyclipper-1.4.0.tar.gz", hash = "sha256:9882bd889f27da78add4dd6f881d25697efc740bf840274e749988d25496c8e1", size = 54489, upload-time = "2025-12-01T13:15:35.015Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/21/3c06205bb407e1f79b73b7b4dfb3950bd9537c4f625a68ab5cc41177f5bc/pyclipper-1.4.0.tar.gz", hash = "sha256:9882bd889f27da78add4dd6f881d25697efc740bf840274e749988d25496c8e1", size = 54489 } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/1b/7a07b68e0842324d46c03e512d8eefa9cb92ba2a792b3b4ebf939dafcac3/pyclipper-1.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:222ac96c8b8281b53d695b9c4fedc674f56d6d4320ad23f1bdbd168f4e316140", size = 265676, upload-time = "2025-12-01T13:15:04.15Z" }, - { url = "https://files.pythonhosted.org/packages/6b/dd/8bd622521c05d04963420ae6664093f154343ed044c53ea260a310c8bb4d/pyclipper-1.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f3672dbafbb458f1b96e1ee3e610d174acb5ace5bd2ed5d1252603bb797f2fc6", size = 140458, upload-time = "2025-12-01T13:15:05.76Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/6e3e241882bf7d6ab23d9c69ba4e85f1ec47397cbbeee948a16cf75e21ed/pyclipper-1.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1f807e2b4760a8e5c6d6b4e8c1d71ef52b7fe1946ff088f4fa41e16a881a5ca", size = 978235, upload-time = "2025-12-01T13:15:06.993Z" }, - { url = "https://files.pythonhosted.org/packages/cf/f4/3418c1cd5eea640a9fa2501d4bc0b3655fa8d40145d1a4f484b987990a75/pyclipper-1.4.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce1f83c9a4e10ea3de1959f0ae79e9a5bd41346dff648fee6228ba9eaf8b3872", size = 961388, upload-time = "2025-12-01T13:15:08.467Z" }, - { url = "https://files.pythonhosted.org/packages/ac/94/c85401d24be634af529c962dd5d781f3cb62a67cd769534df2cb3feee97a/pyclipper-1.4.0-cp312-cp312-win32.whl", hash = "sha256:3ef44b64666ebf1cb521a08a60c3e639d21b8c50bfbe846ba7c52a0415e936f4", size = 95169, upload-time = "2025-12-01T13:15:10.098Z" }, - { url = "https://files.pythonhosted.org/packages/97/77/dfea08e3b230b82ee22543c30c35d33d42f846a77f96caf7c504dd54fab1/pyclipper-1.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:d1e5498d883b706a4ce636247f0d830c6eb34a25b843a1b78e2c969754ca9037", size = 104619, upload-time = "2025-12-01T13:15:11.592Z" }, - { url = "https://files.pythonhosted.org/packages/67/d0/cbce7d47de1e6458f66a4d999b091640134deb8f2c7351eab993b70d2e10/pyclipper-1.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d49df13cbb2627ccb13a1046f3ea6ebf7177b5504ec61bdef87d6a704046fd6e", size = 264342, upload-time = "2025-12-01T13:15:12.697Z" }, - { url = "https://files.pythonhosted.org/packages/ce/cc/742b9d69d96c58ac156947e1b56d0f81cbacbccf869e2ac7229f2f86dc4e/pyclipper-1.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37bfec361e174110cdddffd5ecd070a8064015c99383d95eb692c253951eee8a", size = 139839, upload-time = "2025-12-01T13:15:13.911Z" }, - { url = "https://files.pythonhosted.org/packages/db/48/dd301d62c1529efdd721b47b9e5fb52120fcdac5f4d3405cfc0d2f391414/pyclipper-1.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:14c8bdb5a72004b721c4e6f448d2c2262d74a7f0c9e3076aeff41e564a92389f", size = 972142, upload-time = "2025-12-01T13:15:15.477Z" }, - { url = "https://files.pythonhosted.org/packages/07/bf/d493fd1b33bb090fa64e28c1009374d5d72fa705f9331cd56517c35e381e/pyclipper-1.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2a50c22c3a78cb4e48347ecf06930f61ce98cf9252f2e292aa025471e9d75b1", size = 952789, upload-time = "2025-12-01T13:15:17.042Z" }, - { url = "https://files.pythonhosted.org/packages/cf/88/b95ea8ea21ddca34aa14b123226a81526dd2faaa993f9aabd3ed21231604/pyclipper-1.4.0-cp313-cp313-win32.whl", hash = "sha256:c9a3faa416ff536cee93417a72bfb690d9dea136dc39a39dbbe1e5dadf108c9c", size = 94817, upload-time = "2025-12-01T13:15:18.724Z" }, - { url = "https://files.pythonhosted.org/packages/ba/42/0a1920d276a0e1ca21dc0d13ee9e3ba10a9a8aa3abac76cd5e5a9f503306/pyclipper-1.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:d4b2d7c41086f1927d14947c563dfc7beed2f6c0d9af13c42fe3dcdc20d35832", size = 104007, upload-time = "2025-12-01T13:15:19.763Z" }, - { url = "https://files.pythonhosted.org/packages/1a/20/04d58c70f3ccd404f179f8dd81d16722a05a3bf1ab61445ee64e8218c1f8/pyclipper-1.4.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7c87480fc91a5af4c1ba310bdb7de2f089a3eeef5fe351a3cedc37da1fcced1c", size = 265167, upload-time = "2025-12-01T13:15:20.844Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2e/a570c1abe69b7260ca0caab4236ce6ea3661193ebf8d1bd7f78ccce537a5/pyclipper-1.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81d8bb2d1fb9d66dc7ea4373b176bb4b02443a7e328b3b603a73faec088b952e", size = 139966, upload-time = "2025-12-01T13:15:22.036Z" }, - { url = "https://files.pythonhosted.org/packages/e8/3b/e0859e54adabdde8a24a29d3f525ebb31c71ddf2e8d93edce83a3c212ffc/pyclipper-1.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:773c0e06b683214dcfc6711be230c83b03cddebe8a57eae053d4603dd63582f9", size = 968216, upload-time = "2025-12-01T13:15:23.18Z" }, - { url = "https://files.pythonhosted.org/packages/f6/6b/e3c4febf0a35ae643ee579b09988dd931602b5bf311020535fd9e5b7e715/pyclipper-1.4.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bc45f2463d997848450dbed91c950ca37c6cf27f84a49a5cad4affc0b469e39", size = 954198, upload-time = "2025-12-01T13:15:24.522Z" }, - { url = "https://files.pythonhosted.org/packages/fc/74/728efcee02e12acb486ce9d56fa037120c9bf5b77c54bbdbaa441c14a9d9/pyclipper-1.4.0-cp314-cp314-win32.whl", hash = "sha256:0b8c2105b3b3c44dbe1a266f64309407fe30bf372cf39a94dc8aaa97df00da5b", size = 96951, upload-time = "2025-12-01T13:15:25.79Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d7/7f4354e69f10a917e5c7d5d72a499ef2e10945312f5e72c414a0a08d2ae4/pyclipper-1.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:6c317e182590c88ec0194149995e3d71a979cfef3b246383f4e035f9d4a11826", size = 106782, upload-time = "2025-12-01T13:15:26.945Z" }, - { url = "https://files.pythonhosted.org/packages/63/60/fc32c7a3d7f61a970511ec2857ecd09693d8ac80d560ee7b8e67a6d268c9/pyclipper-1.4.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f160a2c6ba036f7eaf09f1f10f4fbfa734234af9112fb5187877efed78df9303", size = 269880, upload-time = "2025-12-01T13:15:28.117Z" }, - { url = "https://files.pythonhosted.org/packages/49/df/c4a72d3f62f0ba03ec440c4fff56cd2d674a4334d23c5064cbf41c9583f6/pyclipper-1.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a9f11ad133257c52c40d50de7a0ca3370a0cdd8e3d11eec0604ad3c34ba549e9", size = 141706, upload-time = "2025-12-01T13:15:30.134Z" }, - { url = "https://files.pythonhosted.org/packages/c5/0b/cf55df03e2175e1e2da9db585241401e0bc98f76bee3791bed39d0313449/pyclipper-1.4.0-cp314-cp314t-win32.whl", hash = "sha256:bbc827b77442c99deaeee26e0e7f172355ddb097a5e126aea206d447d3b26286", size = 105308, upload-time = "2025-12-01T13:15:31.225Z" }, - { url = "https://files.pythonhosted.org/packages/8f/dc/53df8b6931d47080b4fe4ee8450d42e660ee1c5c1556c7ab73359182b769/pyclipper-1.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29dae3e0296dff8502eeb7639fcfee794b0eec8590ba3563aee28db269da6b04", size = 117608, upload-time = "2025-12-01T13:15:32.69Z" }, + { url = "https://files.pythonhosted.org/packages/90/1b/7a07b68e0842324d46c03e512d8eefa9cb92ba2a792b3b4ebf939dafcac3/pyclipper-1.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:222ac96c8b8281b53d695b9c4fedc674f56d6d4320ad23f1bdbd168f4e316140", size = 265676 }, + { url = "https://files.pythonhosted.org/packages/6b/dd/8bd622521c05d04963420ae6664093f154343ed044c53ea260a310c8bb4d/pyclipper-1.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f3672dbafbb458f1b96e1ee3e610d174acb5ace5bd2ed5d1252603bb797f2fc6", size = 140458 }, + { url = "https://files.pythonhosted.org/packages/7a/06/6e3e241882bf7d6ab23d9c69ba4e85f1ec47397cbbeee948a16cf75e21ed/pyclipper-1.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1f807e2b4760a8e5c6d6b4e8c1d71ef52b7fe1946ff088f4fa41e16a881a5ca", size = 978235 }, + { url = "https://files.pythonhosted.org/packages/cf/f4/3418c1cd5eea640a9fa2501d4bc0b3655fa8d40145d1a4f484b987990a75/pyclipper-1.4.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce1f83c9a4e10ea3de1959f0ae79e9a5bd41346dff648fee6228ba9eaf8b3872", size = 961388 }, + { url = "https://files.pythonhosted.org/packages/ac/94/c85401d24be634af529c962dd5d781f3cb62a67cd769534df2cb3feee97a/pyclipper-1.4.0-cp312-cp312-win32.whl", hash = "sha256:3ef44b64666ebf1cb521a08a60c3e639d21b8c50bfbe846ba7c52a0415e936f4", size = 95169 }, + { url = "https://files.pythonhosted.org/packages/97/77/dfea08e3b230b82ee22543c30c35d33d42f846a77f96caf7c504dd54fab1/pyclipper-1.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:d1e5498d883b706a4ce636247f0d830c6eb34a25b843a1b78e2c969754ca9037", size = 104619 }, + { url = "https://files.pythonhosted.org/packages/67/d0/cbce7d47de1e6458f66a4d999b091640134deb8f2c7351eab993b70d2e10/pyclipper-1.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d49df13cbb2627ccb13a1046f3ea6ebf7177b5504ec61bdef87d6a704046fd6e", size = 264342 }, + { url = "https://files.pythonhosted.org/packages/ce/cc/742b9d69d96c58ac156947e1b56d0f81cbacbccf869e2ac7229f2f86dc4e/pyclipper-1.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37bfec361e174110cdddffd5ecd070a8064015c99383d95eb692c253951eee8a", size = 139839 }, + { url = "https://files.pythonhosted.org/packages/db/48/dd301d62c1529efdd721b47b9e5fb52120fcdac5f4d3405cfc0d2f391414/pyclipper-1.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:14c8bdb5a72004b721c4e6f448d2c2262d74a7f0c9e3076aeff41e564a92389f", size = 972142 }, + { url = "https://files.pythonhosted.org/packages/07/bf/d493fd1b33bb090fa64e28c1009374d5d72fa705f9331cd56517c35e381e/pyclipper-1.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2a50c22c3a78cb4e48347ecf06930f61ce98cf9252f2e292aa025471e9d75b1", size = 952789 }, + { url = "https://files.pythonhosted.org/packages/cf/88/b95ea8ea21ddca34aa14b123226a81526dd2faaa993f9aabd3ed21231604/pyclipper-1.4.0-cp313-cp313-win32.whl", hash = "sha256:c9a3faa416ff536cee93417a72bfb690d9dea136dc39a39dbbe1e5dadf108c9c", size = 94817 }, + { url = "https://files.pythonhosted.org/packages/ba/42/0a1920d276a0e1ca21dc0d13ee9e3ba10a9a8aa3abac76cd5e5a9f503306/pyclipper-1.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:d4b2d7c41086f1927d14947c563dfc7beed2f6c0d9af13c42fe3dcdc20d35832", size = 104007 }, + { url = "https://files.pythonhosted.org/packages/1a/20/04d58c70f3ccd404f179f8dd81d16722a05a3bf1ab61445ee64e8218c1f8/pyclipper-1.4.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7c87480fc91a5af4c1ba310bdb7de2f089a3eeef5fe351a3cedc37da1fcced1c", size = 265167 }, + { url = "https://files.pythonhosted.org/packages/bd/2e/a570c1abe69b7260ca0caab4236ce6ea3661193ebf8d1bd7f78ccce537a5/pyclipper-1.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81d8bb2d1fb9d66dc7ea4373b176bb4b02443a7e328b3b603a73faec088b952e", size = 139966 }, + { url = "https://files.pythonhosted.org/packages/e8/3b/e0859e54adabdde8a24a29d3f525ebb31c71ddf2e8d93edce83a3c212ffc/pyclipper-1.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:773c0e06b683214dcfc6711be230c83b03cddebe8a57eae053d4603dd63582f9", size = 968216 }, + { url = "https://files.pythonhosted.org/packages/f6/6b/e3c4febf0a35ae643ee579b09988dd931602b5bf311020535fd9e5b7e715/pyclipper-1.4.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bc45f2463d997848450dbed91c950ca37c6cf27f84a49a5cad4affc0b469e39", size = 954198 }, + { url = "https://files.pythonhosted.org/packages/fc/74/728efcee02e12acb486ce9d56fa037120c9bf5b77c54bbdbaa441c14a9d9/pyclipper-1.4.0-cp314-cp314-win32.whl", hash = "sha256:0b8c2105b3b3c44dbe1a266f64309407fe30bf372cf39a94dc8aaa97df00da5b", size = 96951 }, + { url = "https://files.pythonhosted.org/packages/e3/d7/7f4354e69f10a917e5c7d5d72a499ef2e10945312f5e72c414a0a08d2ae4/pyclipper-1.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:6c317e182590c88ec0194149995e3d71a979cfef3b246383f4e035f9d4a11826", size = 106782 }, + { url = "https://files.pythonhosted.org/packages/63/60/fc32c7a3d7f61a970511ec2857ecd09693d8ac80d560ee7b8e67a6d268c9/pyclipper-1.4.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f160a2c6ba036f7eaf09f1f10f4fbfa734234af9112fb5187877efed78df9303", size = 269880 }, + { url = "https://files.pythonhosted.org/packages/49/df/c4a72d3f62f0ba03ec440c4fff56cd2d674a4334d23c5064cbf41c9583f6/pyclipper-1.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a9f11ad133257c52c40d50de7a0ca3370a0cdd8e3d11eec0604ad3c34ba549e9", size = 141706 }, + { url = "https://files.pythonhosted.org/packages/c5/0b/cf55df03e2175e1e2da9db585241401e0bc98f76bee3791bed39d0313449/pyclipper-1.4.0-cp314-cp314t-win32.whl", hash = "sha256:bbc827b77442c99deaeee26e0e7f172355ddb097a5e126aea206d447d3b26286", size = 105308 }, + { url = "https://files.pythonhosted.org/packages/8f/dc/53df8b6931d47080b4fe4ee8450d42e660ee1c5c1556c7ab73359182b769/pyclipper-1.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29dae3e0296dff8502eeb7639fcfee794b0eec8590ba3563aee28db269da6b04", size = 117608 }, ] [[package]] name = "pycparser" version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172 }, ] [[package]] @@ -6517,9 +6504,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580 }, ] [[package]] @@ -6529,68 +6516,64 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990 }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003 }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200 }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578 }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504 }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816 }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366 }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698 }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603 }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591 }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068 }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908 }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145 }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179 }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403 }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206 }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307 }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258 }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917 }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186 }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164 }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146 }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788 }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133 }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852 }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679 }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766 }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005 }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622 }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725 }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040 }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691 }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897 }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302 }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877 }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680 }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960 }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102 }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039 }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126 }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489 }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288 }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255 }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760 }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092 }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385 }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832 }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585 }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078 }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914 }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560 }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244 }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955 }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906 }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607 }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769 }, ] [[package]] @@ -6602,9 +6585,9 @@ dependencies = [ { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826 } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929 }, ] [[package]] @@ -6614,27 +6597,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/04/e7c1fe4dc78a6fdbfd6c337b1c3732ff543b8a397683ab38378447baa331/pyee-13.0.1.tar.gz", hash = "sha256:0b931f7c14535667ed4c7e0d531716368715e860b988770fc7eb8578d1f67fc8", size = 31655, upload-time = "2026-02-14T21:12:28.044Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/04/e7c1fe4dc78a6fdbfd6c337b1c3732ff543b8a397683ab38378447baa331/pyee-13.0.1.tar.gz", hash = "sha256:0b931f7c14535667ed4c7e0d531716368715e860b988770fc7eb8578d1f67fc8", size = 31655 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl", hash = "sha256:af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228", size = 15659, upload-time = "2026-02-14T21:12:26.263Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl", hash = "sha256:af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228", size = 15659 }, ] [[package]] name = "pygments" version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, ] [[package]] name = "pyjwt" version = "2.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726 }, ] [package.optional-dependencies] @@ -6646,7 +6629,7 @@ crypto = [ name = "pylatexenc" version = "2.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5d/ab/34ec41718af73c00119d0351b7a2531d2ebddb51833a36448fc7b862be60/pylatexenc-2.10.tar.gz", hash = "sha256:3dd8fd84eb46dc30bee1e23eaab8d8fb5a7f507347b23e5f38ad9675c84f40d3", size = 162597, upload-time = "2021-04-06T07:56:07.854Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/ab/34ec41718af73c00119d0351b7a2531d2ebddb51833a36448fc7b862be60/pylatexenc-2.10.tar.gz", hash = "sha256:3dd8fd84eb46dc30bee1e23eaab8d8fb5a7f507347b23e5f38ad9675c84f40d3", size = 162597 } [[package]] name = "pymilvus" @@ -6662,9 +6645,9 @@ dependencies = [ { name = "requests" }, { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/e6/0adc3b374f5c5d1eebd4f551b455c6865c449b170b17545001b208e2b153/pymilvus-2.6.11.tar.gz", hash = "sha256:a40c10322cde25184a8c3d84993a14dfb67ad2bdcfc5dff7e68b11a79ff8f6d8", size = 1583634, upload-time = "2026-03-27T06:25:46.023Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/e6/0adc3b374f5c5d1eebd4f551b455c6865c449b170b17545001b208e2b153/pymilvus-2.6.11.tar.gz", hash = "sha256:a40c10322cde25184a8c3d84993a14dfb67ad2bdcfc5dff7e68b11a79ff8f6d8", size = 1583634 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/1c/bccb331d71f824738f80f11e9b8b4da47973c903826355526ae4fa2b762f/pymilvus-2.6.11-py3-none-any.whl", hash = "sha256:a11e1718b15045361c71ca671b959900cb7e2faae863c896f6b7e87bf2e4d10a", size = 315252, upload-time = "2026-03-27T06:25:44.215Z" }, + { url = "https://files.pythonhosted.org/packages/9c/1c/bccb331d71f824738f80f11e9b8b4da47973c903826355526ae4fa2b762f/pymilvus-2.6.11-py3-none-any.whl", hash = "sha256:a11e1718b15045361c71ca671b959900cb7e2faae863c896f6b7e87bf2e4d10a", size = 315252 }, ] [[package]] @@ -6674,61 +6657,61 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dnspython" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/65/9c/a4895c4b785fc9865a84a56e14b5bd21ca75aadc3dab79c14187cdca189b/pymongo-4.16.0.tar.gz", hash = "sha256:8ba8405065f6e258a6f872fe62d797a28f383a12178c7153c01ed04e845c600c", size = 2495323, upload-time = "2026-01-07T18:05:48.107Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/9c/a4895c4b785fc9865a84a56e14b5bd21ca75aadc3dab79c14187cdca189b/pymongo-4.16.0.tar.gz", hash = "sha256:8ba8405065f6e258a6f872fe62d797a28f383a12178c7153c01ed04e845c600c", size = 2495323 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/03/6dd7c53cbde98de469a3e6fb893af896dca644c476beb0f0c6342bcc368b/pymongo-4.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bd4911c40a43a821dfd93038ac824b756b6e703e26e951718522d29f6eb166a8", size = 917619, upload-time = "2026-01-07T18:04:19.173Z" }, - { url = "https://files.pythonhosted.org/packages/73/e1/328915f2734ea1f355dc9b0e98505ff670f5fab8be5e951d6ed70971c6aa/pymongo-4.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25a6b03a68f9907ea6ec8bc7cf4c58a1b51a18e23394f962a6402f8e46d41211", size = 917364, upload-time = "2026-01-07T18:04:20.861Z" }, - { url = "https://files.pythonhosted.org/packages/41/fe/4769874dd9812a1bc2880a9785e61eba5340da966af888dd430392790ae0/pymongo-4.16.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:91ac0cb0fe2bf17616c2039dac88d7c9a5088f5cb5829b27c9d250e053664d31", size = 1686901, upload-time = "2026-01-07T18:04:22.219Z" }, - { url = "https://files.pythonhosted.org/packages/fa/8d/15707b9669fdc517bbc552ac60da7124dafe7ac1552819b51e97ed4038b4/pymongo-4.16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf0ec79e8ca7077f455d14d915d629385153b6a11abc0b93283ed73a8013e376", size = 1723034, upload-time = "2026-01-07T18:04:24.055Z" }, - { url = "https://files.pythonhosted.org/packages/5b/af/3d5d16ff11d447d40c1472da1b366a31c7380d7ea2922a449c7f7f495567/pymongo-4.16.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2d0082631a7510318befc2b4fdab140481eb4b9dd62d9245e042157085da2a70", size = 1797161, upload-time = "2026-01-07T18:04:25.964Z" }, - { url = "https://files.pythonhosted.org/packages/fb/04/725ab8664eeec73ec125b5a873448d80f5d8cf2750aaaf804cbc538a50a5/pymongo-4.16.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85dc2f3444c346ea019a371e321ac868a4fab513b7a55fe368f0cc78de8177cc", size = 1780938, upload-time = "2026-01-07T18:04:28.745Z" }, - { url = "https://files.pythonhosted.org/packages/22/50/dd7e9095e1ca35f93c3c844c92eb6eb0bc491caeb2c9bff3b32fe3c9b18f/pymongo-4.16.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dabbf3c14de75a20cc3c30bf0c6527157224a93dfb605838eabb1a2ee3be008d", size = 1714342, upload-time = "2026-01-07T18:04:30.331Z" }, - { url = "https://files.pythonhosted.org/packages/03/c9/542776987d5c31ae8e93e92680ea2b6e5a2295f398b25756234cabf38a39/pymongo-4.16.0-cp312-cp312-win32.whl", hash = "sha256:60307bb91e0ab44e560fe3a211087748b2b5f3e31f403baf41f5b7b0a70bd104", size = 887868, upload-time = "2026-01-07T18:04:32.124Z" }, - { url = "https://files.pythonhosted.org/packages/2e/d4/b4045a7ccc5680fb496d01edf749c7a9367cc8762fbdf7516cf807ef679b/pymongo-4.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:f513b2c6c0d5c491f478422f6b5b5c27ac1af06a54c93ef8631806f7231bd92e", size = 907554, upload-time = "2026-01-07T18:04:33.685Z" }, - { url = "https://files.pythonhosted.org/packages/60/4c/33f75713d50d5247f2258405142c0318ff32c6f8976171c4fcae87a9dbdf/pymongo-4.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:dfc320f08ea9a7ec5b2403dc4e8150636f0d6150f4b9792faaae539c88e7db3b", size = 892971, upload-time = "2026-01-07T18:04:35.594Z" }, - { url = "https://files.pythonhosted.org/packages/47/84/148d8b5da8260f4679d6665196ae04ab14ffdf06f5fe670b0ab11942951f/pymongo-4.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d15f060bc6d0964a8bb70aba8f0cb6d11ae99715438f640cff11bbcf172eb0e8", size = 972009, upload-time = "2026-01-07T18:04:38.303Z" }, - { url = "https://files.pythonhosted.org/packages/1e/5e/9f3a8daf583d0adaaa033a3e3e58194d2282737dc164014ff33c7a081103/pymongo-4.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a19ea46a0fe71248965305a020bc076a163311aefbaa1d83e47d06fa30ac747", size = 971784, upload-time = "2026-01-07T18:04:39.669Z" }, - { url = "https://files.pythonhosted.org/packages/ad/f2/b6c24361fcde24946198573c0176406bfd5f7b8538335f3d939487055322/pymongo-4.16.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:311d4549d6bf1f8c61d025965aebb5ba29d1481dc6471693ab91610aaffbc0eb", size = 1947174, upload-time = "2026-01-07T18:04:41.368Z" }, - { url = "https://files.pythonhosted.org/packages/47/1a/8634192f98cf740b3d174e1018dd0350018607d5bd8ac35a666dc49c732b/pymongo-4.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46ffb728d92dd5b09fc034ed91acf5595657c7ca17d4cf3751322cd554153c17", size = 1991727, upload-time = "2026-01-07T18:04:42.965Z" }, - { url = "https://files.pythonhosted.org/packages/5a/2f/0c47ac84572b28e23028a23a3798a1f725e1c23b0cf1c1424678d16aff42/pymongo-4.16.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:acda193f440dd88c2023cb00aa8bd7b93a9df59978306d14d87a8b12fe426b05", size = 2082497, upload-time = "2026-01-07T18:04:44.652Z" }, - { url = "https://files.pythonhosted.org/packages/ba/57/9f46ef9c862b2f0cf5ce798f3541c201c574128d31ded407ba4b3918d7b6/pymongo-4.16.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d9fdb386cf958e6ef6ff537d6149be7edb76c3268cd6833e6c36aa447e4443f", size = 2064947, upload-time = "2026-01-07T18:04:46.228Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/5421c0998f38e32288100a07f6cb2f5f9f352522157c901910cb2927e211/pymongo-4.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91899dd7fb9a8c50f09c3c1cf0cb73bfbe2737f511f641f19b9650deb61c00ca", size = 1980478, upload-time = "2026-01-07T18:04:48.017Z" }, - { url = "https://files.pythonhosted.org/packages/92/93/bfc448d025e12313a937d6e1e0101b50cc9751636b4b170e600fe3203063/pymongo-4.16.0-cp313-cp313-win32.whl", hash = "sha256:2cd60cd1e05de7f01927f8e25ca26b3ea2c09de8723241e5d3bcfdc70eaff76b", size = 934672, upload-time = "2026-01-07T18:04:49.538Z" }, - { url = "https://files.pythonhosted.org/packages/96/10/12710a5e01218d50c3dd165fd72c5ed2699285f77348a3b1a119a191d826/pymongo-4.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3ead8a0050c53eaa55935895d6919d393d0328ec24b2b9115bdbe881aa222673", size = 959237, upload-time = "2026-01-07T18:04:51.382Z" }, - { url = "https://files.pythonhosted.org/packages/0c/56/d288bcd1d05bc17ec69df1d0b1d67bc710c7c5dbef86033a5a4d2e2b08e6/pymongo-4.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:dbbc5b254c36c37d10abb50e899bc3939bbb7ab1e7c659614409af99bd3e7675", size = 940909, upload-time = "2026-01-07T18:04:52.904Z" }, - { url = "https://files.pythonhosted.org/packages/30/9e/4d343f8d0512002fce17915a89477b9f916bda1205729e042d8f23acf194/pymongo-4.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:8a254d49a9ffe9d7f888e3c677eed3729b14ce85abb08cd74732cead6ccc3c66", size = 1026634, upload-time = "2026-01-07T18:04:54.359Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e3/341f88c5535df40c0450fda915f582757bb7d988cdfc92990a5e27c4c324/pymongo-4.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a1bf44e13cf2d44d2ea2e928a8140d5d667304abe1a61c4d55b4906f389fbe64", size = 1026252, upload-time = "2026-01-07T18:04:56.642Z" }, - { url = "https://files.pythonhosted.org/packages/af/64/9471b22eb98f0a2ca0b8e09393de048502111b2b5b14ab1bd9e39708aab5/pymongo-4.16.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f1c5f1f818b669875d191323a48912d3fcd2e4906410e8297bb09ac50c4d5ccc", size = 2207399, upload-time = "2026-01-07T18:04:58.255Z" }, - { url = "https://files.pythonhosted.org/packages/87/ac/47c4d50b25a02f21764f140295a2efaa583ee7f17992a5e5fa542b3a690f/pymongo-4.16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77cfd37a43a53b02b7bd930457c7994c924ad8bbe8dff91817904bcbf291b371", size = 2260595, upload-time = "2026-01-07T18:04:59.788Z" }, - { url = "https://files.pythonhosted.org/packages/ee/1b/0ce1ce9dd036417646b2fe6f63b58127acff3cf96eeb630c34ec9cd675ff/pymongo-4.16.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:36ef2fee50eee669587d742fb456e349634b4fcf8926208766078b089054b24b", size = 2366958, upload-time = "2026-01-07T18:05:01.942Z" }, - { url = "https://files.pythonhosted.org/packages/3e/3c/a5a17c0d413aa9d6c17bc35c2b472e9e79cda8068ba8e93433b5f43028e9/pymongo-4.16.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55f8d5a6fe2fa0b823674db2293f92d74cd5f970bc0360f409a1fc21003862d3", size = 2346081, upload-time = "2026-01-07T18:05:03.576Z" }, - { url = "https://files.pythonhosted.org/packages/65/19/f815533d1a88fb8a3b6c6e895bb085ffdae68ccb1e6ed7102202a307f8e2/pymongo-4.16.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9caacac0dd105e2555521002e2d17afc08665187017b466b5753e84c016628e6", size = 2246053, upload-time = "2026-01-07T18:05:05.459Z" }, - { url = "https://files.pythonhosted.org/packages/c6/88/4be3ec78828dc64b212c123114bd6ae8db5b7676085a7b43cc75d0131bd2/pymongo-4.16.0-cp314-cp314-win32.whl", hash = "sha256:c789236366525c3ee3cd6e4e450a9ff629a7d1f4d88b8e18a0aea0615fd7ecf8", size = 989461, upload-time = "2026-01-07T18:05:07.018Z" }, - { url = "https://files.pythonhosted.org/packages/af/5a/ab8d5af76421b34db483c9c8ebc3a2199fb80ae63dc7e18f4cf1df46306a/pymongo-4.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b0714d7764efb29bf9d3c51c964aed7c4c7237b341f9346f15ceaf8321fdb35", size = 1017803, upload-time = "2026-01-07T18:05:08.499Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f4/98d68020728ac6423cf02d17cfd8226bf6cce5690b163d30d3f705e8297e/pymongo-4.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:12762e7cc0f8374a8cae3b9f9ed8dabb5d438c7b33329232dd9b7de783454033", size = 997184, upload-time = "2026-01-07T18:05:09.944Z" }, - { url = "https://files.pythonhosted.org/packages/50/00/dc3a271daf06401825b9c1f4f76f018182c7738281ea54b9762aea0560c1/pymongo-4.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1c01e8a7cd0ea66baf64a118005535ab5bf9f9eb63a1b50ac3935dccf9a54abe", size = 1083303, upload-time = "2026-01-07T18:05:11.702Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4b/b5375ee21d12eababe46215011ebc63801c0d2c5ffdf203849d0d79f9852/pymongo-4.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4c4872299ebe315a79f7f922051061634a64fda95b6b17677ba57ef00b2ba2a4", size = 1083233, upload-time = "2026-01-07T18:05:13.182Z" }, - { url = "https://files.pythonhosted.org/packages/ee/e3/52efa3ca900622c7dcb56c5e70f15c906816d98905c22d2ee1f84d9a7b60/pymongo-4.16.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:78037d02389745e247fe5ab0bcad5d1ab30726eaac3ad79219c7d6bbb07eec53", size = 2527438, upload-time = "2026-01-07T18:05:14.981Z" }, - { url = "https://files.pythonhosted.org/packages/cb/96/43b1be151c734e7766c725444bcbfa1de6b60cc66bfb406203746839dd25/pymongo-4.16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c126fb72be2518395cc0465d4bae03125119136462e1945aea19840e45d89cfc", size = 2600399, upload-time = "2026-01-07T18:05:16.794Z" }, - { url = "https://files.pythonhosted.org/packages/e7/62/fa64a5045dfe3a1cd9217232c848256e7bc0136cffb7da4735c5e0d30e40/pymongo-4.16.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f3867dc225d9423c245a51eaac2cfcd53dde8e0a8d8090bb6aed6e31bd6c2d4f", size = 2720960, upload-time = "2026-01-07T18:05:18.498Z" }, - { url = "https://files.pythonhosted.org/packages/54/7b/01577eb97e605502821273a5bc16ce0fb0be5c978fe03acdbff471471202/pymongo-4.16.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f25001a955073b80510c0c3db0e043dbbc36904fd69e511c74e3d8640b8a5111", size = 2699344, upload-time = "2026-01-07T18:05:20.073Z" }, - { url = "https://files.pythonhosted.org/packages/55/68/6ef6372d516f703479c3b6cbbc45a5afd307173b1cbaccd724e23919bb1a/pymongo-4.16.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d9885aad05f82fd7ea0c9ca505d60939746b39263fa273d0125170da8f59098", size = 2577133, upload-time = "2026-01-07T18:05:22.052Z" }, - { url = "https://files.pythonhosted.org/packages/15/c7/b5337093bb01da852f945802328665f85f8109dbe91d81ea2afe5ff059b9/pymongo-4.16.0-cp314-cp314t-win32.whl", hash = "sha256:948152b30eddeae8355495f9943a3bf66b708295c0b9b6f467de1c620f215487", size = 1040560, upload-time = "2026-01-07T18:05:23.888Z" }, - { url = "https://files.pythonhosted.org/packages/96/8c/5b448cd1b103f3889d5713dda37304c81020ff88e38a826e8a75ddff4610/pymongo-4.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f6e42c1bc985d9beee884780ae6048790eb4cd565c46251932906bdb1630034a", size = 1075081, upload-time = "2026-01-07T18:05:26.874Z" }, - { url = "https://files.pythonhosted.org/packages/32/cd/ddc794cdc8500f6f28c119c624252fb6dfb19481c6d7ed150f13cf468a6d/pymongo-4.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6b2a20edb5452ac8daa395890eeb076c570790dfce6b7a44d788af74c2f8cf96", size = 1047725, upload-time = "2026-01-07T18:05:28.47Z" }, + { url = "https://files.pythonhosted.org/packages/6a/03/6dd7c53cbde98de469a3e6fb893af896dca644c476beb0f0c6342bcc368b/pymongo-4.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bd4911c40a43a821dfd93038ac824b756b6e703e26e951718522d29f6eb166a8", size = 917619 }, + { url = "https://files.pythonhosted.org/packages/73/e1/328915f2734ea1f355dc9b0e98505ff670f5fab8be5e951d6ed70971c6aa/pymongo-4.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25a6b03a68f9907ea6ec8bc7cf4c58a1b51a18e23394f962a6402f8e46d41211", size = 917364 }, + { url = "https://files.pythonhosted.org/packages/41/fe/4769874dd9812a1bc2880a9785e61eba5340da966af888dd430392790ae0/pymongo-4.16.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:91ac0cb0fe2bf17616c2039dac88d7c9a5088f5cb5829b27c9d250e053664d31", size = 1686901 }, + { url = "https://files.pythonhosted.org/packages/fa/8d/15707b9669fdc517bbc552ac60da7124dafe7ac1552819b51e97ed4038b4/pymongo-4.16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf0ec79e8ca7077f455d14d915d629385153b6a11abc0b93283ed73a8013e376", size = 1723034 }, + { url = "https://files.pythonhosted.org/packages/5b/af/3d5d16ff11d447d40c1472da1b366a31c7380d7ea2922a449c7f7f495567/pymongo-4.16.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2d0082631a7510318befc2b4fdab140481eb4b9dd62d9245e042157085da2a70", size = 1797161 }, + { url = "https://files.pythonhosted.org/packages/fb/04/725ab8664eeec73ec125b5a873448d80f5d8cf2750aaaf804cbc538a50a5/pymongo-4.16.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85dc2f3444c346ea019a371e321ac868a4fab513b7a55fe368f0cc78de8177cc", size = 1780938 }, + { url = "https://files.pythonhosted.org/packages/22/50/dd7e9095e1ca35f93c3c844c92eb6eb0bc491caeb2c9bff3b32fe3c9b18f/pymongo-4.16.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dabbf3c14de75a20cc3c30bf0c6527157224a93dfb605838eabb1a2ee3be008d", size = 1714342 }, + { url = "https://files.pythonhosted.org/packages/03/c9/542776987d5c31ae8e93e92680ea2b6e5a2295f398b25756234cabf38a39/pymongo-4.16.0-cp312-cp312-win32.whl", hash = "sha256:60307bb91e0ab44e560fe3a211087748b2b5f3e31f403baf41f5b7b0a70bd104", size = 887868 }, + { url = "https://files.pythonhosted.org/packages/2e/d4/b4045a7ccc5680fb496d01edf749c7a9367cc8762fbdf7516cf807ef679b/pymongo-4.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:f513b2c6c0d5c491f478422f6b5b5c27ac1af06a54c93ef8631806f7231bd92e", size = 907554 }, + { url = "https://files.pythonhosted.org/packages/60/4c/33f75713d50d5247f2258405142c0318ff32c6f8976171c4fcae87a9dbdf/pymongo-4.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:dfc320f08ea9a7ec5b2403dc4e8150636f0d6150f4b9792faaae539c88e7db3b", size = 892971 }, + { url = "https://files.pythonhosted.org/packages/47/84/148d8b5da8260f4679d6665196ae04ab14ffdf06f5fe670b0ab11942951f/pymongo-4.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d15f060bc6d0964a8bb70aba8f0cb6d11ae99715438f640cff11bbcf172eb0e8", size = 972009 }, + { url = "https://files.pythonhosted.org/packages/1e/5e/9f3a8daf583d0adaaa033a3e3e58194d2282737dc164014ff33c7a081103/pymongo-4.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a19ea46a0fe71248965305a020bc076a163311aefbaa1d83e47d06fa30ac747", size = 971784 }, + { url = "https://files.pythonhosted.org/packages/ad/f2/b6c24361fcde24946198573c0176406bfd5f7b8538335f3d939487055322/pymongo-4.16.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:311d4549d6bf1f8c61d025965aebb5ba29d1481dc6471693ab91610aaffbc0eb", size = 1947174 }, + { url = "https://files.pythonhosted.org/packages/47/1a/8634192f98cf740b3d174e1018dd0350018607d5bd8ac35a666dc49c732b/pymongo-4.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46ffb728d92dd5b09fc034ed91acf5595657c7ca17d4cf3751322cd554153c17", size = 1991727 }, + { url = "https://files.pythonhosted.org/packages/5a/2f/0c47ac84572b28e23028a23a3798a1f725e1c23b0cf1c1424678d16aff42/pymongo-4.16.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:acda193f440dd88c2023cb00aa8bd7b93a9df59978306d14d87a8b12fe426b05", size = 2082497 }, + { url = "https://files.pythonhosted.org/packages/ba/57/9f46ef9c862b2f0cf5ce798f3541c201c574128d31ded407ba4b3918d7b6/pymongo-4.16.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d9fdb386cf958e6ef6ff537d6149be7edb76c3268cd6833e6c36aa447e4443f", size = 2064947 }, + { url = "https://files.pythonhosted.org/packages/b8/56/5421c0998f38e32288100a07f6cb2f5f9f352522157c901910cb2927e211/pymongo-4.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91899dd7fb9a8c50f09c3c1cf0cb73bfbe2737f511f641f19b9650deb61c00ca", size = 1980478 }, + { url = "https://files.pythonhosted.org/packages/92/93/bfc448d025e12313a937d6e1e0101b50cc9751636b4b170e600fe3203063/pymongo-4.16.0-cp313-cp313-win32.whl", hash = "sha256:2cd60cd1e05de7f01927f8e25ca26b3ea2c09de8723241e5d3bcfdc70eaff76b", size = 934672 }, + { url = "https://files.pythonhosted.org/packages/96/10/12710a5e01218d50c3dd165fd72c5ed2699285f77348a3b1a119a191d826/pymongo-4.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3ead8a0050c53eaa55935895d6919d393d0328ec24b2b9115bdbe881aa222673", size = 959237 }, + { url = "https://files.pythonhosted.org/packages/0c/56/d288bcd1d05bc17ec69df1d0b1d67bc710c7c5dbef86033a5a4d2e2b08e6/pymongo-4.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:dbbc5b254c36c37d10abb50e899bc3939bbb7ab1e7c659614409af99bd3e7675", size = 940909 }, + { url = "https://files.pythonhosted.org/packages/30/9e/4d343f8d0512002fce17915a89477b9f916bda1205729e042d8f23acf194/pymongo-4.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:8a254d49a9ffe9d7f888e3c677eed3729b14ce85abb08cd74732cead6ccc3c66", size = 1026634 }, + { url = "https://files.pythonhosted.org/packages/c3/e3/341f88c5535df40c0450fda915f582757bb7d988cdfc92990a5e27c4c324/pymongo-4.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a1bf44e13cf2d44d2ea2e928a8140d5d667304abe1a61c4d55b4906f389fbe64", size = 1026252 }, + { url = "https://files.pythonhosted.org/packages/af/64/9471b22eb98f0a2ca0b8e09393de048502111b2b5b14ab1bd9e39708aab5/pymongo-4.16.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f1c5f1f818b669875d191323a48912d3fcd2e4906410e8297bb09ac50c4d5ccc", size = 2207399 }, + { url = "https://files.pythonhosted.org/packages/87/ac/47c4d50b25a02f21764f140295a2efaa583ee7f17992a5e5fa542b3a690f/pymongo-4.16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77cfd37a43a53b02b7bd930457c7994c924ad8bbe8dff91817904bcbf291b371", size = 2260595 }, + { url = "https://files.pythonhosted.org/packages/ee/1b/0ce1ce9dd036417646b2fe6f63b58127acff3cf96eeb630c34ec9cd675ff/pymongo-4.16.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:36ef2fee50eee669587d742fb456e349634b4fcf8926208766078b089054b24b", size = 2366958 }, + { url = "https://files.pythonhosted.org/packages/3e/3c/a5a17c0d413aa9d6c17bc35c2b472e9e79cda8068ba8e93433b5f43028e9/pymongo-4.16.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55f8d5a6fe2fa0b823674db2293f92d74cd5f970bc0360f409a1fc21003862d3", size = 2346081 }, + { url = "https://files.pythonhosted.org/packages/65/19/f815533d1a88fb8a3b6c6e895bb085ffdae68ccb1e6ed7102202a307f8e2/pymongo-4.16.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9caacac0dd105e2555521002e2d17afc08665187017b466b5753e84c016628e6", size = 2246053 }, + { url = "https://files.pythonhosted.org/packages/c6/88/4be3ec78828dc64b212c123114bd6ae8db5b7676085a7b43cc75d0131bd2/pymongo-4.16.0-cp314-cp314-win32.whl", hash = "sha256:c789236366525c3ee3cd6e4e450a9ff629a7d1f4d88b8e18a0aea0615fd7ecf8", size = 989461 }, + { url = "https://files.pythonhosted.org/packages/af/5a/ab8d5af76421b34db483c9c8ebc3a2199fb80ae63dc7e18f4cf1df46306a/pymongo-4.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b0714d7764efb29bf9d3c51c964aed7c4c7237b341f9346f15ceaf8321fdb35", size = 1017803 }, + { url = "https://files.pythonhosted.org/packages/f6/f4/98d68020728ac6423cf02d17cfd8226bf6cce5690b163d30d3f705e8297e/pymongo-4.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:12762e7cc0f8374a8cae3b9f9ed8dabb5d438c7b33329232dd9b7de783454033", size = 997184 }, + { url = "https://files.pythonhosted.org/packages/50/00/dc3a271daf06401825b9c1f4f76f018182c7738281ea54b9762aea0560c1/pymongo-4.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1c01e8a7cd0ea66baf64a118005535ab5bf9f9eb63a1b50ac3935dccf9a54abe", size = 1083303 }, + { url = "https://files.pythonhosted.org/packages/b8/4b/b5375ee21d12eababe46215011ebc63801c0d2c5ffdf203849d0d79f9852/pymongo-4.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4c4872299ebe315a79f7f922051061634a64fda95b6b17677ba57ef00b2ba2a4", size = 1083233 }, + { url = "https://files.pythonhosted.org/packages/ee/e3/52efa3ca900622c7dcb56c5e70f15c906816d98905c22d2ee1f84d9a7b60/pymongo-4.16.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:78037d02389745e247fe5ab0bcad5d1ab30726eaac3ad79219c7d6bbb07eec53", size = 2527438 }, + { url = "https://files.pythonhosted.org/packages/cb/96/43b1be151c734e7766c725444bcbfa1de6b60cc66bfb406203746839dd25/pymongo-4.16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c126fb72be2518395cc0465d4bae03125119136462e1945aea19840e45d89cfc", size = 2600399 }, + { url = "https://files.pythonhosted.org/packages/e7/62/fa64a5045dfe3a1cd9217232c848256e7bc0136cffb7da4735c5e0d30e40/pymongo-4.16.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f3867dc225d9423c245a51eaac2cfcd53dde8e0a8d8090bb6aed6e31bd6c2d4f", size = 2720960 }, + { url = "https://files.pythonhosted.org/packages/54/7b/01577eb97e605502821273a5bc16ce0fb0be5c978fe03acdbff471471202/pymongo-4.16.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f25001a955073b80510c0c3db0e043dbbc36904fd69e511c74e3d8640b8a5111", size = 2699344 }, + { url = "https://files.pythonhosted.org/packages/55/68/6ef6372d516f703479c3b6cbbc45a5afd307173b1cbaccd724e23919bb1a/pymongo-4.16.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d9885aad05f82fd7ea0c9ca505d60939746b39263fa273d0125170da8f59098", size = 2577133 }, + { url = "https://files.pythonhosted.org/packages/15/c7/b5337093bb01da852f945802328665f85f8109dbe91d81ea2afe5ff059b9/pymongo-4.16.0-cp314-cp314t-win32.whl", hash = "sha256:948152b30eddeae8355495f9943a3bf66b708295c0b9b6f467de1c620f215487", size = 1040560 }, + { url = "https://files.pythonhosted.org/packages/96/8c/5b448cd1b103f3889d5713dda37304c81020ff88e38a826e8a75ddff4610/pymongo-4.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f6e42c1bc985d9beee884780ae6048790eb4cd565c46251932906bdb1630034a", size = 1075081 }, + { url = "https://files.pythonhosted.org/packages/32/cd/ddc794cdc8500f6f28c119c624252fb6dfb19481c6d7ed150f13cf468a6d/pymongo-4.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6b2a20edb5452ac8daa395890eeb076c570790dfce6b7a44d788af74c2f8cf96", size = 1047725 }, ] [[package]] name = "pyobjc-core" version = "12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532, upload-time = "2025-11-14T10:08:28.292Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532 } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335, upload-time = "2025-11-14T09:32:20.107Z" }, - { url = "https://files.pythonhosted.org/packages/f4/d2/29e5e536adc07bc3d33dd09f3f7cf844bf7b4981820dc2a91dd810f3c782/pyobjc_core-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:01c0cf500596f03e21c23aef9b5f326b9fb1f8f118cf0d8b66749b6cf4cbb37a", size = 677370, upload-time = "2025-11-14T09:33:05.273Z" }, - { url = "https://files.pythonhosted.org/packages/1b/f0/4b4ed8924cd04e425f2a07269943018d43949afad1c348c3ed4d9d032787/pyobjc_core-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:177aaca84bb369a483e4961186704f64b2697708046745f8167e818d968c88fc", size = 719586, upload-time = "2025-11-14T09:33:53.302Z" }, - { url = "https://files.pythonhosted.org/packages/25/98/9f4ed07162de69603144ff480be35cd021808faa7f730d082b92f7ebf2b5/pyobjc_core-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:844515f5d86395b979d02152576e7dee9cc679acc0b32dc626ef5bda315eaa43", size = 670164, upload-time = "2025-11-14T09:34:37.458Z" }, - { url = "https://files.pythonhosted.org/packages/62/50/dc076965c96c7f0de25c0a32b7f8aa98133ed244deaeeacfc758783f1f30/pyobjc_core-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:453b191df1a4b80e756445b935491b974714456ae2cbae816840bd96f86db882", size = 712204, upload-time = "2025-11-14T09:35:24.148Z" }, + { url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335 }, + { url = "https://files.pythonhosted.org/packages/f4/d2/29e5e536adc07bc3d33dd09f3f7cf844bf7b4981820dc2a91dd810f3c782/pyobjc_core-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:01c0cf500596f03e21c23aef9b5f326b9fb1f8f118cf0d8b66749b6cf4cbb37a", size = 677370 }, + { url = "https://files.pythonhosted.org/packages/1b/f0/4b4ed8924cd04e425f2a07269943018d43949afad1c348c3ed4d9d032787/pyobjc_core-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:177aaca84bb369a483e4961186704f64b2697708046745f8167e818d968c88fc", size = 719586 }, + { url = "https://files.pythonhosted.org/packages/25/98/9f4ed07162de69603144ff480be35cd021808faa7f730d082b92f7ebf2b5/pyobjc_core-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:844515f5d86395b979d02152576e7dee9cc679acc0b32dc626ef5bda315eaa43", size = 670164 }, + { url = "https://files.pythonhosted.org/packages/62/50/dc076965c96c7f0de25c0a32b7f8aa98133ed244deaeeacfc758783f1f30/pyobjc_core-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:453b191df1a4b80e756445b935491b974714456ae2cbae816840bd96f86db882", size = 712204 }, ] [[package]] @@ -6738,13 +6721,13 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191 } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590, upload-time = "2025-11-14T09:41:17.336Z" }, - { url = "https://files.pythonhosted.org/packages/ad/31/0c2e734165abb46215797bd830c4bdcb780b699854b15f2b6240515edcc6/pyobjc_framework_cocoa-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5a3dcd491cacc2f5a197142b3c556d8aafa3963011110102a093349017705118", size = 384689, upload-time = "2025-11-14T09:41:41.478Z" }, - { url = "https://files.pythonhosted.org/packages/23/3b/b9f61be7b9f9b4e0a6db18b3c35c4c4d589f2d04e963e2174d38c6555a92/pyobjc_framework_cocoa-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:914b74328c22d8ca261d78c23ef2befc29776e0b85555973927b338c5734ca44", size = 388843, upload-time = "2025-11-14T09:42:05.719Z" }, - { url = "https://files.pythonhosted.org/packages/59/bb/f777cc9e775fc7dae77b569254570fe46eb842516b3e4fe383ab49eab598/pyobjc_framework_cocoa-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:03342a60fc0015bcdf9b93ac0b4f457d3938e9ef761b28df9564c91a14f0129a", size = 384932, upload-time = "2025-11-14T09:42:29.771Z" }, - { url = "https://files.pythonhosted.org/packages/58/27/b457b7b37089cad692c8aada90119162dfb4c4a16f513b79a8b2b022b33b/pyobjc_framework_cocoa-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6ba1dc1bfa4da42d04e93d2363491275fb2e2be5c20790e561c8a9e09b8cf2cc", size = 388970, upload-time = "2025-11-14T09:42:53.964Z" }, + { url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590 }, + { url = "https://files.pythonhosted.org/packages/ad/31/0c2e734165abb46215797bd830c4bdcb780b699854b15f2b6240515edcc6/pyobjc_framework_cocoa-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5a3dcd491cacc2f5a197142b3c556d8aafa3963011110102a093349017705118", size = 384689 }, + { url = "https://files.pythonhosted.org/packages/23/3b/b9f61be7b9f9b4e0a6db18b3c35c4c4d589f2d04e963e2174d38c6555a92/pyobjc_framework_cocoa-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:914b74328c22d8ca261d78c23ef2befc29776e0b85555973927b338c5734ca44", size = 388843 }, + { url = "https://files.pythonhosted.org/packages/59/bb/f777cc9e775fc7dae77b569254570fe46eb842516b3e4fe383ab49eab598/pyobjc_framework_cocoa-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:03342a60fc0015bcdf9b93ac0b4f457d3938e9ef761b28df9564c91a14f0129a", size = 384932 }, + { url = "https://files.pythonhosted.org/packages/58/27/b457b7b37089cad692c8aada90119162dfb4c4a16f513b79a8b2b022b33b/pyobjc_framework_cocoa-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6ba1dc1bfa4da42d04e93d2363491275fb2e2be5c20790e561c8a9e09b8cf2cc", size = 388970 }, ] [[package]] @@ -6755,13 +6738,13 @@ dependencies = [ { name = "pyobjc-core", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, { name = "pyobjc-framework-cocoa", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/2d/baa9ea02cbb1c200683cb7273b69b4bee5070e86f2060b77e6a27c2a9d7e/pyobjc_framework_coreml-12.1.tar.gz", hash = "sha256:0d1a4216891a18775c9e0170d908714c18e4f53f9dc79fb0f5263b2aa81609ba", size = 40465, upload-time = "2025-11-14T10:14:02.265Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/2d/baa9ea02cbb1c200683cb7273b69b4bee5070e86f2060b77e6a27c2a9d7e/pyobjc_framework_coreml-12.1.tar.gz", hash = "sha256:0d1a4216891a18775c9e0170d908714c18e4f53f9dc79fb0f5263b2aa81609ba", size = 40465 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/39/4defef0deb25c5d7e3b7826d301e71ac5b54ef901b7dac4db1adc00f172d/pyobjc_framework_coreml-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10dc8e8db53d7631ebc712cad146e3a9a9a443f4e1a037e844149a24c3c42669", size = 11356, upload-time = "2025-11-14T09:45:52.271Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3f/3749964aa3583f8c30d9996f0d15541120b78d307bb3070f5e47154ef38d/pyobjc_framework_coreml-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:48fa3bb4a03fa23e0e36c93936dca2969598e4102f4b441e1663f535fc99cd31", size = 11371, upload-time = "2025-11-14T09:45:54.105Z" }, - { url = "https://files.pythonhosted.org/packages/9c/c8/cf20ea91ae33f05f3b92dec648c6f44a65f86d1a64c1d6375c95b85ccb7c/pyobjc_framework_coreml-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:71de5b37e6a017e3ed16645c5d6533138f24708da5b56c35c818ae49d0253ee1", size = 11600, upload-time = "2025-11-14T09:45:55.976Z" }, - { url = "https://files.pythonhosted.org/packages/bc/5c/510ae8e3663238d32e653ed6a09ac65611dd045a7241f12633c1ab48bb9b/pyobjc_framework_coreml-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a04a96e512ecf6999aa9e1f60ad5635cb9d1cd839be470341d8d1541797baef6", size = 11418, upload-time = "2025-11-14T09:45:57.75Z" }, - { url = "https://files.pythonhosted.org/packages/d3/1a/b7367819381b07c440fa5797d2b0487e31f09aa72079a693ceab6875fa0a/pyobjc_framework_coreml-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7762b3dd2de01565b7cf3049ce1e4c27341ba179d97016b0b7607448e1c39865", size = 11593, upload-time = "2025-11-14T09:45:59.623Z" }, + { url = "https://files.pythonhosted.org/packages/bb/39/4defef0deb25c5d7e3b7826d301e71ac5b54ef901b7dac4db1adc00f172d/pyobjc_framework_coreml-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10dc8e8db53d7631ebc712cad146e3a9a9a443f4e1a037e844149a24c3c42669", size = 11356 }, + { url = "https://files.pythonhosted.org/packages/ae/3f/3749964aa3583f8c30d9996f0d15541120b78d307bb3070f5e47154ef38d/pyobjc_framework_coreml-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:48fa3bb4a03fa23e0e36c93936dca2969598e4102f4b441e1663f535fc99cd31", size = 11371 }, + { url = "https://files.pythonhosted.org/packages/9c/c8/cf20ea91ae33f05f3b92dec648c6f44a65f86d1a64c1d6375c95b85ccb7c/pyobjc_framework_coreml-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:71de5b37e6a017e3ed16645c5d6533138f24708da5b56c35c818ae49d0253ee1", size = 11600 }, + { url = "https://files.pythonhosted.org/packages/bc/5c/510ae8e3663238d32e653ed6a09ac65611dd045a7241f12633c1ab48bb9b/pyobjc_framework_coreml-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a04a96e512ecf6999aa9e1f60ad5635cb9d1cd839be470341d8d1541797baef6", size = 11418 }, + { url = "https://files.pythonhosted.org/packages/d3/1a/b7367819381b07c440fa5797d2b0487e31f09aa72079a693ceab6875fa0a/pyobjc_framework_coreml-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7762b3dd2de01565b7cf3049ce1e4c27341ba179d97016b0b7607448e1c39865", size = 11593 }, ] [[package]] @@ -6772,13 +6755,13 @@ dependencies = [ { name = "pyobjc-core", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, { name = "pyobjc-framework-cocoa", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798, upload-time = "2025-11-14T10:00:01.236Z" }, - { url = "https://files.pythonhosted.org/packages/ba/2d/e8f495328101898c16c32ac10e7b14b08ff2c443a756a76fd1271915f097/pyobjc_framework_quartz-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:629b7971b1b43a11617f1460cd218bd308dfea247cd4ee3842eb40ca6f588860", size = 219206, upload-time = "2025-11-14T10:00:15.623Z" }, - { url = "https://files.pythonhosted.org/packages/67/43/b1f0ad3b842ab150a7e6b7d97f6257eab6af241b4c7d14cb8e7fde9214b8/pyobjc_framework_quartz-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:53b84e880c358ba1ddcd7e8d5ea0407d760eca58b96f0d344829162cda5f37b3", size = 224317, upload-time = "2025-11-14T10:00:30.703Z" }, - { url = "https://files.pythonhosted.org/packages/4a/00/96249c5c7e5aaca5f688ca18b8d8ad05cd7886ebd639b3c71a6a4cadbe75/pyobjc_framework_quartz-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:42d306b07f05ae7d155984503e0fb1b701fecd31dcc5c79fe8ab9790ff7e0de0", size = 219558, upload-time = "2025-11-14T10:00:45.476Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a6/708a55f3ff7a18c403b30a29a11dccfed0410485a7548c60a4b6d4cc0676/pyobjc_framework_quartz-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0cc08fddb339b2760df60dea1057453557588908e42bdc62184b6396ce2d6e9a", size = 224580, upload-time = "2025-11-14T10:01:00.091Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798 }, + { url = "https://files.pythonhosted.org/packages/ba/2d/e8f495328101898c16c32ac10e7b14b08ff2c443a756a76fd1271915f097/pyobjc_framework_quartz-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:629b7971b1b43a11617f1460cd218bd308dfea247cd4ee3842eb40ca6f588860", size = 219206 }, + { url = "https://files.pythonhosted.org/packages/67/43/b1f0ad3b842ab150a7e6b7d97f6257eab6af241b4c7d14cb8e7fde9214b8/pyobjc_framework_quartz-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:53b84e880c358ba1ddcd7e8d5ea0407d760eca58b96f0d344829162cda5f37b3", size = 224317 }, + { url = "https://files.pythonhosted.org/packages/4a/00/96249c5c7e5aaca5f688ca18b8d8ad05cd7886ebd639b3c71a6a4cadbe75/pyobjc_framework_quartz-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:42d306b07f05ae7d155984503e0fb1b701fecd31dcc5c79fe8ab9790ff7e0de0", size = 219558 }, + { url = "https://files.pythonhosted.org/packages/4d/a6/708a55f3ff7a18c403b30a29a11dccfed0410485a7548c60a4b6d4cc0676/pyobjc_framework_quartz-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0cc08fddb339b2760df60dea1057453557588908e42bdc62184b6396ce2d6e9a", size = 224580 }, ] [[package]] @@ -6791,22 +6774,22 @@ dependencies = [ { name = "pyobjc-framework-coreml", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, { name = "pyobjc-framework-quartz", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/5a/08bb3e278f870443d226c141af14205ff41c0274da1e053b72b11dfc9fb2/pyobjc_framework_vision-12.1.tar.gz", hash = "sha256:a30959100e85dcede3a786c544e621ad6eb65ff6abf85721f805822b8c5fe9b0", size = 59538, upload-time = "2025-11-14T10:23:21.979Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/5a/08bb3e278f870443d226c141af14205ff41c0274da1e053b72b11dfc9fb2/pyobjc_framework_vision-12.1.tar.gz", hash = "sha256:a30959100e85dcede3a786c544e621ad6eb65ff6abf85721f805822b8c5fe9b0", size = 59538 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/5a/23502935b3fc877d7573e743fc3e6c28748f33a45c43851d503bde52cde7/pyobjc_framework_vision-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6b3211d84f3a12aad0cde752cfd43a80d0218960ac9e6b46b141c730e7d655bd", size = 16625, upload-time = "2025-11-14T10:06:44.422Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e4/e87361a31b82b22f8c0a59652d6e17625870dd002e8da75cb2343a84f2f9/pyobjc_framework_vision-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7273e2508db4c2e88523b4b7ff38ac54808756e7ba01d78e6c08ea68f32577d2", size = 16640, upload-time = "2025-11-14T10:06:46.653Z" }, - { url = "https://files.pythonhosted.org/packages/b1/dd/def55d8a80b0817f486f2712fc6243482c3264d373dc5ff75037b3aeb7ea/pyobjc_framework_vision-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:04296f0848cc8cdead66c76df6063720885cbdf24fdfd1900749a6e2297313db", size = 16782, upload-time = "2025-11-14T10:06:48.816Z" }, - { url = "https://files.pythonhosted.org/packages/a7/a4/ee1ef14d6e1df6617e64dbaaa0ecf8ecb9e0af1425613fa633f6a94049c1/pyobjc_framework_vision-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:631add775ed1dafb221a6116137cdcd78432addc16200ca434571c2a039c0e03", size = 16614, upload-time = "2025-11-14T10:06:50.852Z" }, - { url = "https://files.pythonhosted.org/packages/af/53/187743d9244becd4499a77f8ee699ae286e2f6ade7c0c7ad2975ae60f187/pyobjc_framework_vision-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fe41a1a70cc91068aee7b5293fa09dc66d1c666a8da79fdf948900988b439df6", size = 16771, upload-time = "2025-11-14T10:06:53.04Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5a/23502935b3fc877d7573e743fc3e6c28748f33a45c43851d503bde52cde7/pyobjc_framework_vision-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6b3211d84f3a12aad0cde752cfd43a80d0218960ac9e6b46b141c730e7d655bd", size = 16625 }, + { url = "https://files.pythonhosted.org/packages/f5/e4/e87361a31b82b22f8c0a59652d6e17625870dd002e8da75cb2343a84f2f9/pyobjc_framework_vision-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7273e2508db4c2e88523b4b7ff38ac54808756e7ba01d78e6c08ea68f32577d2", size = 16640 }, + { url = "https://files.pythonhosted.org/packages/b1/dd/def55d8a80b0817f486f2712fc6243482c3264d373dc5ff75037b3aeb7ea/pyobjc_framework_vision-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:04296f0848cc8cdead66c76df6063720885cbdf24fdfd1900749a6e2297313db", size = 16782 }, + { url = "https://files.pythonhosted.org/packages/a7/a4/ee1ef14d6e1df6617e64dbaaa0ecf8ecb9e0af1425613fa633f6a94049c1/pyobjc_framework_vision-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:631add775ed1dafb221a6116137cdcd78432addc16200ca434571c2a039c0e03", size = 16614 }, + { url = "https://files.pythonhosted.org/packages/af/53/187743d9244becd4499a77f8ee699ae286e2f6ade7c0c7ad2975ae60f187/pyobjc_framework_vision-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fe41a1a70cc91068aee7b5293fa09dc66d1c666a8da79fdf948900988b439df6", size = 16771 }, ] [[package]] name = "pypandoc" version = "1.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/d6/410615fc433e5d1eacc00db2044ae2a9c82302df0d35366fe2bd15de024d/pypandoc-1.17.tar.gz", hash = "sha256:51179abfd6e582a25ed03477541b48836b5bba5a4c3b282a547630793934d799", size = 69071, upload-time = "2026-03-14T22:39:07.21Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/d6/410615fc433e5d1eacc00db2044ae2a9c82302df0d35366fe2bd15de024d/pypandoc-1.17.tar.gz", hash = "sha256:51179abfd6e582a25ed03477541b48836b5bba5a4c3b282a547630793934d799", size = 69071 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/86/e2ffa604eacfbec3f430b1d850e7e04c4101eca1a5828f9ae54bf51dfba4/pypandoc-1.17-py3-none-any.whl", hash = "sha256:01fdbffa61edb9f8e82e8faad6954efcb7b6f8f0634aead4d89e322a00225a67", size = 23554, upload-time = "2026-03-14T22:38:46.007Z" }, + { url = "https://files.pythonhosted.org/packages/0c/86/e2ffa604eacfbec3f430b1d850e7e04c4101eca1a5828f9ae54bf51dfba4/pypandoc-1.17-py3-none-any.whl", hash = "sha256:01fdbffa61edb9f8e82e8faad6954efcb7b6f8f0634aead4d89e322a00225a67", size = 23554 }, ] [[package]] @@ -6814,78 +6797,78 @@ name = "pypandoc-binary" version = "1.17" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/85/681a54111f0948821a5cf87ce30a88bb0a3f6848af5112c912abac4a2b77/pypandoc_binary-1.17-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:734726dc618ef276343e272e1a6b4567e59c2ef9ef41d5533042deac3b0531f1", size = 25553945, upload-time = "2026-03-14T22:38:47.91Z" }, - { url = "https://files.pythonhosted.org/packages/15/58/8fd107c68522957868c1e785fbea7595608df118e440e424d189668294df/pypandoc_binary-1.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fcfd28f347ed998dda28823fc6bc24f9310e7fdf3ddceaf925bf0563a100ab5b", size = 25553944, upload-time = "2026-03-14T22:38:50.74Z" }, - { url = "https://files.pythonhosted.org/packages/f4/27/ac1078239aae14b94c51975b7f46ad8e099e47d7ae26c175a5486b1c0099/pypandoc_binary-1.17-py3-none-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b620b21c9374e3e48aabd518492bf0776b148442ee28816f6aaf52da3d4387", size = 34460960, upload-time = "2026-03-14T22:38:53.391Z" }, - { url = "https://files.pythonhosted.org/packages/8d/7f/1e5612b52900ebe590862dabeadf546f739b27527dcd8bfd632f8adac1be/pypandoc_binary-1.17-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ada156cb980cd54fd6534231788e668c00dbb591cbd24f0be0bd86812eb8788", size = 36867598, upload-time = "2026-03-14T22:38:56.351Z" }, - { url = "https://files.pythonhosted.org/packages/3b/31/a5a867159c4080e5d368f4a53540a727501a2f31affc297dc8e0fced96a7/pypandoc_binary-1.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2f439dcd211183bb3460253ca4511101df6e1acf4a01f45f5617e1fa2ad24279", size = 36867584, upload-time = "2026-03-14T22:38:59.087Z" }, - { url = "https://files.pythonhosted.org/packages/0d/2d/6a51cd4e54bdf132c19416801077c34bd40ba182e85d843360d36ae03a2d/pypandoc_binary-1.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f6e6d3e4cfafbe23189a08db3d41f8def260bacd6e7e382bceadab7ba1f17da6", size = 34460949, upload-time = "2026-03-14T22:39:01.71Z" }, - { url = "https://files.pythonhosted.org/packages/c6/b9/f47b77ba75ed5d47ec85fcc2ecfbf7f78e3a73347f3a09836634d930de98/pypandoc_binary-1.17-py3-none-win_amd64.whl", hash = "sha256:76fae066cd2d7e78fb97f0ec8e9e36f437b07187b689b0b415ca18216f8f898a", size = 40891661, upload-time = "2026-03-14T22:39:04.782Z" }, + { url = "https://files.pythonhosted.org/packages/80/85/681a54111f0948821a5cf87ce30a88bb0a3f6848af5112c912abac4a2b77/pypandoc_binary-1.17-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:734726dc618ef276343e272e1a6b4567e59c2ef9ef41d5533042deac3b0531f1", size = 25553945 }, + { url = "https://files.pythonhosted.org/packages/15/58/8fd107c68522957868c1e785fbea7595608df118e440e424d189668294df/pypandoc_binary-1.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fcfd28f347ed998dda28823fc6bc24f9310e7fdf3ddceaf925bf0563a100ab5b", size = 25553944 }, + { url = "https://files.pythonhosted.org/packages/f4/27/ac1078239aae14b94c51975b7f46ad8e099e47d7ae26c175a5486b1c0099/pypandoc_binary-1.17-py3-none-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b620b21c9374e3e48aabd518492bf0776b148442ee28816f6aaf52da3d4387", size = 34460960 }, + { url = "https://files.pythonhosted.org/packages/8d/7f/1e5612b52900ebe590862dabeadf546f739b27527dcd8bfd632f8adac1be/pypandoc_binary-1.17-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ada156cb980cd54fd6534231788e668c00dbb591cbd24f0be0bd86812eb8788", size = 36867598 }, + { url = "https://files.pythonhosted.org/packages/3b/31/a5a867159c4080e5d368f4a53540a727501a2f31affc297dc8e0fced96a7/pypandoc_binary-1.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2f439dcd211183bb3460253ca4511101df6e1acf4a01f45f5617e1fa2ad24279", size = 36867584 }, + { url = "https://files.pythonhosted.org/packages/0d/2d/6a51cd4e54bdf132c19416801077c34bd40ba182e85d843360d36ae03a2d/pypandoc_binary-1.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f6e6d3e4cfafbe23189a08db3d41f8def260bacd6e7e382bceadab7ba1f17da6", size = 34460949 }, + { url = "https://files.pythonhosted.org/packages/c6/b9/f47b77ba75ed5d47ec85fcc2ecfbf7f78e3a73347f3a09836634d930de98/pypandoc_binary-1.17-py3-none-win_amd64.whl", hash = "sha256:76fae066cd2d7e78fb97f0ec8e9e36f437b07187b689b0b415ca18216f8f898a", size = 40891661 }, ] [[package]] name = "pyparsing" version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574 } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781 }, ] [[package]] name = "pypdf" version = "6.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/31/83/691bdb309306232362503083cb15777491045dd54f45393a317dc7d8082f/pypdf-6.9.2.tar.gz", hash = "sha256:7f850faf2b0d4ab936582c05da32c52214c2b089d61a316627b5bfb5b0dab46c", size = 5311837, upload-time = "2026-03-23T14:53:27.983Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/83/691bdb309306232362503083cb15777491045dd54f45393a317dc7d8082f/pypdf-6.9.2.tar.gz", hash = "sha256:7f850faf2b0d4ab936582c05da32c52214c2b089d61a316627b5bfb5b0dab46c", size = 5311837 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/7e/c85f41243086a8fe5d1baeba527cb26a1918158a565932b41e0f7c0b32e9/pypdf-6.9.2-py3-none-any.whl", hash = "sha256:662cf29bcb419a36a1365232449624ab40b7c2d0cfc28e54f42eeecd1fd7e844", size = 333744, upload-time = "2026-03-23T14:53:26.573Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7e/c85f41243086a8fe5d1baeba527cb26a1918158a565932b41e0f7c0b32e9/pypdf-6.9.2-py3-none-any.whl", hash = "sha256:662cf29bcb419a36a1365232449624ab40b7c2d0cfc28e54f42eeecd1fd7e844", size = 333744 }, ] [[package]] name = "pypdfium2" version = "5.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/01/be763b9081c7eb823196e7d13d9c145bf75ac43f3c1466de81c21c24b381/pypdfium2-5.6.0.tar.gz", hash = "sha256:bcb9368acfe3547054698abbdae68ba0cbd2d3bda8e8ee437e061deef061976d", size = 270714, upload-time = "2026-03-08T01:05:06.5Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/01/be763b9081c7eb823196e7d13d9c145bf75ac43f3c1466de81c21c24b381/pypdfium2-5.6.0.tar.gz", hash = "sha256:bcb9368acfe3547054698abbdae68ba0cbd2d3bda8e8ee437e061deef061976d", size = 270714 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/b1/129ed0177521a93a892f8a6a215dd3260093e30e77ef7035004bb8af7b6c/pypdfium2-5.6.0-py3-none-android_23_arm64_v8a.whl", hash = "sha256:fb7858c9707708555b4a719b5548a6e7f5d26bc82aef55ae4eb085d7a2190b11", size = 3346059, upload-time = "2026-03-08T01:04:21.37Z" }, - { url = "https://files.pythonhosted.org/packages/86/34/cbdece6886012180a7f2c7b2c360c415cf5e1f83f1973d2c9201dae3506a/pypdfium2-5.6.0-py3-none-android_23_armeabi_v7a.whl", hash = "sha256:6a7e1f4597317786f994bfb947eef480e53933f804a990193ab89eef8243f805", size = 2804418, upload-time = "2026-03-08T01:04:23.384Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f6/9f9e190fe0e5a6b86b82f83bd8b5d3490348766062381140ca5cad8e00b1/pypdfium2-5.6.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e468c38997573f0e86f03273c2c1fbdea999de52ba43fee96acaa2f6b2ad35f7", size = 3412541, upload-time = "2026-03-08T01:04:25.45Z" }, - { url = "https://files.pythonhosted.org/packages/ee/8d/e57492cb2228ba56ed57de1ff044c8ac114b46905f8b1445c33299ba0488/pypdfium2-5.6.0-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:ad3abddc5805424f962e383253ccad6a0d1d2ebd86afa9a9e1b9ca659773cd0d", size = 3592320, upload-time = "2026-03-08T01:04:27.509Z" }, - { url = "https://files.pythonhosted.org/packages/f9/8a/8ab82e33e9c551494cbe1526ea250ca8cc4e9e98d6a4fc6b6f8d959aa1d1/pypdfium2-5.6.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b5eb9eae5c45076395454522ca26add72ba8bd1fe473e1e4721aa58521470c", size = 3596450, upload-time = "2026-03-08T01:04:29.183Z" }, - { url = "https://files.pythonhosted.org/packages/f5/b5/602a792282312ccb158cc63849528079d94b0a11efdc61f2a359edfb41e9/pypdfium2-5.6.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:258624da8ef45cdc426e11b33e9d83f9fb723c1c201c6e0f4ab5a85966c6b876", size = 3325442, upload-time = "2026-03-08T01:04:30.886Z" }, - { url = "https://files.pythonhosted.org/packages/81/1f/9e48ec05ed8d19d736c2d1f23c1bd0f20673f02ef846a2576c69e237f15d/pypdfium2-5.6.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9367451c8a00931d6612db0822525a18c06f649d562cd323a719e46ac19c9bb", size = 3727434, upload-time = "2026-03-08T01:04:33.619Z" }, - { url = "https://files.pythonhosted.org/packages/33/90/0efd020928b4edbd65f4f3c2af0c84e20b43a3ada8fa6d04f999a97afe7a/pypdfium2-5.6.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a757869f891eac1cc1372e38a4aa01adac8abc8fe2a8a4e2ebf50595e3bf5937", size = 4139029, upload-time = "2026-03-08T01:04:36.08Z" }, - { url = "https://files.pythonhosted.org/packages/ff/49/a640b288a48dab1752281dd9b72c0679fccea107874e80a65a606b00efa9/pypdfium2-5.6.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:515be355222cc57ae9e62cd5c7c350b8e0c863efc539f80c7d75e2811ba45cb6", size = 3646387, upload-time = "2026-03-08T01:04:38.151Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/a344c19c01021eeb5d830c102e4fc9b1602f19c04aa7d11abbe2d188fd8e/pypdfium2-5.6.0-py3-none-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1c4753c7caf7d004211d7f57a21f10d127f5e0e5510a14d24bc073e7220a3ea", size = 3097212, upload-time = "2026-03-08T01:04:40.776Z" }, - { url = "https://files.pythonhosted.org/packages/50/96/e48e13789ace22aeb9b7510904a1b1493ec588196e11bbacc122da330b3d/pypdfium2-5.6.0-py3-none-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c49729090281fdd85775fb8912c10bd19e99178efaa98f145ab06e7ce68554d2", size = 2965026, upload-time = "2026-03-08T01:04:42.857Z" }, - { url = "https://files.pythonhosted.org/packages/cb/06/3100e44d4935f73af8f5d633d3bd40f0d36d606027085a0ef1f0566a6320/pypdfium2-5.6.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a4a1749a8d4afd62924a8d95cfa4f2e26fc32957ce34ac3b674be6f127ed252e", size = 4131431, upload-time = "2026-03-08T01:04:44.982Z" }, - { url = "https://files.pythonhosted.org/packages/64/ef/d8df63569ce9a66c8496057782eb8af78e0d28667922d62ec958434e3d4b/pypdfium2-5.6.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:36469ebd0fdffb7130ce45ed9c44f8232d91571c89eb851bd1633c64b6f6114f", size = 3747469, upload-time = "2026-03-08T01:04:46.702Z" }, - { url = "https://files.pythonhosted.org/packages/a6/47/fd2c6a67a49fade1acd719fbd11f7c375e7219912923ef2de0ea0ac1544e/pypdfium2-5.6.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9da900df09be3cf546b637a127a7b6428fb22d705951d731269e25fd3adef457", size = 4337578, upload-time = "2026-03-08T01:04:49.007Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f5/836c83e54b01e09478c4d6bf4912651d6053c932250fcee953f5c72d8e4a/pypdfium2-5.6.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:45fccd5622233c5ec91a885770ae7dd4004d4320ac05a4ad8fa03a66dea40244", size = 4376104, upload-time = "2026-03-08T01:04:51.04Z" }, - { url = "https://files.pythonhosted.org/packages/6e/7f/b940b6a1664daf8f9bad87c6c99b84effa3611615b8708d10392dc33036c/pypdfium2-5.6.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:282dc030e767cd61bd0299f9d581052b91188e2b87561489057a8e7963e7e0cb", size = 3929824, upload-time = "2026-03-08T01:04:53.544Z" }, - { url = "https://files.pythonhosted.org/packages/88/79/00267d92a6a58c229e364d474f5698efe446e0c7f4f152f58d0138715e99/pypdfium2-5.6.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:a1c1dfe950382c76a7bba1ba160ec5e40df8dd26b04a1124ae268fda55bc4cbe", size = 4270201, upload-time = "2026-03-08T01:04:55.81Z" }, - { url = "https://files.pythonhosted.org/packages/e1/ab/b127f38aba41746bdf9ace15ba08411d7ef6ecba1326d529ba414eb1ed50/pypdfium2-5.6.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:43b0341ca6feb6c92e4b7a9eb4813e5466f5f5e8b6baeb14df0a94d5f312c00b", size = 4180793, upload-time = "2026-03-08T01:04:57.961Z" }, - { url = "https://files.pythonhosted.org/packages/0e/8c/a01c8e4302448b614d25a85c08298b0d3e9dfbdac5bd1b2f32c9b02e83d9/pypdfium2-5.6.0-py3-none-win32.whl", hash = "sha256:9dfcd4ff49a2b9260d00e38539ab28190d59e785e83030b30ffaf7a29c42155d", size = 3596753, upload-time = "2026-03-08T01:05:00.566Z" }, - { url = "https://files.pythonhosted.org/packages/9b/5f/2d871adf46761bb002a62686545da6348afe838d19af03df65d1ece786a2/pypdfium2-5.6.0-py3-none-win_amd64.whl", hash = "sha256:c6bc8dd63d0568f4b592f3e03de756afafc0e44aa1fe8878cc4aba1b11ae7374", size = 3716526, upload-time = "2026-03-08T01:05:02.433Z" }, - { url = "https://files.pythonhosted.org/packages/3a/80/0d9b162098597fbe3ac2b269b1682c0c3e8db9ba87679603fdd9b19afaa6/pypdfium2-5.6.0-py3-none-win_arm64.whl", hash = "sha256:5538417b199bdcb3207370c88df61f2ba3dac7a3253f82e1aa2708e6376b6f90", size = 3515049, upload-time = "2026-03-08T01:05:04.587Z" }, + { url = "https://files.pythonhosted.org/packages/9d/b1/129ed0177521a93a892f8a6a215dd3260093e30e77ef7035004bb8af7b6c/pypdfium2-5.6.0-py3-none-android_23_arm64_v8a.whl", hash = "sha256:fb7858c9707708555b4a719b5548a6e7f5d26bc82aef55ae4eb085d7a2190b11", size = 3346059 }, + { url = "https://files.pythonhosted.org/packages/86/34/cbdece6886012180a7f2c7b2c360c415cf5e1f83f1973d2c9201dae3506a/pypdfium2-5.6.0-py3-none-android_23_armeabi_v7a.whl", hash = "sha256:6a7e1f4597317786f994bfb947eef480e53933f804a990193ab89eef8243f805", size = 2804418 }, + { url = "https://files.pythonhosted.org/packages/6e/f6/9f9e190fe0e5a6b86b82f83bd8b5d3490348766062381140ca5cad8e00b1/pypdfium2-5.6.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e468c38997573f0e86f03273c2c1fbdea999de52ba43fee96acaa2f6b2ad35f7", size = 3412541 }, + { url = "https://files.pythonhosted.org/packages/ee/8d/e57492cb2228ba56ed57de1ff044c8ac114b46905f8b1445c33299ba0488/pypdfium2-5.6.0-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:ad3abddc5805424f962e383253ccad6a0d1d2ebd86afa9a9e1b9ca659773cd0d", size = 3592320 }, + { url = "https://files.pythonhosted.org/packages/f9/8a/8ab82e33e9c551494cbe1526ea250ca8cc4e9e98d6a4fc6b6f8d959aa1d1/pypdfium2-5.6.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6b5eb9eae5c45076395454522ca26add72ba8bd1fe473e1e4721aa58521470c", size = 3596450 }, + { url = "https://files.pythonhosted.org/packages/f5/b5/602a792282312ccb158cc63849528079d94b0a11efdc61f2a359edfb41e9/pypdfium2-5.6.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:258624da8ef45cdc426e11b33e9d83f9fb723c1c201c6e0f4ab5a85966c6b876", size = 3325442 }, + { url = "https://files.pythonhosted.org/packages/81/1f/9e48ec05ed8d19d736c2d1f23c1bd0f20673f02ef846a2576c69e237f15d/pypdfium2-5.6.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9367451c8a00931d6612db0822525a18c06f649d562cd323a719e46ac19c9bb", size = 3727434 }, + { url = "https://files.pythonhosted.org/packages/33/90/0efd020928b4edbd65f4f3c2af0c84e20b43a3ada8fa6d04f999a97afe7a/pypdfium2-5.6.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a757869f891eac1cc1372e38a4aa01adac8abc8fe2a8a4e2ebf50595e3bf5937", size = 4139029 }, + { url = "https://files.pythonhosted.org/packages/ff/49/a640b288a48dab1752281dd9b72c0679fccea107874e80a65a606b00efa9/pypdfium2-5.6.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:515be355222cc57ae9e62cd5c7c350b8e0c863efc539f80c7d75e2811ba45cb6", size = 3646387 }, + { url = "https://files.pythonhosted.org/packages/b0/3b/a344c19c01021eeb5d830c102e4fc9b1602f19c04aa7d11abbe2d188fd8e/pypdfium2-5.6.0-py3-none-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1c4753c7caf7d004211d7f57a21f10d127f5e0e5510a14d24bc073e7220a3ea", size = 3097212 }, + { url = "https://files.pythonhosted.org/packages/50/96/e48e13789ace22aeb9b7510904a1b1493ec588196e11bbacc122da330b3d/pypdfium2-5.6.0-py3-none-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c49729090281fdd85775fb8912c10bd19e99178efaa98f145ab06e7ce68554d2", size = 2965026 }, + { url = "https://files.pythonhosted.org/packages/cb/06/3100e44d4935f73af8f5d633d3bd40f0d36d606027085a0ef1f0566a6320/pypdfium2-5.6.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a4a1749a8d4afd62924a8d95cfa4f2e26fc32957ce34ac3b674be6f127ed252e", size = 4131431 }, + { url = "https://files.pythonhosted.org/packages/64/ef/d8df63569ce9a66c8496057782eb8af78e0d28667922d62ec958434e3d4b/pypdfium2-5.6.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:36469ebd0fdffb7130ce45ed9c44f8232d91571c89eb851bd1633c64b6f6114f", size = 3747469 }, + { url = "https://files.pythonhosted.org/packages/a6/47/fd2c6a67a49fade1acd719fbd11f7c375e7219912923ef2de0ea0ac1544e/pypdfium2-5.6.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9da900df09be3cf546b637a127a7b6428fb22d705951d731269e25fd3adef457", size = 4337578 }, + { url = "https://files.pythonhosted.org/packages/6b/f5/836c83e54b01e09478c4d6bf4912651d6053c932250fcee953f5c72d8e4a/pypdfium2-5.6.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:45fccd5622233c5ec91a885770ae7dd4004d4320ac05a4ad8fa03a66dea40244", size = 4376104 }, + { url = "https://files.pythonhosted.org/packages/6e/7f/b940b6a1664daf8f9bad87c6c99b84effa3611615b8708d10392dc33036c/pypdfium2-5.6.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:282dc030e767cd61bd0299f9d581052b91188e2b87561489057a8e7963e7e0cb", size = 3929824 }, + { url = "https://files.pythonhosted.org/packages/88/79/00267d92a6a58c229e364d474f5698efe446e0c7f4f152f58d0138715e99/pypdfium2-5.6.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:a1c1dfe950382c76a7bba1ba160ec5e40df8dd26b04a1124ae268fda55bc4cbe", size = 4270201 }, + { url = "https://files.pythonhosted.org/packages/e1/ab/b127f38aba41746bdf9ace15ba08411d7ef6ecba1326d529ba414eb1ed50/pypdfium2-5.6.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:43b0341ca6feb6c92e4b7a9eb4813e5466f5f5e8b6baeb14df0a94d5f312c00b", size = 4180793 }, + { url = "https://files.pythonhosted.org/packages/0e/8c/a01c8e4302448b614d25a85c08298b0d3e9dfbdac5bd1b2f32c9b02e83d9/pypdfium2-5.6.0-py3-none-win32.whl", hash = "sha256:9dfcd4ff49a2b9260d00e38539ab28190d59e785e83030b30ffaf7a29c42155d", size = 3596753 }, + { url = "https://files.pythonhosted.org/packages/9b/5f/2d871adf46761bb002a62686545da6348afe838d19af03df65d1ece786a2/pypdfium2-5.6.0-py3-none-win_amd64.whl", hash = "sha256:c6bc8dd63d0568f4b592f3e03de756afafc0e44aa1fe8878cc4aba1b11ae7374", size = 3716526 }, + { url = "https://files.pythonhosted.org/packages/3a/80/0d9b162098597fbe3ac2b269b1682c0c3e8db9ba87679603fdd9b19afaa6/pypdfium2-5.6.0-py3-none-win_arm64.whl", hash = "sha256:5538417b199bdcb3207370c88df61f2ba3dac7a3253f82e1aa2708e6376b6f90", size = 3515049 }, ] [[package]] name = "pypika" version = "0.51.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/78/cbaebba88e05e2dcda13ca203131b38d3640219f20ebb49676d26714861b/pypika-0.51.1.tar.gz", hash = "sha256:c30c7c1048fbf056fd3920c5a2b88b0c29dd190a9b2bee971fd17e4abe4d0ebe", size = 80919, upload-time = "2026-02-04T11:27:48.304Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/78/cbaebba88e05e2dcda13ca203131b38d3640219f20ebb49676d26714861b/pypika-0.51.1.tar.gz", hash = "sha256:c30c7c1048fbf056fd3920c5a2b88b0c29dd190a9b2bee971fd17e4abe4d0ebe", size = 80919 } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/83/c77dfeed04022e8930b08eedca2b6e5efed256ab3321396fde90066efb65/pypika-0.51.1-py2.py3-none-any.whl", hash = "sha256:77985b4d7ce71b9905255bf12468cf598349e98837c037541cfc240e528aec46", size = 60585, upload-time = "2026-02-04T11:27:46.251Z" }, + { url = "https://files.pythonhosted.org/packages/57/83/c77dfeed04022e8930b08eedca2b6e5efed256ab3321396fde90066efb65/pypika-0.51.1-py2.py3-none-any.whl", hash = "sha256:77985b4d7ce71b9905255bf12468cf598349e98837c037541cfc240e528aec46", size = 60585 }, ] [[package]] name = "pyproject-hooks" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216 }, ] [[package]] @@ -6896,7 +6879,7 @@ dependencies = [ { name = "requests" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/a0/d0638470df605ce266991fb04f74c69ab1bed3b90ac3838e9c3c8b69b66a/Pysher-1.0.8.tar.gz", hash = "sha256:7849c56032b208e49df67d7bd8d49029a69042ab0bb45b2ed59fa08f11ac5988", size = 9071, upload-time = "2022-10-10T13:41:09.936Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/a0/d0638470df605ce266991fb04f74c69ab1bed3b90ac3838e9c3c8b69b66a/Pysher-1.0.8.tar.gz", hash = "sha256:7849c56032b208e49df67d7bd8d49029a69042ab0bb45b2ed59fa08f11ac5988", size = 9071 } [[package]] name = "pytest" @@ -6909,9 +6892,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801 }, ] [[package]] @@ -6922,9 +6905,9 @@ dependencies = [ { name = "pytest" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075 }, ] [[package]] @@ -6934,9 +6917,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095 }, ] [[package]] @@ -6946,9 +6929,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, ] [[package]] @@ -6959,18 +6942,18 @@ dependencies = [ { name = "lxml" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256, upload-time = "2025-06-16T20:46:27.921Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987, upload-time = "2025-06-16T20:46:22.506Z" }, + { url = "https://files.pythonhosted.org/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987 }, ] [[package]] name = "python-dotenv" version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101 }, ] [[package]] @@ -6981,36 +6964,36 @@ dependencies = [ { name = "pyee" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dd/4d/7ecffb341d646e016be76e36f5a42cb32f409c9ca21a57b68f067fad3fc7/python_ffmpeg-2.0.12.tar.gz", hash = "sha256:19ac80af5a064a2f53c245af1a909b2d7648ea045500d96d3bcd507b88d43dc7", size = 14126292, upload-time = "2024-04-15T10:15:31.878Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/4d/7ecffb341d646e016be76e36f5a42cb32f409c9ca21a57b68f067fad3fc7/python_ffmpeg-2.0.12.tar.gz", hash = "sha256:19ac80af5a064a2f53c245af1a909b2d7648ea045500d96d3bcd507b88d43dc7", size = 14126292 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/6d/02e817aec661defe148cb9eb0c4eca2444846305f625c2243fb9f92a9045/python_ffmpeg-2.0.12-py3-none-any.whl", hash = "sha256:d86697da8dfb39335183e336d31baf42fb217468adf5ac97fd743898240faae3", size = 14411, upload-time = "2024-04-15T10:15:28.966Z" }, + { url = "https://files.pythonhosted.org/packages/7f/6d/02e817aec661defe148cb9eb0c4eca2444846305f625c2243fb9f92a9045/python_ffmpeg-2.0.12-py3-none-any.whl", hash = "sha256:d86697da8dfb39335183e336d31baf42fb217468adf5ac97fd743898240faae3", size = 14411 }, ] [[package]] name = "python-iso639" version = "2026.1.31" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/da/701fc47ea3b0579a8ae489d50d5b54f2ef3aeb7768afd31db1d1cfe9f24e/python_iso639-2026.1.31.tar.gz", hash = "sha256:55a1612c15e5fbd3a1fa269a309cbf1e7c13019356e3d6f75bb435ed44c45ddb", size = 174144, upload-time = "2026-01-31T15:04:48.105Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/da/701fc47ea3b0579a8ae489d50d5b54f2ef3aeb7768afd31db1d1cfe9f24e/python_iso639-2026.1.31.tar.gz", hash = "sha256:55a1612c15e5fbd3a1fa269a309cbf1e7c13019356e3d6f75bb435ed44c45ddb", size = 174144 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/3a/03ee682b04099e6b02b591955851b0347deb2e3691ae850112000c54ba12/python_iso639-2026.1.31-py3-none-any.whl", hash = "sha256:b2c48fa1300af1299dff4f1e1995ad1059996ed9f22270ea2d6d6bdc5fb03d4c", size = 167757, upload-time = "2026-01-31T15:04:46.458Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3a/03ee682b04099e6b02b591955851b0347deb2e3691ae850112000c54ba12/python_iso639-2026.1.31-py3-none-any.whl", hash = "sha256:b2c48fa1300af1299dff4f1e1995ad1059996ed9f22270ea2d6d6bdc5fb03d4c", size = 167757 }, ] [[package]] name = "python-magic" version = "0.4.27" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677, upload-time = "2022-06-07T20:16:59.508Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840, upload-time = "2022-06-07T20:16:57.763Z" }, + { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840 }, ] [[package]] name = "python-multipart" version = "0.0.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, + { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579 }, ] [[package]] @@ -7022,9 +7005,9 @@ dependencies = [ { name = "olefile" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/4e/869f34faedbc968796d2c7e9837dede079c9cb9750917356b1f1eda926e9/python_oxmsg-0.0.2.tar.gz", hash = "sha256:a6aff4deb1b5975d44d49dab1d9384089ffeec819e19c6940bc7ffbc84775fad", size = 34713, upload-time = "2025-02-03T17:13:47.415Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/4e/869f34faedbc968796d2c7e9837dede079c9cb9750917356b1f1eda926e9/python_oxmsg-0.0.2.tar.gz", hash = "sha256:a6aff4deb1b5975d44d49dab1d9384089ffeec819e19c6940bc7ffbc84775fad", size = 34713 } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/67/f56c69a98c7eb244025845506387d0f961681657c9fcd8b2d2edd148f9d2/python_oxmsg-0.0.2-py3-none-any.whl", hash = "sha256:22be29b14c46016bcd05e34abddfd8e05ee82082f53b82753d115da3fc7d0355", size = 31455, upload-time = "2025-02-03T17:13:46.061Z" }, + { url = "https://files.pythonhosted.org/packages/53/67/f56c69a98c7eb244025845506387d0f961681657c9fcd8b2d2edd148f9d2/python_oxmsg-0.0.2-py3-none-any.whl", hash = "sha256:22be29b14c46016bcd05e34abddfd8e05ee82082f53b82753d115da3fc7d0355", size = 31455 }, ] [[package]] @@ -7037,18 +7020,18 @@ dependencies = [ { name = "typing-extensions" }, { name = "xlsxwriter" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/a9/0c0db8d37b2b8a645666f7fd8accea4c6224e013c42b1d5c17c93590cd06/python_pptx-1.0.2.tar.gz", hash = "sha256:479a8af0eaf0f0d76b6f00b0887732874ad2e3188230315290cd1f9dd9cc7095", size = 10109297, upload-time = "2024-08-07T17:33:37.772Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/a9/0c0db8d37b2b8a645666f7fd8accea4c6224e013c42b1d5c17c93590cd06/python_pptx-1.0.2.tar.gz", hash = "sha256:479a8af0eaf0f0d76b6f00b0887732874ad2e3188230315290cd1f9dd9cc7095", size = 10109297 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788, upload-time = "2024-08-07T17:33:28.192Z" }, + { url = "https://files.pythonhosted.org/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788 }, ] [[package]] name = "pytz" version = "2026.1.post1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088, upload-time = "2026-03-03T07:47:50.683Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088 } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489, upload-time = "2026-03-03T07:47:49.167Z" }, + { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489 }, ] [[package]] @@ -7056,70 +7039,70 @@ name = "pywin32" version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, - { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, - { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, - { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, - { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, - { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, - { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543 }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040 }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102 }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700 }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700 }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318 }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714 }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800 }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540 }, ] [[package]] name = "pywin32-ctypes" version = "0.2.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471 } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 }, ] [[package]] name = "pyyaml" version = "6.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669 }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252 }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081 }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159 }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626 }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613 }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115 }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814 }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809 }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454 }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355 }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175 }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228 }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194 }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429 }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912 }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108 }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641 }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901 }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132 }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261 }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272 }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923 }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062 }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 }, ] [[package]] @@ -7135,72 +7118,72 @@ dependencies = [ { name = "pydantic" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/dd/f8a8261b83946af3cd65943c93c4f83e044f01184e8525404989d22a81a5/qdrant_client-1.17.1.tar.gz", hash = "sha256:22f990bbd63485ed97ba551a4c498181fcb723f71dcab5d6e4e43fe1050a2bc0", size = 344979, upload-time = "2026-03-13T17:13:44.678Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/dd/f8a8261b83946af3cd65943c93c4f83e044f01184e8525404989d22a81a5/qdrant_client-1.17.1.tar.gz", hash = "sha256:22f990bbd63485ed97ba551a4c498181fcb723f71dcab5d6e4e43fe1050a2bc0", size = 344979 } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/69/77d1a971c4b933e8c79403e99bcbb790463da5e48333cc4fd5d412c63c98/qdrant_client-1.17.1-py3-none-any.whl", hash = "sha256:6cda4064adfeaf211c751f3fbc00edbbdb499850918c7aff4855a9a759d56cbd", size = 389947, upload-time = "2026-03-13T17:13:43.156Z" }, + { url = "https://files.pythonhosted.org/packages/68/69/77d1a971c4b933e8c79403e99bcbb790463da5e48333cc4fd5d412c63c98/qdrant_client-1.17.1-py3-none-any.whl", hash = "sha256:6cda4064adfeaf211c751f3fbc00edbbdb499850918c7aff4855a9a759d56cbd", size = 389947 }, ] [[package]] name = "rapidfuzz" version = "3.14.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/28/9d808fe62375b9aab5ba92fa9b29371297b067c2790b2d7cda648b1e2f8d/rapidfuzz-3.14.3.tar.gz", hash = "sha256:2491937177868bc4b1e469087601d53f925e8d270ccc21e07404b4b5814b7b5f", size = 57863900, upload-time = "2025-11-01T11:54:52.321Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/28/9d808fe62375b9aab5ba92fa9b29371297b067c2790b2d7cda648b1e2f8d/rapidfuzz-3.14.3.tar.gz", hash = "sha256:2491937177868bc4b1e469087601d53f925e8d270ccc21e07404b4b5814b7b5f", size = 57863900 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/8e/3c215e860b458cfbedb3ed73bc72e98eb7e0ed72f6b48099604a7a3260c2/rapidfuzz-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:685c93ea961d135893b5984a5a9851637d23767feabe414ec974f43babbd8226", size = 1945306, upload-time = "2025-11-01T11:53:06.452Z" }, - { url = "https://files.pythonhosted.org/packages/36/d9/31b33512015c899f4a6e6af64df8dfe8acddf4c8b40a4b3e0e6e1bcd00e5/rapidfuzz-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa7c8f26f009f8c673fbfb443792f0cf8cf50c4e18121ff1e285b5e08a94fbdb", size = 1390788, upload-time = "2025-11-01T11:53:08.721Z" }, - { url = "https://files.pythonhosted.org/packages/a9/67/2ee6f8de6e2081ccd560a571d9c9063184fe467f484a17fa90311a7f4a2e/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57f878330c8d361b2ce76cebb8e3e1dc827293b6abf404e67d53260d27b5d941", size = 1374580, upload-time = "2025-11-01T11:53:10.164Z" }, - { url = "https://files.pythonhosted.org/packages/30/83/80d22997acd928eda7deadc19ccd15883904622396d6571e935993e0453a/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c5f545f454871e6af05753a0172849c82feaf0f521c5ca62ba09e1b382d6382", size = 3154947, upload-time = "2025-11-01T11:53:12.093Z" }, - { url = "https://files.pythonhosted.org/packages/5b/cf/9f49831085a16384695f9fb096b99662f589e30b89b4a589a1ebc1a19d34/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:07aa0b5d8863e3151e05026a28e0d924accf0a7a3b605da978f0359bb804df43", size = 1223872, upload-time = "2025-11-01T11:53:13.664Z" }, - { url = "https://files.pythonhosted.org/packages/c8/0f/41ee8034e744b871c2e071ef0d360686f5ccfe5659f4fd96c3ec406b3c8b/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73b07566bc7e010e7b5bd490fb04bb312e820970180df6b5655e9e6224c137db", size = 2392512, upload-time = "2025-11-01T11:53:15.109Z" }, - { url = "https://files.pythonhosted.org/packages/da/86/280038b6b0c2ccec54fb957c732ad6b41cc1fd03b288d76545b9cf98343f/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6de00eb84c71476af7d3110cf25d8fe7c792d7f5fa86764ef0b4ca97e78ca3ed", size = 2521398, upload-time = "2025-11-01T11:53:17.146Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7b/05c26f939607dca0006505e3216248ae2de631e39ef94dd63dbbf0860021/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d7843a1abf0091773a530636fdd2a49a41bcae22f9910b86b4f903e76ddc82dc", size = 4259416, upload-time = "2025-11-01T11:53:19.34Z" }, - { url = "https://files.pythonhosted.org/packages/40/eb/9e3af4103d91788f81111af1b54a28de347cdbed8eaa6c91d5e98a889aab/rapidfuzz-3.14.3-cp312-cp312-win32.whl", hash = "sha256:dea97ac3ca18cd3ba8f3d04b5c1fe4aa60e58e8d9b7793d3bd595fdb04128d7a", size = 1709527, upload-time = "2025-11-01T11:53:20.949Z" }, - { url = "https://files.pythonhosted.org/packages/b8/63/d06ecce90e2cf1747e29aeab9f823d21e5877a4c51b79720b2d3be7848f8/rapidfuzz-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:b5100fd6bcee4d27f28f4e0a1c6b5127bc8ba7c2a9959cad9eab0bf4a7ab3329", size = 1538989, upload-time = "2025-11-01T11:53:22.428Z" }, - { url = "https://files.pythonhosted.org/packages/fc/6d/beee32dcda64af8128aab3ace2ccb33d797ed58c434c6419eea015fec779/rapidfuzz-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:4e49c9e992bc5fc873bd0fff7ef16a4405130ec42f2ce3d2b735ba5d3d4eb70f", size = 811161, upload-time = "2025-11-01T11:53:23.811Z" }, - { url = "https://files.pythonhosted.org/packages/e4/4f/0d94d09646853bd26978cb3a7541b6233c5760687777fa97da8de0d9a6ac/rapidfuzz-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbcb726064b12f356bf10fffdb6db4b6dce5390b23627c08652b3f6e49aa56ae", size = 1939646, upload-time = "2025-11-01T11:53:25.292Z" }, - { url = "https://files.pythonhosted.org/packages/b6/eb/f96aefc00f3bbdbab9c0657363ea8437a207d7545ac1c3789673e05d80bd/rapidfuzz-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1704fc70d214294e554a2421b473779bcdeef715881c5e927dc0f11e1692a0ff", size = 1385512, upload-time = "2025-11-01T11:53:27.594Z" }, - { url = "https://files.pythonhosted.org/packages/26/34/71c4f7749c12ee223dba90017a5947e8f03731a7cc9f489b662a8e9e643d/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc65e72790ddfd310c2c8912b45106e3800fefe160b0c2ef4d6b6fec4e826457", size = 1373571, upload-time = "2025-11-01T11:53:29.096Z" }, - { url = "https://files.pythonhosted.org/packages/32/00/ec8597a64f2be301ce1ee3290d067f49f6a7afb226b67d5f15b56d772ba5/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e38c1305cffae8472572a0584d4ffc2f130865586a81038ca3965301f7c97c", size = 3156759, upload-time = "2025-11-01T11:53:30.777Z" }, - { url = "https://files.pythonhosted.org/packages/61/d5/b41eeb4930501cc899d5a9a7b5c9a33d85a670200d7e81658626dcc0ecc0/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:e195a77d06c03c98b3fc06b8a28576ba824392ce40de8c708f96ce04849a052e", size = 1222067, upload-time = "2025-11-01T11:53:32.334Z" }, - { url = "https://files.pythonhosted.org/packages/2a/7d/6d9abb4ffd1027c6ed837b425834f3bed8344472eb3a503ab55b3407c721/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b7ef2f4b8583a744338a18f12c69693c194fb6777c0e9ada98cd4d9e8f09d10", size = 2394775, upload-time = "2025-11-01T11:53:34.24Z" }, - { url = "https://files.pythonhosted.org/packages/15/ce/4f3ab4c401c5a55364da1ffff8cc879fc97b4e5f4fa96033827da491a973/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a2135b138bcdcb4c3742d417f215ac2d8c2b87bde15b0feede231ae95f09ec41", size = 2526123, upload-time = "2025-11-01T11:53:35.779Z" }, - { url = "https://files.pythonhosted.org/packages/c1/4b/54f804975376a328f57293bd817c12c9036171d15cf7292032e3f5820b2d/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33a325ed0e8e1aa20c3e75f8ab057a7b248fdea7843c2a19ade0008906c14af0", size = 4262874, upload-time = "2025-11-01T11:53:37.866Z" }, - { url = "https://files.pythonhosted.org/packages/e9/b6/958db27d8a29a50ee6edd45d33debd3ce732e7209183a72f57544cd5fe22/rapidfuzz-3.14.3-cp313-cp313-win32.whl", hash = "sha256:8383b6d0d92f6cd008f3c9216535be215a064b2cc890398a678b56e6d280cb63", size = 1707972, upload-time = "2025-11-01T11:53:39.442Z" }, - { url = "https://files.pythonhosted.org/packages/07/75/fde1f334b0cec15b5946d9f84d73250fbfcc73c236b4bc1b25129d90876b/rapidfuzz-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:e6b5e3036976f0fde888687d91be86d81f9ac5f7b02e218913c38285b756be6c", size = 1537011, upload-time = "2025-11-01T11:53:40.92Z" }, - { url = "https://files.pythonhosted.org/packages/2e/d7/d83fe001ce599dc7ead57ba1debf923dc961b6bdce522b741e6b8c82f55c/rapidfuzz-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:7ba009977601d8b0828bfac9a110b195b3e4e79b350dcfa48c11269a9f1918a0", size = 810744, upload-time = "2025-11-01T11:53:42.723Z" }, - { url = "https://files.pythonhosted.org/packages/92/13/a486369e63ff3c1a58444d16b15c5feb943edd0e6c28a1d7d67cb8946b8f/rapidfuzz-3.14.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0a28add871425c2fe94358c6300bbeb0bc2ed828ca003420ac6825408f5a424", size = 1967702, upload-time = "2025-11-01T11:53:44.554Z" }, - { url = "https://files.pythonhosted.org/packages/f1/82/efad25e260b7810f01d6b69122685e355bed78c94a12784bac4e0beb2afb/rapidfuzz-3.14.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:010e12e2411a4854b0434f920e72b717c43f8ec48d57e7affe5c42ecfa05dd0e", size = 1410702, upload-time = "2025-11-01T11:53:46.066Z" }, - { url = "https://files.pythonhosted.org/packages/ba/1a/34c977b860cde91082eae4a97ae503f43e0d84d4af301d857679b66f9869/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cfc3d57abd83c734d1714ec39c88a34dd69c85474918ebc21296f1e61eb5ca8", size = 1382337, upload-time = "2025-11-01T11:53:47.62Z" }, - { url = "https://files.pythonhosted.org/packages/88/74/f50ea0e24a5880a9159e8fd256b84d8f4634c2f6b4f98028bdd31891d907/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89acb8cbb52904f763e5ac238083b9fc193bed8d1f03c80568b20e4cef43a519", size = 3165563, upload-time = "2025-11-01T11:53:49.216Z" }, - { url = "https://files.pythonhosted.org/packages/e8/7a/e744359404d7737049c26099423fc54bcbf303de5d870d07d2fb1410f567/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_31_armv7l.whl", hash = "sha256:7d9af908c2f371bfb9c985bd134e295038e3031e666e4b2ade1e7cb7f5af2f1a", size = 1214727, upload-time = "2025-11-01T11:53:50.883Z" }, - { url = "https://files.pythonhosted.org/packages/d3/2e/87adfe14ce75768ec6c2b8acd0e05e85e84be4be5e3d283cdae360afc4fe/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1f1925619627f8798f8c3a391d81071336942e5fe8467bc3c567f982e7ce2897", size = 2403349, upload-time = "2025-11-01T11:53:52.322Z" }, - { url = "https://files.pythonhosted.org/packages/70/17/6c0b2b2bff9c8b12e12624c07aa22e922b0c72a490f180fa9183d1ef2c75/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:152555187360978119e98ce3e8263d70dd0c40c7541193fc302e9b7125cf8f58", size = 2507596, upload-time = "2025-11-01T11:53:53.835Z" }, - { url = "https://files.pythonhosted.org/packages/c3/d1/87852a7cbe4da7b962174c749a47433881a63a817d04f3e385ea9babcd9e/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52619d25a09546b8db078981ca88939d72caa6b8701edd8b22e16482a38e799f", size = 4273595, upload-time = "2025-11-01T11:53:55.961Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ab/1d0354b7d1771a28fa7fe089bc23acec2bdd3756efa2419f463e3ed80e16/rapidfuzz-3.14.3-cp313-cp313t-win32.whl", hash = "sha256:489ce98a895c98cad284f0a47960c3e264c724cb4cfd47a1430fa091c0c25204", size = 1757773, upload-time = "2025-11-01T11:53:57.628Z" }, - { url = "https://files.pythonhosted.org/packages/0b/0c/71ef356adc29e2bdf74cd284317b34a16b80258fa0e7e242dd92cc1e6d10/rapidfuzz-3.14.3-cp313-cp313t-win_amd64.whl", hash = "sha256:656e52b054d5b5c2524169240e50cfa080b04b1c613c5f90a2465e84888d6f15", size = 1576797, upload-time = "2025-11-01T11:53:59.455Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d2/0e64fc27bb08d4304aa3d11154eb5480bcf5d62d60140a7ee984dc07468a/rapidfuzz-3.14.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c7e40c0a0af02ad6e57e89f62bef8604f55a04ecae90b0ceeda591bbf5923317", size = 829940, upload-time = "2025-11-01T11:54:01.1Z" }, - { url = "https://files.pythonhosted.org/packages/32/6f/1b88aaeade83abc5418788f9e6b01efefcd1a69d65ded37d89cd1662be41/rapidfuzz-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:442125473b247227d3f2de807a11da6c08ccf536572d1be943f8e262bae7e4ea", size = 1942086, upload-time = "2025-11-01T11:54:02.592Z" }, - { url = "https://files.pythonhosted.org/packages/a0/2c/b23861347436cb10f46c2bd425489ec462790faaa360a54a7ede5f78de88/rapidfuzz-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ec0c8c0c3d4f97ced46b2e191e883f8c82dbbf6d5ebc1842366d7eff13cd5a6", size = 1386993, upload-time = "2025-11-01T11:54:04.12Z" }, - { url = "https://files.pythonhosted.org/packages/83/86/5d72e2c060aa1fbdc1f7362d938f6b237dff91f5b9fc5dd7cc297e112250/rapidfuzz-3.14.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2dc37bc20272f388b8c3a4eba4febc6e77e50a8f450c472def4751e7678f55e4", size = 1379126, upload-time = "2025-11-01T11:54:05.777Z" }, - { url = "https://files.pythonhosted.org/packages/c9/bc/ef2cee3e4d8b3fc22705ff519f0d487eecc756abdc7c25d53686689d6cf2/rapidfuzz-3.14.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee362e7e79bae940a5e2b3f6d09c6554db6a4e301cc68343886c08be99844f1", size = 3159304, upload-time = "2025-11-01T11:54:07.351Z" }, - { url = "https://files.pythonhosted.org/packages/a0/36/dc5f2f62bbc7bc90be1f75eeaf49ed9502094bb19290dfb4747317b17f12/rapidfuzz-3.14.3-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:4b39921df948388a863f0e267edf2c36302983459b021ab928d4b801cbe6a421", size = 1218207, upload-time = "2025-11-01T11:54:09.641Z" }, - { url = "https://files.pythonhosted.org/packages/df/7e/8f4be75c1bc62f47edf2bbbe2370ee482fae655ebcc4718ac3827ead3904/rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:beda6aa9bc44d1d81242e7b291b446be352d3451f8217fcb068fc2933927d53b", size = 2401245, upload-time = "2025-11-01T11:54:11.543Z" }, - { url = "https://files.pythonhosted.org/packages/05/38/f7c92759e1bb188dd05b80d11c630ba59b8d7856657baf454ff56059c2ab/rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6a014ba09657abfcfeed64b7d09407acb29af436d7fc075b23a298a7e4a6b41c", size = 2518308, upload-time = "2025-11-01T11:54:13.134Z" }, - { url = "https://files.pythonhosted.org/packages/c7/ac/85820f70fed5ecb5f1d9a55f1e1e2090ef62985ef41db289b5ac5ec56e28/rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:32eeafa3abce138bb725550c0e228fc7eaeec7059aa8093d9cbbec2b58c2371a", size = 4265011, upload-time = "2025-11-01T11:54:15.087Z" }, - { url = "https://files.pythonhosted.org/packages/46/a9/616930721ea9835c918af7cde22bff17f9db3639b0c1a7f96684be7f5630/rapidfuzz-3.14.3-cp314-cp314-win32.whl", hash = "sha256:adb44d996fc610c7da8c5048775b21db60dd63b1548f078e95858c05c86876a3", size = 1742245, upload-time = "2025-11-01T11:54:17.19Z" }, - { url = "https://files.pythonhosted.org/packages/06/8a/f2fa5e9635b1ccafda4accf0e38246003f69982d7c81f2faa150014525a4/rapidfuzz-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:f3d15d8527e2b293e38ce6e437631af0708df29eafd7c9fc48210854c94472f9", size = 1584856, upload-time = "2025-11-01T11:54:18.764Z" }, - { url = "https://files.pythonhosted.org/packages/ef/97/09e20663917678a6d60d8e0e29796db175b1165e2079830430342d5298be/rapidfuzz-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:576e4b9012a67e0bf54fccb69a7b6c94d4e86a9540a62f1a5144977359133583", size = 833490, upload-time = "2025-11-01T11:54:20.753Z" }, - { url = "https://files.pythonhosted.org/packages/03/1b/6b6084576ba87bf21877c77218a0c97ba98cb285b0c02eaaee3acd7c4513/rapidfuzz-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cec3c0da88562727dd5a5a364bd9efeb535400ff0bfb1443156dd139a1dd7b50", size = 1968658, upload-time = "2025-11-01T11:54:22.25Z" }, - { url = "https://files.pythonhosted.org/packages/38/c0/fb02a0db80d95704b0a6469cc394e8c38501abf7e1c0b2afe3261d1510c2/rapidfuzz-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d1fa009f8b1100e4880868137e7bf0501422898f7674f2adcd85d5a67f041296", size = 1410742, upload-time = "2025-11-01T11:54:23.863Z" }, - { url = "https://files.pythonhosted.org/packages/a4/72/3fbf12819fc6afc8ec75a45204013b40979d068971e535a7f3512b05e765/rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b86daa7419b5e8b180690efd1fdbac43ff19230803282521c5b5a9c83977655", size = 1382810, upload-time = "2025-11-01T11:54:25.571Z" }, - { url = "https://files.pythonhosted.org/packages/0f/18/0f1991d59bb7eee28922a00f79d83eafa8c7bfb4e8edebf4af2a160e7196/rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7bd1816db05d6c5ffb3a4df0a2b7b56fb8c81ef584d08e37058afa217da91b1", size = 3166349, upload-time = "2025-11-01T11:54:27.195Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f0/baa958b1989c8f88c78bbb329e969440cf330b5a01a982669986495bb980/rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:33da4bbaf44e9755b0ce192597f3bde7372fe2e381ab305f41b707a95ac57aa7", size = 1214994, upload-time = "2025-11-01T11:54:28.821Z" }, - { url = "https://files.pythonhosted.org/packages/e4/a0/cd12ec71f9b2519a3954febc5740291cceabc64c87bc6433afcb36259f3b/rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3fecce764cf5a991ee2195a844196da840aba72029b2612f95ac68a8b74946bf", size = 2403919, upload-time = "2025-11-01T11:54:30.393Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ce/019bd2176c1644098eced4f0595cb4b3ef52e4941ac9a5854f209d0a6e16/rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ecd7453e02cf072258c3a6b8e930230d789d5d46cc849503729f9ce475d0e785", size = 2508346, upload-time = "2025-11-01T11:54:32.048Z" }, - { url = "https://files.pythonhosted.org/packages/23/f8/be16c68e2c9e6c4f23e8f4adbb7bccc9483200087ed28ff76c5312da9b14/rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea188aa00e9bcae8c8411f006a5f2f06c4607a02f24eab0d8dc58566aa911f35", size = 4274105, upload-time = "2025-11-01T11:54:33.701Z" }, - { url = "https://files.pythonhosted.org/packages/a1/d1/5ab148e03f7e6ec8cd220ccf7af74d3aaa4de26dd96df58936beb7cba820/rapidfuzz-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:7ccbf68100c170e9a0581accbe9291850936711548c6688ce3bfb897b8c589ad", size = 1793465, upload-time = "2025-11-01T11:54:35.331Z" }, - { url = "https://files.pythonhosted.org/packages/cd/97/433b2d98e97abd9fff1c470a109b311669f44cdec8d0d5aa250aceaed1fb/rapidfuzz-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9ec02e62ae765a318d6de38df609c57fc6dacc65c0ed1fd489036834fd8a620c", size = 1623491, upload-time = "2025-11-01T11:54:38.085Z" }, - { url = "https://files.pythonhosted.org/packages/e2/f6/e2176eb94f94892441bce3ddc514c179facb65db245e7ce3356965595b19/rapidfuzz-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e805e52322ae29aa945baf7168b6c898120fbc16d2b8f940b658a5e9e3999253", size = 851487, upload-time = "2025-11-01T11:54:40.176Z" }, + { url = "https://files.pythonhosted.org/packages/fa/8e/3c215e860b458cfbedb3ed73bc72e98eb7e0ed72f6b48099604a7a3260c2/rapidfuzz-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:685c93ea961d135893b5984a5a9851637d23767feabe414ec974f43babbd8226", size = 1945306 }, + { url = "https://files.pythonhosted.org/packages/36/d9/31b33512015c899f4a6e6af64df8dfe8acddf4c8b40a4b3e0e6e1bcd00e5/rapidfuzz-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa7c8f26f009f8c673fbfb443792f0cf8cf50c4e18121ff1e285b5e08a94fbdb", size = 1390788 }, + { url = "https://files.pythonhosted.org/packages/a9/67/2ee6f8de6e2081ccd560a571d9c9063184fe467f484a17fa90311a7f4a2e/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57f878330c8d361b2ce76cebb8e3e1dc827293b6abf404e67d53260d27b5d941", size = 1374580 }, + { url = "https://files.pythonhosted.org/packages/30/83/80d22997acd928eda7deadc19ccd15883904622396d6571e935993e0453a/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c5f545f454871e6af05753a0172849c82feaf0f521c5ca62ba09e1b382d6382", size = 3154947 }, + { url = "https://files.pythonhosted.org/packages/5b/cf/9f49831085a16384695f9fb096b99662f589e30b89b4a589a1ebc1a19d34/rapidfuzz-3.14.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:07aa0b5d8863e3151e05026a28e0d924accf0a7a3b605da978f0359bb804df43", size = 1223872 }, + { url = "https://files.pythonhosted.org/packages/c8/0f/41ee8034e744b871c2e071ef0d360686f5ccfe5659f4fd96c3ec406b3c8b/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73b07566bc7e010e7b5bd490fb04bb312e820970180df6b5655e9e6224c137db", size = 2392512 }, + { url = "https://files.pythonhosted.org/packages/da/86/280038b6b0c2ccec54fb957c732ad6b41cc1fd03b288d76545b9cf98343f/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6de00eb84c71476af7d3110cf25d8fe7c792d7f5fa86764ef0b4ca97e78ca3ed", size = 2521398 }, + { url = "https://files.pythonhosted.org/packages/fa/7b/05c26f939607dca0006505e3216248ae2de631e39ef94dd63dbbf0860021/rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d7843a1abf0091773a530636fdd2a49a41bcae22f9910b86b4f903e76ddc82dc", size = 4259416 }, + { url = "https://files.pythonhosted.org/packages/40/eb/9e3af4103d91788f81111af1b54a28de347cdbed8eaa6c91d5e98a889aab/rapidfuzz-3.14.3-cp312-cp312-win32.whl", hash = "sha256:dea97ac3ca18cd3ba8f3d04b5c1fe4aa60e58e8d9b7793d3bd595fdb04128d7a", size = 1709527 }, + { url = "https://files.pythonhosted.org/packages/b8/63/d06ecce90e2cf1747e29aeab9f823d21e5877a4c51b79720b2d3be7848f8/rapidfuzz-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:b5100fd6bcee4d27f28f4e0a1c6b5127bc8ba7c2a9959cad9eab0bf4a7ab3329", size = 1538989 }, + { url = "https://files.pythonhosted.org/packages/fc/6d/beee32dcda64af8128aab3ace2ccb33d797ed58c434c6419eea015fec779/rapidfuzz-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:4e49c9e992bc5fc873bd0fff7ef16a4405130ec42f2ce3d2b735ba5d3d4eb70f", size = 811161 }, + { url = "https://files.pythonhosted.org/packages/e4/4f/0d94d09646853bd26978cb3a7541b6233c5760687777fa97da8de0d9a6ac/rapidfuzz-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbcb726064b12f356bf10fffdb6db4b6dce5390b23627c08652b3f6e49aa56ae", size = 1939646 }, + { url = "https://files.pythonhosted.org/packages/b6/eb/f96aefc00f3bbdbab9c0657363ea8437a207d7545ac1c3789673e05d80bd/rapidfuzz-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1704fc70d214294e554a2421b473779bcdeef715881c5e927dc0f11e1692a0ff", size = 1385512 }, + { url = "https://files.pythonhosted.org/packages/26/34/71c4f7749c12ee223dba90017a5947e8f03731a7cc9f489b662a8e9e643d/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc65e72790ddfd310c2c8912b45106e3800fefe160b0c2ef4d6b6fec4e826457", size = 1373571 }, + { url = "https://files.pythonhosted.org/packages/32/00/ec8597a64f2be301ce1ee3290d067f49f6a7afb226b67d5f15b56d772ba5/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e38c1305cffae8472572a0584d4ffc2f130865586a81038ca3965301f7c97c", size = 3156759 }, + { url = "https://files.pythonhosted.org/packages/61/d5/b41eeb4930501cc899d5a9a7b5c9a33d85a670200d7e81658626dcc0ecc0/rapidfuzz-3.14.3-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:e195a77d06c03c98b3fc06b8a28576ba824392ce40de8c708f96ce04849a052e", size = 1222067 }, + { url = "https://files.pythonhosted.org/packages/2a/7d/6d9abb4ffd1027c6ed837b425834f3bed8344472eb3a503ab55b3407c721/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b7ef2f4b8583a744338a18f12c69693c194fb6777c0e9ada98cd4d9e8f09d10", size = 2394775 }, + { url = "https://files.pythonhosted.org/packages/15/ce/4f3ab4c401c5a55364da1ffff8cc879fc97b4e5f4fa96033827da491a973/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a2135b138bcdcb4c3742d417f215ac2d8c2b87bde15b0feede231ae95f09ec41", size = 2526123 }, + { url = "https://files.pythonhosted.org/packages/c1/4b/54f804975376a328f57293bd817c12c9036171d15cf7292032e3f5820b2d/rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33a325ed0e8e1aa20c3e75f8ab057a7b248fdea7843c2a19ade0008906c14af0", size = 4262874 }, + { url = "https://files.pythonhosted.org/packages/e9/b6/958db27d8a29a50ee6edd45d33debd3ce732e7209183a72f57544cd5fe22/rapidfuzz-3.14.3-cp313-cp313-win32.whl", hash = "sha256:8383b6d0d92f6cd008f3c9216535be215a064b2cc890398a678b56e6d280cb63", size = 1707972 }, + { url = "https://files.pythonhosted.org/packages/07/75/fde1f334b0cec15b5946d9f84d73250fbfcc73c236b4bc1b25129d90876b/rapidfuzz-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:e6b5e3036976f0fde888687d91be86d81f9ac5f7b02e218913c38285b756be6c", size = 1537011 }, + { url = "https://files.pythonhosted.org/packages/2e/d7/d83fe001ce599dc7ead57ba1debf923dc961b6bdce522b741e6b8c82f55c/rapidfuzz-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:7ba009977601d8b0828bfac9a110b195b3e4e79b350dcfa48c11269a9f1918a0", size = 810744 }, + { url = "https://files.pythonhosted.org/packages/92/13/a486369e63ff3c1a58444d16b15c5feb943edd0e6c28a1d7d67cb8946b8f/rapidfuzz-3.14.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0a28add871425c2fe94358c6300bbeb0bc2ed828ca003420ac6825408f5a424", size = 1967702 }, + { url = "https://files.pythonhosted.org/packages/f1/82/efad25e260b7810f01d6b69122685e355bed78c94a12784bac4e0beb2afb/rapidfuzz-3.14.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:010e12e2411a4854b0434f920e72b717c43f8ec48d57e7affe5c42ecfa05dd0e", size = 1410702 }, + { url = "https://files.pythonhosted.org/packages/ba/1a/34c977b860cde91082eae4a97ae503f43e0d84d4af301d857679b66f9869/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cfc3d57abd83c734d1714ec39c88a34dd69c85474918ebc21296f1e61eb5ca8", size = 1382337 }, + { url = "https://files.pythonhosted.org/packages/88/74/f50ea0e24a5880a9159e8fd256b84d8f4634c2f6b4f98028bdd31891d907/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89acb8cbb52904f763e5ac238083b9fc193bed8d1f03c80568b20e4cef43a519", size = 3165563 }, + { url = "https://files.pythonhosted.org/packages/e8/7a/e744359404d7737049c26099423fc54bcbf303de5d870d07d2fb1410f567/rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_31_armv7l.whl", hash = "sha256:7d9af908c2f371bfb9c985bd134e295038e3031e666e4b2ade1e7cb7f5af2f1a", size = 1214727 }, + { url = "https://files.pythonhosted.org/packages/d3/2e/87adfe14ce75768ec6c2b8acd0e05e85e84be4be5e3d283cdae360afc4fe/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1f1925619627f8798f8c3a391d81071336942e5fe8467bc3c567f982e7ce2897", size = 2403349 }, + { url = "https://files.pythonhosted.org/packages/70/17/6c0b2b2bff9c8b12e12624c07aa22e922b0c72a490f180fa9183d1ef2c75/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:152555187360978119e98ce3e8263d70dd0c40c7541193fc302e9b7125cf8f58", size = 2507596 }, + { url = "https://files.pythonhosted.org/packages/c3/d1/87852a7cbe4da7b962174c749a47433881a63a817d04f3e385ea9babcd9e/rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52619d25a09546b8db078981ca88939d72caa6b8701edd8b22e16482a38e799f", size = 4273595 }, + { url = "https://files.pythonhosted.org/packages/c1/ab/1d0354b7d1771a28fa7fe089bc23acec2bdd3756efa2419f463e3ed80e16/rapidfuzz-3.14.3-cp313-cp313t-win32.whl", hash = "sha256:489ce98a895c98cad284f0a47960c3e264c724cb4cfd47a1430fa091c0c25204", size = 1757773 }, + { url = "https://files.pythonhosted.org/packages/0b/0c/71ef356adc29e2bdf74cd284317b34a16b80258fa0e7e242dd92cc1e6d10/rapidfuzz-3.14.3-cp313-cp313t-win_amd64.whl", hash = "sha256:656e52b054d5b5c2524169240e50cfa080b04b1c613c5f90a2465e84888d6f15", size = 1576797 }, + { url = "https://files.pythonhosted.org/packages/fe/d2/0e64fc27bb08d4304aa3d11154eb5480bcf5d62d60140a7ee984dc07468a/rapidfuzz-3.14.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c7e40c0a0af02ad6e57e89f62bef8604f55a04ecae90b0ceeda591bbf5923317", size = 829940 }, + { url = "https://files.pythonhosted.org/packages/32/6f/1b88aaeade83abc5418788f9e6b01efefcd1a69d65ded37d89cd1662be41/rapidfuzz-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:442125473b247227d3f2de807a11da6c08ccf536572d1be943f8e262bae7e4ea", size = 1942086 }, + { url = "https://files.pythonhosted.org/packages/a0/2c/b23861347436cb10f46c2bd425489ec462790faaa360a54a7ede5f78de88/rapidfuzz-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ec0c8c0c3d4f97ced46b2e191e883f8c82dbbf6d5ebc1842366d7eff13cd5a6", size = 1386993 }, + { url = "https://files.pythonhosted.org/packages/83/86/5d72e2c060aa1fbdc1f7362d938f6b237dff91f5b9fc5dd7cc297e112250/rapidfuzz-3.14.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2dc37bc20272f388b8c3a4eba4febc6e77e50a8f450c472def4751e7678f55e4", size = 1379126 }, + { url = "https://files.pythonhosted.org/packages/c9/bc/ef2cee3e4d8b3fc22705ff519f0d487eecc756abdc7c25d53686689d6cf2/rapidfuzz-3.14.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee362e7e79bae940a5e2b3f6d09c6554db6a4e301cc68343886c08be99844f1", size = 3159304 }, + { url = "https://files.pythonhosted.org/packages/a0/36/dc5f2f62bbc7bc90be1f75eeaf49ed9502094bb19290dfb4747317b17f12/rapidfuzz-3.14.3-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:4b39921df948388a863f0e267edf2c36302983459b021ab928d4b801cbe6a421", size = 1218207 }, + { url = "https://files.pythonhosted.org/packages/df/7e/8f4be75c1bc62f47edf2bbbe2370ee482fae655ebcc4718ac3827ead3904/rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:beda6aa9bc44d1d81242e7b291b446be352d3451f8217fcb068fc2933927d53b", size = 2401245 }, + { url = "https://files.pythonhosted.org/packages/05/38/f7c92759e1bb188dd05b80d11c630ba59b8d7856657baf454ff56059c2ab/rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6a014ba09657abfcfeed64b7d09407acb29af436d7fc075b23a298a7e4a6b41c", size = 2518308 }, + { url = "https://files.pythonhosted.org/packages/c7/ac/85820f70fed5ecb5f1d9a55f1e1e2090ef62985ef41db289b5ac5ec56e28/rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:32eeafa3abce138bb725550c0e228fc7eaeec7059aa8093d9cbbec2b58c2371a", size = 4265011 }, + { url = "https://files.pythonhosted.org/packages/46/a9/616930721ea9835c918af7cde22bff17f9db3639b0c1a7f96684be7f5630/rapidfuzz-3.14.3-cp314-cp314-win32.whl", hash = "sha256:adb44d996fc610c7da8c5048775b21db60dd63b1548f078e95858c05c86876a3", size = 1742245 }, + { url = "https://files.pythonhosted.org/packages/06/8a/f2fa5e9635b1ccafda4accf0e38246003f69982d7c81f2faa150014525a4/rapidfuzz-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:f3d15d8527e2b293e38ce6e437631af0708df29eafd7c9fc48210854c94472f9", size = 1584856 }, + { url = "https://files.pythonhosted.org/packages/ef/97/09e20663917678a6d60d8e0e29796db175b1165e2079830430342d5298be/rapidfuzz-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:576e4b9012a67e0bf54fccb69a7b6c94d4e86a9540a62f1a5144977359133583", size = 833490 }, + { url = "https://files.pythonhosted.org/packages/03/1b/6b6084576ba87bf21877c77218a0c97ba98cb285b0c02eaaee3acd7c4513/rapidfuzz-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cec3c0da88562727dd5a5a364bd9efeb535400ff0bfb1443156dd139a1dd7b50", size = 1968658 }, + { url = "https://files.pythonhosted.org/packages/38/c0/fb02a0db80d95704b0a6469cc394e8c38501abf7e1c0b2afe3261d1510c2/rapidfuzz-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d1fa009f8b1100e4880868137e7bf0501422898f7674f2adcd85d5a67f041296", size = 1410742 }, + { url = "https://files.pythonhosted.org/packages/a4/72/3fbf12819fc6afc8ec75a45204013b40979d068971e535a7f3512b05e765/rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b86daa7419b5e8b180690efd1fdbac43ff19230803282521c5b5a9c83977655", size = 1382810 }, + { url = "https://files.pythonhosted.org/packages/0f/18/0f1991d59bb7eee28922a00f79d83eafa8c7bfb4e8edebf4af2a160e7196/rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7bd1816db05d6c5ffb3a4df0a2b7b56fb8c81ef584d08e37058afa217da91b1", size = 3166349 }, + { url = "https://files.pythonhosted.org/packages/0d/f0/baa958b1989c8f88c78bbb329e969440cf330b5a01a982669986495bb980/rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:33da4bbaf44e9755b0ce192597f3bde7372fe2e381ab305f41b707a95ac57aa7", size = 1214994 }, + { url = "https://files.pythonhosted.org/packages/e4/a0/cd12ec71f9b2519a3954febc5740291cceabc64c87bc6433afcb36259f3b/rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3fecce764cf5a991ee2195a844196da840aba72029b2612f95ac68a8b74946bf", size = 2403919 }, + { url = "https://files.pythonhosted.org/packages/0b/ce/019bd2176c1644098eced4f0595cb4b3ef52e4941ac9a5854f209d0a6e16/rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ecd7453e02cf072258c3a6b8e930230d789d5d46cc849503729f9ce475d0e785", size = 2508346 }, + { url = "https://files.pythonhosted.org/packages/23/f8/be16c68e2c9e6c4f23e8f4adbb7bccc9483200087ed28ff76c5312da9b14/rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea188aa00e9bcae8c8411f006a5f2f06c4607a02f24eab0d8dc58566aa911f35", size = 4274105 }, + { url = "https://files.pythonhosted.org/packages/a1/d1/5ab148e03f7e6ec8cd220ccf7af74d3aaa4de26dd96df58936beb7cba820/rapidfuzz-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:7ccbf68100c170e9a0581accbe9291850936711548c6688ce3bfb897b8c589ad", size = 1793465 }, + { url = "https://files.pythonhosted.org/packages/cd/97/433b2d98e97abd9fff1c470a109b311669f44cdec8d0d5aa250aceaed1fb/rapidfuzz-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9ec02e62ae765a318d6de38df609c57fc6dacc65c0ed1fd489036834fd8a620c", size = 1623491 }, + { url = "https://files.pythonhosted.org/packages/e2/f6/e2176eb94f94892441bce3ddc514c179facb65db245e7ce3356965595b19/rapidfuzz-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e805e52322ae29aa945baf7168b6c898120fbc16d2b8f940b658a5e9e3999253", size = 851487 }, ] [[package]] @@ -7221,7 +7204,7 @@ dependencies = [ { name = "tqdm" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/b8/011338eec8aea40cf9b82da7481f3e65e100537cff4c866b3c1b1e719b97/rapidocr-3.7.0-py3-none-any.whl", hash = "sha256:ace47f037956fa3780875f8556a0f27ab20d91962d36a9a2816aa367bb48718f", size = 15080131, upload-time = "2026-03-04T15:38:20.339Z" }, + { url = "https://files.pythonhosted.org/packages/5c/b8/011338eec8aea40cf9b82da7481f3e65e100537cff4c866b3c1b1e719b97/rapidocr-3.7.0-py3-none-any.whl", hash = "sha256:ace47f037956fa3780875f8556a0f27ab20d91962d36a9a2816aa367bb48718f", size = 15080131 }, ] [[package]] @@ -7233,18 +7216,18 @@ dependencies = [ { name = "nh3" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", size = 32056, upload-time = "2024-07-08T15:00:57.805Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", size = 32056 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", size = 13310, upload-time = "2024-07-08T15:00:56.577Z" }, + { url = "https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", size = 13310 }, ] [[package]] name = "redis" version = "6.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/d6/e8b92798a5bd67d659d51a18170e91c16ac3b59738d91894651ee255ed49/redis-6.4.0.tar.gz", hash = "sha256:b01bc7282b8444e28ec36b261df5375183bb47a07eb9c603f284e89cbc5ef010", size = 4647399, upload-time = "2025-08-07T08:10:11.441Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/d6/e8b92798a5bd67d659d51a18170e91c16ac3b59738d91894651ee255ed49/redis-6.4.0.tar.gz", hash = "sha256:b01bc7282b8444e28ec36b261df5375183bb47a07eb9c603f284e89cbc5ef010", size = 4647399 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" }, + { url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847 }, ] [[package]] @@ -7256,97 +7239,97 @@ dependencies = [ { name = "rpds-py" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766 }, ] [[package]] name = "regex" version = "2026.2.28" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8b/71/41455aa99a5a5ac1eaf311f5d8efd9ce6433c03ac1e0962de163350d0d97/regex-2026.2.28.tar.gz", hash = "sha256:a729e47d418ea11d03469f321aaf67cdee8954cde3ff2cf8403ab87951ad10f2", size = 415184, upload-time = "2026-02-28T02:19:42.792Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/71/41455aa99a5a5ac1eaf311f5d8efd9ce6433c03ac1e0962de163350d0d97/regex-2026.2.28.tar.gz", hash = "sha256:a729e47d418ea11d03469f321aaf67cdee8954cde3ff2cf8403ab87951ad10f2", size = 415184 } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/42/9061b03cf0fc4b5fa2c3984cbbaed54324377e440a5c5a29d29a72518d62/regex-2026.2.28-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fcf26c3c6d0da98fada8ae4ef0aa1c3405a431c0a77eb17306d38a89b02adcd7", size = 489574, upload-time = "2026-02-28T02:16:50.455Z" }, - { url = "https://files.pythonhosted.org/packages/77/83/0c8a5623a233015595e3da499c5a1c13720ac63c107897a6037bb97af248/regex-2026.2.28-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02473c954af35dd2defeb07e44182f5705b30ea3f351a7cbffa9177beb14da5d", size = 291426, upload-time = "2026-02-28T02:16:52.52Z" }, - { url = "https://files.pythonhosted.org/packages/9e/06/3ef1ac6910dc3295ebd71b1f9bfa737e82cfead211a18b319d45f85ddd09/regex-2026.2.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b65d33a17101569f86d9c5966a8b1d7fbf8afdda5a8aa219301b0a80f58cf7d", size = 289200, upload-time = "2026-02-28T02:16:54.08Z" }, - { url = "https://files.pythonhosted.org/packages/dd/c9/8cc8d850b35ab5650ff6756a1cb85286e2000b66c97520b29c1587455344/regex-2026.2.28-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71dcecaa113eebcc96622c17692672c2d104b1d71ddf7adeda90da7ddeb26fc", size = 796765, upload-time = "2026-02-28T02:16:55.905Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5d/57702597627fc23278ebf36fbb497ac91c0ce7fec89ac6c81e420ca3e38c/regex-2026.2.28-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:481df4623fa4969c8b11f3433ed7d5e3dc9cec0f008356c3212b3933fb77e3d8", size = 863093, upload-time = "2026-02-28T02:16:58.094Z" }, - { url = "https://files.pythonhosted.org/packages/02/6d/f3ecad537ca2811b4d26b54ca848cf70e04fcfc138667c146a9f3157779c/regex-2026.2.28-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64e7c6ad614573e0640f271e811a408d79a9e1fe62a46adb602f598df42a818d", size = 909455, upload-time = "2026-02-28T02:17:00.918Z" }, - { url = "https://files.pythonhosted.org/packages/9e/40/bb226f203caa22c1043c1ca79b36340156eca0f6a6742b46c3bb222a3a57/regex-2026.2.28-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6b08a06976ff4fb0d83077022fde3eca06c55432bb997d8c0495b9a4e9872f4", size = 802037, upload-time = "2026-02-28T02:17:02.842Z" }, - { url = "https://files.pythonhosted.org/packages/44/7c/c6d91d8911ac6803b45ca968e8e500c46934e58c0903cbc6d760ee817a0a/regex-2026.2.28-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:864cdd1a2ef5716b0ab468af40139e62ede1b3a53386b375ec0786bb6783fc05", size = 775113, upload-time = "2026-02-28T02:17:04.506Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/4a9368d168d47abd4158580b8c848709667b1cd293ff0c0c277279543bd0/regex-2026.2.28-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:511f7419f7afab475fd4d639d4aedfc54205bcb0800066753ef68a59f0f330b5", size = 784194, upload-time = "2026-02-28T02:17:06.888Z" }, - { url = "https://files.pythonhosted.org/packages/cc/bf/2c72ab5d8b7be462cb1651b5cc333da1d0068740342f350fcca3bca31947/regex-2026.2.28-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b42f7466e32bf15a961cf09f35fa6323cc72e64d3d2c990b10de1274a5da0a59", size = 856846, upload-time = "2026-02-28T02:17:09.11Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f4/6b65c979bb6d09f51bb2d2a7bc85de73c01ec73335d7ddd202dcb8cd1c8f/regex-2026.2.28-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8710d61737b0c0ce6836b1da7109f20d495e49b3809f30e27e9560be67a257bf", size = 763516, upload-time = "2026-02-28T02:17:11.004Z" }, - { url = "https://files.pythonhosted.org/packages/8e/32/29ea5e27400ee86d2cc2b4e80aa059df04eaf78b4f0c18576ae077aeff68/regex-2026.2.28-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4390c365fd2d45278f45afd4673cb90f7285f5701607e3ad4274df08e36140ae", size = 849278, upload-time = "2026-02-28T02:17:12.693Z" }, - { url = "https://files.pythonhosted.org/packages/1d/91/3233d03b5f865111cd517e1c95ee8b43e8b428d61fa73764a80c9bb6f537/regex-2026.2.28-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cb3b1db8ff6c7b8bf838ab05583ea15230cb2f678e569ab0e3a24d1e8320940b", size = 790068, upload-time = "2026-02-28T02:17:14.9Z" }, - { url = "https://files.pythonhosted.org/packages/76/92/abc706c1fb03b4580a09645b206a3fc032f5a9f457bc1a8038ac555658ab/regex-2026.2.28-cp312-cp312-win32.whl", hash = "sha256:f8ed9a5d4612df9d4de15878f0bc6aa7a268afbe5af21a3fdd97fa19516e978c", size = 266416, upload-time = "2026-02-28T02:17:17.15Z" }, - { url = "https://files.pythonhosted.org/packages/fa/06/2a6f7dff190e5fa9df9fb4acf2fdf17a1aa0f7f54596cba8de608db56b3a/regex-2026.2.28-cp312-cp312-win_amd64.whl", hash = "sha256:01d65fd24206c8e1e97e2e31b286c59009636c022eb5d003f52760b0f42155d4", size = 277297, upload-time = "2026-02-28T02:17:18.723Z" }, - { url = "https://files.pythonhosted.org/packages/b7/f0/58a2484851fadf284458fdbd728f580d55c1abac059ae9f048c63b92f427/regex-2026.2.28-cp312-cp312-win_arm64.whl", hash = "sha256:c0b5ccbb8ffb433939d248707d4a8b31993cb76ab1a0187ca886bf50e96df952", size = 270408, upload-time = "2026-02-28T02:17:20.328Z" }, - { url = "https://files.pythonhosted.org/packages/87/f6/dc9ef48c61b79c8201585bf37fa70cd781977da86e466cd94e8e95d2443b/regex-2026.2.28-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6d63a07e5ec8ce7184452cb00c41c37b49e67dc4f73b2955b5b8e782ea970784", size = 489311, upload-time = "2026-02-28T02:17:22.591Z" }, - { url = "https://files.pythonhosted.org/packages/95/c8/c20390f2232d3f7956f420f4ef1852608ad57aa26c3dd78516cb9f3dc913/regex-2026.2.28-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e59bc8f30414d283ae8ee1617b13d8112e7135cb92830f0ec3688cb29152585a", size = 291285, upload-time = "2026-02-28T02:17:24.355Z" }, - { url = "https://files.pythonhosted.org/packages/d2/a6/ba1068a631ebd71a230e7d8013fcd284b7c89c35f46f34a7da02082141b1/regex-2026.2.28-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:de0cf053139f96219ccfabb4a8dd2d217c8c82cb206c91d9f109f3f552d6b43d", size = 289051, upload-time = "2026-02-28T02:17:26.722Z" }, - { url = "https://files.pythonhosted.org/packages/1d/1b/7cc3b7af4c244c204b7a80924bd3d85aecd9ba5bc82b485c5806ee8cda9e/regex-2026.2.28-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb4db2f17e6484904f986c5a657cec85574c76b5c5e61c7aae9ffa1bc6224f95", size = 796842, upload-time = "2026-02-28T02:17:29.064Z" }, - { url = "https://files.pythonhosted.org/packages/24/87/26bd03efc60e0d772ac1e7b60a2e6325af98d974e2358f659c507d3c76db/regex-2026.2.28-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52b017b35ac2214d0db5f4f90e303634dc44e4aba4bd6235a27f97ecbe5b0472", size = 863083, upload-time = "2026-02-28T02:17:31.363Z" }, - { url = "https://files.pythonhosted.org/packages/ae/54/aeaf4afb1aa0a65e40de52a61dc2ac5b00a83c6cb081c8a1d0dda74f3010/regex-2026.2.28-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:69fc560ccbf08a09dc9b52ab69cacfae51e0ed80dc5693078bdc97db2f91ae96", size = 909412, upload-time = "2026-02-28T02:17:33.248Z" }, - { url = "https://files.pythonhosted.org/packages/12/2f/049901def913954e640d199bbc6a7ca2902b6aeda0e5da9d17f114100ec2/regex-2026.2.28-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e61eea47230eba62a31f3e8a0e3164d0f37ef9f40529fb2c79361bc6b53d2a92", size = 802101, upload-time = "2026-02-28T02:17:35.053Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/512fb9ff7f5b15ea204bb1967ebb649059446decacccb201381f9fa6aad4/regex-2026.2.28-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4f5c0b182ad4269e7381b7c27fdb0408399881f7a92a4624fd5487f2971dfc11", size = 775260, upload-time = "2026-02-28T02:17:37.692Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/9a92935878aba19bd72706b9db5646a6f993d99b3f6ed42c02ec8beb1d61/regex-2026.2.28-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:96f6269a2882fbb0ee76967116b83679dc628e68eaea44e90884b8d53d833881", size = 784311, upload-time = "2026-02-28T02:17:39.855Z" }, - { url = "https://files.pythonhosted.org/packages/09/d3/fc51a8a738a49a6b6499626580554c9466d3ea561f2b72cfdc72e4149773/regex-2026.2.28-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b5acd4b6a95f37c3c3828e5d053a7d4edaedb85de551db0153754924cb7c83e3", size = 856876, upload-time = "2026-02-28T02:17:42.317Z" }, - { url = "https://files.pythonhosted.org/packages/08/b7/2e641f3d084b120ca4c52e8c762a78da0b32bf03ef546330db3e2635dc5f/regex-2026.2.28-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2234059cfe33d9813a3677ef7667999caea9eeaa83fef98eb6ce15c6cf9e0215", size = 763632, upload-time = "2026-02-28T02:17:45.073Z" }, - { url = "https://files.pythonhosted.org/packages/fe/6d/0009021d97e79ee99f3d8641f0a8d001eed23479ade4c3125a5480bf3e2d/regex-2026.2.28-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c15af43c72a7fb0c97cbc66fa36a43546eddc5c06a662b64a0cbf30d6ac40944", size = 849320, upload-time = "2026-02-28T02:17:47.192Z" }, - { url = "https://files.pythonhosted.org/packages/05/7a/51cfbad5758f8edae430cb21961a9c8d04bce1dae4d2d18d4186eec7cfa1/regex-2026.2.28-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9185cc63359862a6e80fe97f696e04b0ad9a11c4ac0a4a927f979f611bfe3768", size = 790152, upload-time = "2026-02-28T02:17:49.067Z" }, - { url = "https://files.pythonhosted.org/packages/90/3d/a83e2b6b3daa142acb8c41d51de3876186307d5cb7490087031747662500/regex-2026.2.28-cp313-cp313-win32.whl", hash = "sha256:fb66e5245db9652abd7196ace599b04d9c0e4aa7c8f0e2803938377835780081", size = 266398, upload-time = "2026-02-28T02:17:50.744Z" }, - { url = "https://files.pythonhosted.org/packages/85/4f/16e9ebb1fe5425e11b9596c8d57bf8877dcb32391da0bfd33742e3290637/regex-2026.2.28-cp313-cp313-win_amd64.whl", hash = "sha256:71a911098be38c859ceb3f9a9ce43f4ed9f4c6720ad8684a066ea246b76ad9ff", size = 277282, upload-time = "2026-02-28T02:17:53.074Z" }, - { url = "https://files.pythonhosted.org/packages/07/b4/92851335332810c5a89723bf7a7e35c7209f90b7d4160024501717b28cc9/regex-2026.2.28-cp313-cp313-win_arm64.whl", hash = "sha256:39bb5727650b9a0275c6a6690f9bb3fe693a7e6cc5c3155b1240aedf8926423e", size = 270382, upload-time = "2026-02-28T02:17:54.888Z" }, - { url = "https://files.pythonhosted.org/packages/24/07/6c7e4cec1e585959e96cbc24299d97e4437a81173217af54f1804994e911/regex-2026.2.28-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:97054c55db06ab020342cc0d35d6f62a465fa7662871190175f1ad6c655c028f", size = 492541, upload-time = "2026-02-28T02:17:56.813Z" }, - { url = "https://files.pythonhosted.org/packages/7c/13/55eb22ada7f43d4f4bb3815b6132183ebc331c81bd496e2d1f3b8d862e0d/regex-2026.2.28-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d25a10811de831c2baa6aef3c0be91622f44dd8d31dd12e69f6398efb15e48b", size = 292984, upload-time = "2026-02-28T02:17:58.538Z" }, - { url = "https://files.pythonhosted.org/packages/5b/11/c301f8cb29ce9644a5ef85104c59244e6e7e90994a0f458da4d39baa8e17/regex-2026.2.28-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d6cfe798d8da41bb1862ed6e0cba14003d387c3c0c4a5d45591076ae9f0ce2f8", size = 291509, upload-time = "2026-02-28T02:18:00.208Z" }, - { url = "https://files.pythonhosted.org/packages/b5/43/aabe384ec1994b91796e903582427bc2ffaed9c4103819ed3c16d8e749f3/regex-2026.2.28-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd0ce43e71d825b7c0661f9c54d4d74bd97c56c3fd102a8985bcfea48236bacb", size = 809429, upload-time = "2026-02-28T02:18:02.328Z" }, - { url = "https://files.pythonhosted.org/packages/04/b8/8d2d987a816720c4f3109cee7c06a4b24ad0e02d4fc74919ab619e543737/regex-2026.2.28-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00945d007fd74a9084d2ab79b695b595c6b7ba3698972fadd43e23230c6979c1", size = 869422, upload-time = "2026-02-28T02:18:04.23Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ad/2c004509e763c0c3719f97c03eca26473bffb3868d54c5f280b8cd4f9e3d/regex-2026.2.28-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bec23c11cbbf09a4df32fe50d57cbdd777bc442269b6e39a1775654f1c95dee2", size = 915175, upload-time = "2026-02-28T02:18:06.791Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/fd429066da487ef555a9da73bf214894aec77fc8c66a261ee355a69871a8/regex-2026.2.28-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5cdcc17d935c8f9d3f4db5c2ebe2640c332e3822ad5d23c2f8e0228e6947943a", size = 812044, upload-time = "2026-02-28T02:18:08.736Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ca/feedb7055c62a3f7f659971bf45f0e0a87544b6b0cf462884761453f97c5/regex-2026.2.28-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a448af01e3d8031c89c5d902040b124a5e921a25c4e5e07a861ca591ce429341", size = 782056, upload-time = "2026-02-28T02:18:10.777Z" }, - { url = "https://files.pythonhosted.org/packages/95/30/1aa959ed0d25c1dd7dd5047ea8ba482ceaef38ce363c401fd32a6b923e60/regex-2026.2.28-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:10d28e19bd4888e4abf43bd3925f3c134c52fdf7259219003588a42e24c2aa25", size = 798743, upload-time = "2026-02-28T02:18:13.025Z" }, - { url = "https://files.pythonhosted.org/packages/3b/1f/dadb9cf359004784051c897dcf4d5d79895f73a1bbb7b827abaa4814ae80/regex-2026.2.28-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:99985a2c277dcb9ccb63f937451af5d65177af1efdeb8173ac55b61095a0a05c", size = 864633, upload-time = "2026-02-28T02:18:16.84Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f1/b9a25eb24e1cf79890f09e6ec971ee5b511519f1851de3453bc04f6c902b/regex-2026.2.28-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:e1e7b24cb3ae9953a560c563045d1ba56ee4749fbd05cf21ba571069bd7be81b", size = 770862, upload-time = "2026-02-28T02:18:18.892Z" }, - { url = "https://files.pythonhosted.org/packages/02/9a/c5cb10b7aa6f182f9247a30cc9527e326601f46f4df864ac6db588d11fcd/regex-2026.2.28-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d8511a01d0e4ee1992eb3ba19e09bc1866fe03f05129c3aec3fdc4cbc77aad3f", size = 854788, upload-time = "2026-02-28T02:18:21.475Z" }, - { url = "https://files.pythonhosted.org/packages/0a/50/414ba0731c4bd40b011fa4703b2cc86879ec060c64f2a906e65a56452589/regex-2026.2.28-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aaffaecffcd2479ce87aa1e74076c221700b7c804e48e98e62500ee748f0f550", size = 800184, upload-time = "2026-02-28T02:18:23.492Z" }, - { url = "https://files.pythonhosted.org/packages/69/50/0c7290987f97e7e6830b0d853f69dc4dc5852c934aae63e7fdcd76b4c383/regex-2026.2.28-cp313-cp313t-win32.whl", hash = "sha256:ef77bdde9c9eba3f7fa5b58084b29bbcc74bcf55fdbeaa67c102a35b5bd7e7cc", size = 269137, upload-time = "2026-02-28T02:18:25.375Z" }, - { url = "https://files.pythonhosted.org/packages/68/80/ef26ff90e74ceb4051ad6efcbbb8a4be965184a57e879ebcbdef327d18fa/regex-2026.2.28-cp313-cp313t-win_amd64.whl", hash = "sha256:98adf340100cbe6fbaf8e6dc75e28f2c191b1be50ffefe292fb0e6f6eefdb0d8", size = 280682, upload-time = "2026-02-28T02:18:27.205Z" }, - { url = "https://files.pythonhosted.org/packages/69/8b/fbad9c52e83ffe8f97e3ed1aa0516e6dff6bb633a41da9e64645bc7efdc5/regex-2026.2.28-cp313-cp313t-win_arm64.whl", hash = "sha256:2fb950ac1d88e6b6a9414381f403797b236f9fa17e1eee07683af72b1634207b", size = 271735, upload-time = "2026-02-28T02:18:29.015Z" }, - { url = "https://files.pythonhosted.org/packages/cf/03/691015f7a7cb1ed6dacb2ea5de5682e4858e05a4c5506b2839cd533bbcd6/regex-2026.2.28-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:78454178c7df31372ea737996fb7f36b3c2c92cccc641d251e072478afb4babc", size = 489497, upload-time = "2026-02-28T02:18:30.889Z" }, - { url = "https://files.pythonhosted.org/packages/c6/ba/8db8fd19afcbfa0e1036eaa70c05f20ca8405817d4ad7a38a6b4c2f031ac/regex-2026.2.28-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:5d10303dd18cedfd4d095543998404df656088240bcfd3cd20a8f95b861f74bd", size = 291295, upload-time = "2026-02-28T02:18:33.426Z" }, - { url = "https://files.pythonhosted.org/packages/5a/79/9aa0caf089e8defef9b857b52fc53801f62ff868e19e5c83d4a96612eba1/regex-2026.2.28-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:19a9c9e0a8f24f39d575a6a854d516b48ffe4cbdcb9de55cb0570a032556ecff", size = 289275, upload-time = "2026-02-28T02:18:35.247Z" }, - { url = "https://files.pythonhosted.org/packages/eb/26/ee53117066a30ef9c883bf1127eece08308ccf8ccd45c45a966e7a665385/regex-2026.2.28-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09500be324f49b470d907b3ef8af9afe857f5cca486f853853f7945ddbf75911", size = 797176, upload-time = "2026-02-28T02:18:37.15Z" }, - { url = "https://files.pythonhosted.org/packages/05/1b/67fb0495a97259925f343ae78b5d24d4a6624356ae138b57f18bd43006e4/regex-2026.2.28-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fb1c4ff62277d87a7335f2c1ea4e0387b8f2b3ad88a64efd9943906aafad4f33", size = 863813, upload-time = "2026-02-28T02:18:39.478Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/93ac9bbafc53618091c685c7ed40239a90bf9f2a82c983f0baa97cb7ae07/regex-2026.2.28-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b8b3f1be1738feadc69f62daa250c933e85c6f34fa378f54a7ff43807c1b9117", size = 908678, upload-time = "2026-02-28T02:18:41.619Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7a/a8f5e0561702b25239846a16349feece59712ae20598ebb205580332a471/regex-2026.2.28-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc8ed8c3f41c27acb83f7b6a9eb727a73fc6663441890c5cb3426a5f6a91ce7d", size = 801528, upload-time = "2026-02-28T02:18:43.624Z" }, - { url = "https://files.pythonhosted.org/packages/96/5d/ed6d4cbde80309854b1b9f42d9062fee38ade15f7eb4909f6ef2440403b5/regex-2026.2.28-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa539be029844c0ce1114762d2952ab6cfdd7c7c9bd72e0db26b94c3c36dcc5a", size = 775373, upload-time = "2026-02-28T02:18:46.102Z" }, - { url = "https://files.pythonhosted.org/packages/6a/e9/6e53c34e8068b9deec3e87210086ecb5b9efebdefca6b0d3fa43d66dcecb/regex-2026.2.28-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7900157786428a79615a8264dac1f12c9b02957c473c8110c6b1f972dcecaddf", size = 784859, upload-time = "2026-02-28T02:18:48.269Z" }, - { url = "https://files.pythonhosted.org/packages/48/3c/736e1c7ca7f0dcd2ae33819888fdc69058a349b7e5e84bc3e2f296bbf794/regex-2026.2.28-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0b1d2b07614d95fa2bf8a63fd1e98bd8fa2b4848dc91b1efbc8ba219fdd73952", size = 857813, upload-time = "2026-02-28T02:18:50.576Z" }, - { url = "https://files.pythonhosted.org/packages/6e/7c/48c4659ad9da61f58e79dbe8c05223e0006696b603c16eb6b5cbfbb52c27/regex-2026.2.28-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b389c61aa28a79c2e0527ac36da579869c2e235a5b208a12c5b5318cda2501d8", size = 763705, upload-time = "2026-02-28T02:18:52.59Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a1/bc1c261789283128165f71b71b4b221dd1b79c77023752a6074c102f18d8/regex-2026.2.28-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f467cb602f03fbd1ab1908f68b53c649ce393fde056628dc8c7e634dab6bfc07", size = 848734, upload-time = "2026-02-28T02:18:54.595Z" }, - { url = "https://files.pythonhosted.org/packages/10/d8/979407faf1397036e25a5ae778157366a911c0f382c62501009f4957cf86/regex-2026.2.28-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c8cb2deba42f5ec1ede46374e990f8adc5e6456a57ac1a261b19be6f28e4e6", size = 789871, upload-time = "2026-02-28T02:18:57.34Z" }, - { url = "https://files.pythonhosted.org/packages/03/23/da716821277115fcb1f4e3de1e5dc5023a1e6533598c486abf5448612579/regex-2026.2.28-cp314-cp314-win32.whl", hash = "sha256:9036b400b20e4858d56d117108d7813ed07bb7803e3eed766675862131135ca6", size = 271825, upload-time = "2026-02-28T02:18:59.202Z" }, - { url = "https://files.pythonhosted.org/packages/91/ff/90696f535d978d5f16a52a419be2770a8d8a0e7e0cfecdbfc31313df7fab/regex-2026.2.28-cp314-cp314-win_amd64.whl", hash = "sha256:1d367257cd86c1cbb97ea94e77b373a0bbc2224976e247f173d19e8f18b4afa7", size = 280548, upload-time = "2026-02-28T02:19:01.049Z" }, - { url = "https://files.pythonhosted.org/packages/69/f9/5e1b5652fc0af3fcdf7677e7df3ad2a0d47d669b34ac29a63bb177bb731b/regex-2026.2.28-cp314-cp314-win_arm64.whl", hash = "sha256:5e68192bb3a1d6fb2836da24aa494e413ea65853a21505e142e5b1064a595f3d", size = 273444, upload-time = "2026-02-28T02:19:03.255Z" }, - { url = "https://files.pythonhosted.org/packages/d3/eb/8389f9e940ac89bcf58d185e230a677b4fd07c5f9b917603ad5c0f8fa8fe/regex-2026.2.28-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a5dac14d0872eeb35260a8e30bac07ddf22adc1e3a0635b52b02e180d17c9c7e", size = 492546, upload-time = "2026-02-28T02:19:05.378Z" }, - { url = "https://files.pythonhosted.org/packages/7b/c7/09441d27ce2a6fa6a61ea3150ea4639c1dcda9b31b2ea07b80d6937b24dd/regex-2026.2.28-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ec0c608b7a7465ffadb344ed7c987ff2f11ee03f6a130b569aa74d8a70e8333c", size = 292986, upload-time = "2026-02-28T02:19:07.24Z" }, - { url = "https://files.pythonhosted.org/packages/fb/69/4144b60ed7760a6bd235e4087041f487aa4aa62b45618ce018b0c14833ea/regex-2026.2.28-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7815afb0ca45456613fdaf60ea9c993715511c8d53a83bc468305cbc0ee23c7", size = 291518, upload-time = "2026-02-28T02:19:09.698Z" }, - { url = "https://files.pythonhosted.org/packages/2d/be/77e5426cf5948c82f98c53582009ca9e94938c71f73a8918474f2e2990bb/regex-2026.2.28-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b059e71ec363968671693a78c5053bd9cb2fe410f9b8e4657e88377ebd603a2e", size = 809464, upload-time = "2026-02-28T02:19:12.494Z" }, - { url = "https://files.pythonhosted.org/packages/45/99/2c8c5ac90dc7d05c6e7d8e72c6a3599dc08cd577ac476898e91ca787d7f1/regex-2026.2.28-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8cf76f1a29f0e99dcfd7aef1551a9827588aae5a737fe31442021165f1920dc", size = 869553, upload-time = "2026-02-28T02:19:15.151Z" }, - { url = "https://files.pythonhosted.org/packages/53/34/daa66a342f0271e7737003abf6c3097aa0498d58c668dbd88362ef94eb5d/regex-2026.2.28-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:180e08a435a0319e6a4821c3468da18dc7001987e1c17ae1335488dfe7518dd8", size = 915289, upload-time = "2026-02-28T02:19:17.331Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c7/e22c2aaf0a12e7e22ab19b004bb78d32ca1ecc7ef245949935463c5567de/regex-2026.2.28-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e496956106fd59ba6322a8ea17141a27c5040e5ee8f9433ae92d4e5204462a0", size = 812156, upload-time = "2026-02-28T02:19:20.011Z" }, - { url = "https://files.pythonhosted.org/packages/7f/bb/2dc18c1efd9051cf389cd0d7a3a4d90f6804b9fff3a51b5dc3c85b935f71/regex-2026.2.28-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bba2b18d70eeb7b79950f12f633beeecd923f7c9ad6f6bae28e59b4cb3ab046b", size = 782215, upload-time = "2026-02-28T02:19:22.047Z" }, - { url = "https://files.pythonhosted.org/packages/17/1e/9e4ec9b9013931faa32226ec4aa3c71fe664a6d8a2b91ac56442128b332f/regex-2026.2.28-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6db7bfae0f8a2793ff1f7021468ea55e2699d0790eb58ee6ab36ae43aa00bc5b", size = 798925, upload-time = "2026-02-28T02:19:24.173Z" }, - { url = "https://files.pythonhosted.org/packages/71/57/a505927e449a9ccb41e2cc8d735e2abe3444b0213d1cf9cb364a8c1f2524/regex-2026.2.28-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d0b02e8b7e5874b48ae0f077ecca61c1a6a9f9895e9c6dfb191b55b242862033", size = 864701, upload-time = "2026-02-28T02:19:26.376Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ad/c62cb60cdd93e13eac5b3d9d6bd5d284225ed0e3329426f94d2552dd7cca/regex-2026.2.28-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:25b6eb660c5cf4b8c3407a1ed462abba26a926cc9965e164268a3267bcc06a43", size = 770899, upload-time = "2026-02-28T02:19:29.38Z" }, - { url = "https://files.pythonhosted.org/packages/3c/5a/874f861f5c3d5ab99633e8030dee1bc113db8e0be299d1f4b07f5b5ec349/regex-2026.2.28-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:5a932ea8ad5d0430351ff9c76c8db34db0d9f53c1d78f06022a21f4e290c5c18", size = 854727, upload-time = "2026-02-28T02:19:31.494Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ca/d2c03b0efde47e13db895b975b2be6a73ed90b8ba963677927283d43bf74/regex-2026.2.28-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1c2c95e1a2b0f89d01e821ff4de1be4b5d73d1f4b0bf679fa27c1ad8d2327f1a", size = 800366, upload-time = "2026-02-28T02:19:34.248Z" }, - { url = "https://files.pythonhosted.org/packages/14/bd/ee13b20b763b8989f7c75d592bfd5de37dc1181814a2a2747fedcf97e3ba/regex-2026.2.28-cp314-cp314t-win32.whl", hash = "sha256:bbb882061f742eb5d46f2f1bd5304055be0a66b783576de3d7eef1bed4778a6e", size = 274936, upload-time = "2026-02-28T02:19:36.313Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e7/d8020e39414c93af7f0d8688eabcecece44abfd5ce314b21dfda0eebd3d8/regex-2026.2.28-cp314-cp314t-win_amd64.whl", hash = "sha256:6591f281cb44dc13de9585b552cec6fc6cf47fb2fe7a48892295ee9bc4a612f9", size = 284779, upload-time = "2026-02-28T02:19:38.625Z" }, - { url = "https://files.pythonhosted.org/packages/13/c0/ad225f4a405827486f1955283407cf758b6d2fb966712644c5f5aef33d1b/regex-2026.2.28-cp314-cp314t-win_arm64.whl", hash = "sha256:dee50f1be42222f89767b64b283283ef963189da0dda4a515aa54a5563c62dec", size = 275010, upload-time = "2026-02-28T02:19:40.65Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/9061b03cf0fc4b5fa2c3984cbbaed54324377e440a5c5a29d29a72518d62/regex-2026.2.28-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fcf26c3c6d0da98fada8ae4ef0aa1c3405a431c0a77eb17306d38a89b02adcd7", size = 489574 }, + { url = "https://files.pythonhosted.org/packages/77/83/0c8a5623a233015595e3da499c5a1c13720ac63c107897a6037bb97af248/regex-2026.2.28-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02473c954af35dd2defeb07e44182f5705b30ea3f351a7cbffa9177beb14da5d", size = 291426 }, + { url = "https://files.pythonhosted.org/packages/9e/06/3ef1ac6910dc3295ebd71b1f9bfa737e82cfead211a18b319d45f85ddd09/regex-2026.2.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b65d33a17101569f86d9c5966a8b1d7fbf8afdda5a8aa219301b0a80f58cf7d", size = 289200 }, + { url = "https://files.pythonhosted.org/packages/dd/c9/8cc8d850b35ab5650ff6756a1cb85286e2000b66c97520b29c1587455344/regex-2026.2.28-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71dcecaa113eebcc96622c17692672c2d104b1d71ddf7adeda90da7ddeb26fc", size = 796765 }, + { url = "https://files.pythonhosted.org/packages/e9/5d/57702597627fc23278ebf36fbb497ac91c0ce7fec89ac6c81e420ca3e38c/regex-2026.2.28-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:481df4623fa4969c8b11f3433ed7d5e3dc9cec0f008356c3212b3933fb77e3d8", size = 863093 }, + { url = "https://files.pythonhosted.org/packages/02/6d/f3ecad537ca2811b4d26b54ca848cf70e04fcfc138667c146a9f3157779c/regex-2026.2.28-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64e7c6ad614573e0640f271e811a408d79a9e1fe62a46adb602f598df42a818d", size = 909455 }, + { url = "https://files.pythonhosted.org/packages/9e/40/bb226f203caa22c1043c1ca79b36340156eca0f6a6742b46c3bb222a3a57/regex-2026.2.28-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6b08a06976ff4fb0d83077022fde3eca06c55432bb997d8c0495b9a4e9872f4", size = 802037 }, + { url = "https://files.pythonhosted.org/packages/44/7c/c6d91d8911ac6803b45ca968e8e500c46934e58c0903cbc6d760ee817a0a/regex-2026.2.28-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:864cdd1a2ef5716b0ab468af40139e62ede1b3a53386b375ec0786bb6783fc05", size = 775113 }, + { url = "https://files.pythonhosted.org/packages/dc/8d/4a9368d168d47abd4158580b8c848709667b1cd293ff0c0c277279543bd0/regex-2026.2.28-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:511f7419f7afab475fd4d639d4aedfc54205bcb0800066753ef68a59f0f330b5", size = 784194 }, + { url = "https://files.pythonhosted.org/packages/cc/bf/2c72ab5d8b7be462cb1651b5cc333da1d0068740342f350fcca3bca31947/regex-2026.2.28-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b42f7466e32bf15a961cf09f35fa6323cc72e64d3d2c990b10de1274a5da0a59", size = 856846 }, + { url = "https://files.pythonhosted.org/packages/7c/f4/6b65c979bb6d09f51bb2d2a7bc85de73c01ec73335d7ddd202dcb8cd1c8f/regex-2026.2.28-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8710d61737b0c0ce6836b1da7109f20d495e49b3809f30e27e9560be67a257bf", size = 763516 }, + { url = "https://files.pythonhosted.org/packages/8e/32/29ea5e27400ee86d2cc2b4e80aa059df04eaf78b4f0c18576ae077aeff68/regex-2026.2.28-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4390c365fd2d45278f45afd4673cb90f7285f5701607e3ad4274df08e36140ae", size = 849278 }, + { url = "https://files.pythonhosted.org/packages/1d/91/3233d03b5f865111cd517e1c95ee8b43e8b428d61fa73764a80c9bb6f537/regex-2026.2.28-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cb3b1db8ff6c7b8bf838ab05583ea15230cb2f678e569ab0e3a24d1e8320940b", size = 790068 }, + { url = "https://files.pythonhosted.org/packages/76/92/abc706c1fb03b4580a09645b206a3fc032f5a9f457bc1a8038ac555658ab/regex-2026.2.28-cp312-cp312-win32.whl", hash = "sha256:f8ed9a5d4612df9d4de15878f0bc6aa7a268afbe5af21a3fdd97fa19516e978c", size = 266416 }, + { url = "https://files.pythonhosted.org/packages/fa/06/2a6f7dff190e5fa9df9fb4acf2fdf17a1aa0f7f54596cba8de608db56b3a/regex-2026.2.28-cp312-cp312-win_amd64.whl", hash = "sha256:01d65fd24206c8e1e97e2e31b286c59009636c022eb5d003f52760b0f42155d4", size = 277297 }, + { url = "https://files.pythonhosted.org/packages/b7/f0/58a2484851fadf284458fdbd728f580d55c1abac059ae9f048c63b92f427/regex-2026.2.28-cp312-cp312-win_arm64.whl", hash = "sha256:c0b5ccbb8ffb433939d248707d4a8b31993cb76ab1a0187ca886bf50e96df952", size = 270408 }, + { url = "https://files.pythonhosted.org/packages/87/f6/dc9ef48c61b79c8201585bf37fa70cd781977da86e466cd94e8e95d2443b/regex-2026.2.28-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6d63a07e5ec8ce7184452cb00c41c37b49e67dc4f73b2955b5b8e782ea970784", size = 489311 }, + { url = "https://files.pythonhosted.org/packages/95/c8/c20390f2232d3f7956f420f4ef1852608ad57aa26c3dd78516cb9f3dc913/regex-2026.2.28-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e59bc8f30414d283ae8ee1617b13d8112e7135cb92830f0ec3688cb29152585a", size = 291285 }, + { url = "https://files.pythonhosted.org/packages/d2/a6/ba1068a631ebd71a230e7d8013fcd284b7c89c35f46f34a7da02082141b1/regex-2026.2.28-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:de0cf053139f96219ccfabb4a8dd2d217c8c82cb206c91d9f109f3f552d6b43d", size = 289051 }, + { url = "https://files.pythonhosted.org/packages/1d/1b/7cc3b7af4c244c204b7a80924bd3d85aecd9ba5bc82b485c5806ee8cda9e/regex-2026.2.28-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb4db2f17e6484904f986c5a657cec85574c76b5c5e61c7aae9ffa1bc6224f95", size = 796842 }, + { url = "https://files.pythonhosted.org/packages/24/87/26bd03efc60e0d772ac1e7b60a2e6325af98d974e2358f659c507d3c76db/regex-2026.2.28-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52b017b35ac2214d0db5f4f90e303634dc44e4aba4bd6235a27f97ecbe5b0472", size = 863083 }, + { url = "https://files.pythonhosted.org/packages/ae/54/aeaf4afb1aa0a65e40de52a61dc2ac5b00a83c6cb081c8a1d0dda74f3010/regex-2026.2.28-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:69fc560ccbf08a09dc9b52ab69cacfae51e0ed80dc5693078bdc97db2f91ae96", size = 909412 }, + { url = "https://files.pythonhosted.org/packages/12/2f/049901def913954e640d199bbc6a7ca2902b6aeda0e5da9d17f114100ec2/regex-2026.2.28-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e61eea47230eba62a31f3e8a0e3164d0f37ef9f40529fb2c79361bc6b53d2a92", size = 802101 }, + { url = "https://files.pythonhosted.org/packages/7d/a5/512fb9ff7f5b15ea204bb1967ebb649059446decacccb201381f9fa6aad4/regex-2026.2.28-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4f5c0b182ad4269e7381b7c27fdb0408399881f7a92a4624fd5487f2971dfc11", size = 775260 }, + { url = "https://files.pythonhosted.org/packages/d1/a8/9a92935878aba19bd72706b9db5646a6f993d99b3f6ed42c02ec8beb1d61/regex-2026.2.28-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:96f6269a2882fbb0ee76967116b83679dc628e68eaea44e90884b8d53d833881", size = 784311 }, + { url = "https://files.pythonhosted.org/packages/09/d3/fc51a8a738a49a6b6499626580554c9466d3ea561f2b72cfdc72e4149773/regex-2026.2.28-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b5acd4b6a95f37c3c3828e5d053a7d4edaedb85de551db0153754924cb7c83e3", size = 856876 }, + { url = "https://files.pythonhosted.org/packages/08/b7/2e641f3d084b120ca4c52e8c762a78da0b32bf03ef546330db3e2635dc5f/regex-2026.2.28-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2234059cfe33d9813a3677ef7667999caea9eeaa83fef98eb6ce15c6cf9e0215", size = 763632 }, + { url = "https://files.pythonhosted.org/packages/fe/6d/0009021d97e79ee99f3d8641f0a8d001eed23479ade4c3125a5480bf3e2d/regex-2026.2.28-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c15af43c72a7fb0c97cbc66fa36a43546eddc5c06a662b64a0cbf30d6ac40944", size = 849320 }, + { url = "https://files.pythonhosted.org/packages/05/7a/51cfbad5758f8edae430cb21961a9c8d04bce1dae4d2d18d4186eec7cfa1/regex-2026.2.28-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9185cc63359862a6e80fe97f696e04b0ad9a11c4ac0a4a927f979f611bfe3768", size = 790152 }, + { url = "https://files.pythonhosted.org/packages/90/3d/a83e2b6b3daa142acb8c41d51de3876186307d5cb7490087031747662500/regex-2026.2.28-cp313-cp313-win32.whl", hash = "sha256:fb66e5245db9652abd7196ace599b04d9c0e4aa7c8f0e2803938377835780081", size = 266398 }, + { url = "https://files.pythonhosted.org/packages/85/4f/16e9ebb1fe5425e11b9596c8d57bf8877dcb32391da0bfd33742e3290637/regex-2026.2.28-cp313-cp313-win_amd64.whl", hash = "sha256:71a911098be38c859ceb3f9a9ce43f4ed9f4c6720ad8684a066ea246b76ad9ff", size = 277282 }, + { url = "https://files.pythonhosted.org/packages/07/b4/92851335332810c5a89723bf7a7e35c7209f90b7d4160024501717b28cc9/regex-2026.2.28-cp313-cp313-win_arm64.whl", hash = "sha256:39bb5727650b9a0275c6a6690f9bb3fe693a7e6cc5c3155b1240aedf8926423e", size = 270382 }, + { url = "https://files.pythonhosted.org/packages/24/07/6c7e4cec1e585959e96cbc24299d97e4437a81173217af54f1804994e911/regex-2026.2.28-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:97054c55db06ab020342cc0d35d6f62a465fa7662871190175f1ad6c655c028f", size = 492541 }, + { url = "https://files.pythonhosted.org/packages/7c/13/55eb22ada7f43d4f4bb3815b6132183ebc331c81bd496e2d1f3b8d862e0d/regex-2026.2.28-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d25a10811de831c2baa6aef3c0be91622f44dd8d31dd12e69f6398efb15e48b", size = 292984 }, + { url = "https://files.pythonhosted.org/packages/5b/11/c301f8cb29ce9644a5ef85104c59244e6e7e90994a0f458da4d39baa8e17/regex-2026.2.28-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d6cfe798d8da41bb1862ed6e0cba14003d387c3c0c4a5d45591076ae9f0ce2f8", size = 291509 }, + { url = "https://files.pythonhosted.org/packages/b5/43/aabe384ec1994b91796e903582427bc2ffaed9c4103819ed3c16d8e749f3/regex-2026.2.28-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd0ce43e71d825b7c0661f9c54d4d74bd97c56c3fd102a8985bcfea48236bacb", size = 809429 }, + { url = "https://files.pythonhosted.org/packages/04/b8/8d2d987a816720c4f3109cee7c06a4b24ad0e02d4fc74919ab619e543737/regex-2026.2.28-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00945d007fd74a9084d2ab79b695b595c6b7ba3698972fadd43e23230c6979c1", size = 869422 }, + { url = "https://files.pythonhosted.org/packages/fc/ad/2c004509e763c0c3719f97c03eca26473bffb3868d54c5f280b8cd4f9e3d/regex-2026.2.28-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bec23c11cbbf09a4df32fe50d57cbdd777bc442269b6e39a1775654f1c95dee2", size = 915175 }, + { url = "https://files.pythonhosted.org/packages/55/c2/fd429066da487ef555a9da73bf214894aec77fc8c66a261ee355a69871a8/regex-2026.2.28-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5cdcc17d935c8f9d3f4db5c2ebe2640c332e3822ad5d23c2f8e0228e6947943a", size = 812044 }, + { url = "https://files.pythonhosted.org/packages/5b/ca/feedb7055c62a3f7f659971bf45f0e0a87544b6b0cf462884761453f97c5/regex-2026.2.28-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a448af01e3d8031c89c5d902040b124a5e921a25c4e5e07a861ca591ce429341", size = 782056 }, + { url = "https://files.pythonhosted.org/packages/95/30/1aa959ed0d25c1dd7dd5047ea8ba482ceaef38ce363c401fd32a6b923e60/regex-2026.2.28-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:10d28e19bd4888e4abf43bd3925f3c134c52fdf7259219003588a42e24c2aa25", size = 798743 }, + { url = "https://files.pythonhosted.org/packages/3b/1f/dadb9cf359004784051c897dcf4d5d79895f73a1bbb7b827abaa4814ae80/regex-2026.2.28-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:99985a2c277dcb9ccb63f937451af5d65177af1efdeb8173ac55b61095a0a05c", size = 864633 }, + { url = "https://files.pythonhosted.org/packages/a7/f1/b9a25eb24e1cf79890f09e6ec971ee5b511519f1851de3453bc04f6c902b/regex-2026.2.28-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:e1e7b24cb3ae9953a560c563045d1ba56ee4749fbd05cf21ba571069bd7be81b", size = 770862 }, + { url = "https://files.pythonhosted.org/packages/02/9a/c5cb10b7aa6f182f9247a30cc9527e326601f46f4df864ac6db588d11fcd/regex-2026.2.28-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d8511a01d0e4ee1992eb3ba19e09bc1866fe03f05129c3aec3fdc4cbc77aad3f", size = 854788 }, + { url = "https://files.pythonhosted.org/packages/0a/50/414ba0731c4bd40b011fa4703b2cc86879ec060c64f2a906e65a56452589/regex-2026.2.28-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aaffaecffcd2479ce87aa1e74076c221700b7c804e48e98e62500ee748f0f550", size = 800184 }, + { url = "https://files.pythonhosted.org/packages/69/50/0c7290987f97e7e6830b0d853f69dc4dc5852c934aae63e7fdcd76b4c383/regex-2026.2.28-cp313-cp313t-win32.whl", hash = "sha256:ef77bdde9c9eba3f7fa5b58084b29bbcc74bcf55fdbeaa67c102a35b5bd7e7cc", size = 269137 }, + { url = "https://files.pythonhosted.org/packages/68/80/ef26ff90e74ceb4051ad6efcbbb8a4be965184a57e879ebcbdef327d18fa/regex-2026.2.28-cp313-cp313t-win_amd64.whl", hash = "sha256:98adf340100cbe6fbaf8e6dc75e28f2c191b1be50ffefe292fb0e6f6eefdb0d8", size = 280682 }, + { url = "https://files.pythonhosted.org/packages/69/8b/fbad9c52e83ffe8f97e3ed1aa0516e6dff6bb633a41da9e64645bc7efdc5/regex-2026.2.28-cp313-cp313t-win_arm64.whl", hash = "sha256:2fb950ac1d88e6b6a9414381f403797b236f9fa17e1eee07683af72b1634207b", size = 271735 }, + { url = "https://files.pythonhosted.org/packages/cf/03/691015f7a7cb1ed6dacb2ea5de5682e4858e05a4c5506b2839cd533bbcd6/regex-2026.2.28-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:78454178c7df31372ea737996fb7f36b3c2c92cccc641d251e072478afb4babc", size = 489497 }, + { url = "https://files.pythonhosted.org/packages/c6/ba/8db8fd19afcbfa0e1036eaa70c05f20ca8405817d4ad7a38a6b4c2f031ac/regex-2026.2.28-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:5d10303dd18cedfd4d095543998404df656088240bcfd3cd20a8f95b861f74bd", size = 291295 }, + { url = "https://files.pythonhosted.org/packages/5a/79/9aa0caf089e8defef9b857b52fc53801f62ff868e19e5c83d4a96612eba1/regex-2026.2.28-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:19a9c9e0a8f24f39d575a6a854d516b48ffe4cbdcb9de55cb0570a032556ecff", size = 289275 }, + { url = "https://files.pythonhosted.org/packages/eb/26/ee53117066a30ef9c883bf1127eece08308ccf8ccd45c45a966e7a665385/regex-2026.2.28-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09500be324f49b470d907b3ef8af9afe857f5cca486f853853f7945ddbf75911", size = 797176 }, + { url = "https://files.pythonhosted.org/packages/05/1b/67fb0495a97259925f343ae78b5d24d4a6624356ae138b57f18bd43006e4/regex-2026.2.28-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fb1c4ff62277d87a7335f2c1ea4e0387b8f2b3ad88a64efd9943906aafad4f33", size = 863813 }, + { url = "https://files.pythonhosted.org/packages/a0/1d/93ac9bbafc53618091c685c7ed40239a90bf9f2a82c983f0baa97cb7ae07/regex-2026.2.28-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b8b3f1be1738feadc69f62daa250c933e85c6f34fa378f54a7ff43807c1b9117", size = 908678 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/a8f5e0561702b25239846a16349feece59712ae20598ebb205580332a471/regex-2026.2.28-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc8ed8c3f41c27acb83f7b6a9eb727a73fc6663441890c5cb3426a5f6a91ce7d", size = 801528 }, + { url = "https://files.pythonhosted.org/packages/96/5d/ed6d4cbde80309854b1b9f42d9062fee38ade15f7eb4909f6ef2440403b5/regex-2026.2.28-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa539be029844c0ce1114762d2952ab6cfdd7c7c9bd72e0db26b94c3c36dcc5a", size = 775373 }, + { url = "https://files.pythonhosted.org/packages/6a/e9/6e53c34e8068b9deec3e87210086ecb5b9efebdefca6b0d3fa43d66dcecb/regex-2026.2.28-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7900157786428a79615a8264dac1f12c9b02957c473c8110c6b1f972dcecaddf", size = 784859 }, + { url = "https://files.pythonhosted.org/packages/48/3c/736e1c7ca7f0dcd2ae33819888fdc69058a349b7e5e84bc3e2f296bbf794/regex-2026.2.28-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0b1d2b07614d95fa2bf8a63fd1e98bd8fa2b4848dc91b1efbc8ba219fdd73952", size = 857813 }, + { url = "https://files.pythonhosted.org/packages/6e/7c/48c4659ad9da61f58e79dbe8c05223e0006696b603c16eb6b5cbfbb52c27/regex-2026.2.28-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b389c61aa28a79c2e0527ac36da579869c2e235a5b208a12c5b5318cda2501d8", size = 763705 }, + { url = "https://files.pythonhosted.org/packages/cf/a1/bc1c261789283128165f71b71b4b221dd1b79c77023752a6074c102f18d8/regex-2026.2.28-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f467cb602f03fbd1ab1908f68b53c649ce393fde056628dc8c7e634dab6bfc07", size = 848734 }, + { url = "https://files.pythonhosted.org/packages/10/d8/979407faf1397036e25a5ae778157366a911c0f382c62501009f4957cf86/regex-2026.2.28-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c8cb2deba42f5ec1ede46374e990f8adc5e6456a57ac1a261b19be6f28e4e6", size = 789871 }, + { url = "https://files.pythonhosted.org/packages/03/23/da716821277115fcb1f4e3de1e5dc5023a1e6533598c486abf5448612579/regex-2026.2.28-cp314-cp314-win32.whl", hash = "sha256:9036b400b20e4858d56d117108d7813ed07bb7803e3eed766675862131135ca6", size = 271825 }, + { url = "https://files.pythonhosted.org/packages/91/ff/90696f535d978d5f16a52a419be2770a8d8a0e7e0cfecdbfc31313df7fab/regex-2026.2.28-cp314-cp314-win_amd64.whl", hash = "sha256:1d367257cd86c1cbb97ea94e77b373a0bbc2224976e247f173d19e8f18b4afa7", size = 280548 }, + { url = "https://files.pythonhosted.org/packages/69/f9/5e1b5652fc0af3fcdf7677e7df3ad2a0d47d669b34ac29a63bb177bb731b/regex-2026.2.28-cp314-cp314-win_arm64.whl", hash = "sha256:5e68192bb3a1d6fb2836da24aa494e413ea65853a21505e142e5b1064a595f3d", size = 273444 }, + { url = "https://files.pythonhosted.org/packages/d3/eb/8389f9e940ac89bcf58d185e230a677b4fd07c5f9b917603ad5c0f8fa8fe/regex-2026.2.28-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a5dac14d0872eeb35260a8e30bac07ddf22adc1e3a0635b52b02e180d17c9c7e", size = 492546 }, + { url = "https://files.pythonhosted.org/packages/7b/c7/09441d27ce2a6fa6a61ea3150ea4639c1dcda9b31b2ea07b80d6937b24dd/regex-2026.2.28-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ec0c608b7a7465ffadb344ed7c987ff2f11ee03f6a130b569aa74d8a70e8333c", size = 292986 }, + { url = "https://files.pythonhosted.org/packages/fb/69/4144b60ed7760a6bd235e4087041f487aa4aa62b45618ce018b0c14833ea/regex-2026.2.28-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7815afb0ca45456613fdaf60ea9c993715511c8d53a83bc468305cbc0ee23c7", size = 291518 }, + { url = "https://files.pythonhosted.org/packages/2d/be/77e5426cf5948c82f98c53582009ca9e94938c71f73a8918474f2e2990bb/regex-2026.2.28-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b059e71ec363968671693a78c5053bd9cb2fe410f9b8e4657e88377ebd603a2e", size = 809464 }, + { url = "https://files.pythonhosted.org/packages/45/99/2c8c5ac90dc7d05c6e7d8e72c6a3599dc08cd577ac476898e91ca787d7f1/regex-2026.2.28-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8cf76f1a29f0e99dcfd7aef1551a9827588aae5a737fe31442021165f1920dc", size = 869553 }, + { url = "https://files.pythonhosted.org/packages/53/34/daa66a342f0271e7737003abf6c3097aa0498d58c668dbd88362ef94eb5d/regex-2026.2.28-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:180e08a435a0319e6a4821c3468da18dc7001987e1c17ae1335488dfe7518dd8", size = 915289 }, + { url = "https://files.pythonhosted.org/packages/c5/c7/e22c2aaf0a12e7e22ab19b004bb78d32ca1ecc7ef245949935463c5567de/regex-2026.2.28-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e496956106fd59ba6322a8ea17141a27c5040e5ee8f9433ae92d4e5204462a0", size = 812156 }, + { url = "https://files.pythonhosted.org/packages/7f/bb/2dc18c1efd9051cf389cd0d7a3a4d90f6804b9fff3a51b5dc3c85b935f71/regex-2026.2.28-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bba2b18d70eeb7b79950f12f633beeecd923f7c9ad6f6bae28e59b4cb3ab046b", size = 782215 }, + { url = "https://files.pythonhosted.org/packages/17/1e/9e4ec9b9013931faa32226ec4aa3c71fe664a6d8a2b91ac56442128b332f/regex-2026.2.28-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6db7bfae0f8a2793ff1f7021468ea55e2699d0790eb58ee6ab36ae43aa00bc5b", size = 798925 }, + { url = "https://files.pythonhosted.org/packages/71/57/a505927e449a9ccb41e2cc8d735e2abe3444b0213d1cf9cb364a8c1f2524/regex-2026.2.28-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d0b02e8b7e5874b48ae0f077ecca61c1a6a9f9895e9c6dfb191b55b242862033", size = 864701 }, + { url = "https://files.pythonhosted.org/packages/a6/ad/c62cb60cdd93e13eac5b3d9d6bd5d284225ed0e3329426f94d2552dd7cca/regex-2026.2.28-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:25b6eb660c5cf4b8c3407a1ed462abba26a926cc9965e164268a3267bcc06a43", size = 770899 }, + { url = "https://files.pythonhosted.org/packages/3c/5a/874f861f5c3d5ab99633e8030dee1bc113db8e0be299d1f4b07f5b5ec349/regex-2026.2.28-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:5a932ea8ad5d0430351ff9c76c8db34db0d9f53c1d78f06022a21f4e290c5c18", size = 854727 }, + { url = "https://files.pythonhosted.org/packages/6b/ca/d2c03b0efde47e13db895b975b2be6a73ed90b8ba963677927283d43bf74/regex-2026.2.28-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1c2c95e1a2b0f89d01e821ff4de1be4b5d73d1f4b0bf679fa27c1ad8d2327f1a", size = 800366 }, + { url = "https://files.pythonhosted.org/packages/14/bd/ee13b20b763b8989f7c75d592bfd5de37dc1181814a2a2747fedcf97e3ba/regex-2026.2.28-cp314-cp314t-win32.whl", hash = "sha256:bbb882061f742eb5d46f2f1bd5304055be0a66b783576de3d7eef1bed4778a6e", size = 274936 }, + { url = "https://files.pythonhosted.org/packages/cb/e7/d8020e39414c93af7f0d8688eabcecece44abfd5ce314b21dfda0eebd3d8/regex-2026.2.28-cp314-cp314t-win_amd64.whl", hash = "sha256:6591f281cb44dc13de9585b552cec6fc6cf47fb2fe7a48892295ee9bc4a612f9", size = 284779 }, + { url = "https://files.pythonhosted.org/packages/13/c0/ad225f4a405827486f1955283407cf758b6d2fb966712644c5f5aef33d1b/regex-2026.2.28-cp314-cp314t-win_arm64.whl", hash = "sha256:dee50f1be42222f89767b64b283283ef963189da0dda4a515aa54a5563c62dec", size = 275010 }, ] [[package]] @@ -7359,9 +7342,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, + { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017 }, ] [[package]] @@ -7372,9 +7355,9 @@ dependencies = [ { name = "oauthlib" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179 }, ] [[package]] @@ -7384,18 +7367,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481 }, ] [[package]] name = "rerankers" version = "0.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/1e/3ed2026be7c135939905eac4f50d1bf8339180821c6757b2e91b83de2fa5/rerankers-0.10.0.tar.gz", hash = "sha256:b8e8b363abc4e9757151956949c27b197993c0a774437287a932f855afc17a73", size = 49679, upload-time = "2025-05-22T08:22:53.396Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/1e/3ed2026be7c135939905eac4f50d1bf8339180821c6757b2e91b83de2fa5/rerankers-0.10.0.tar.gz", hash = "sha256:b8e8b363abc4e9757151956949c27b197993c0a774437287a932f855afc17a73", size = 49679 } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/ed/f3b81ca8743d69b95d679b95e6e1d22cb7cc678ae77c6a57827303a7e48c/rerankers-0.10.0-py3-none-any.whl", hash = "sha256:634a6befa130a245ed46022ade217ee482869448f01aae2051ed54d7d5bd2791", size = 53084, upload-time = "2025-05-22T08:22:52.022Z" }, + { url = "https://files.pythonhosted.org/packages/df/ed/f3b81ca8743d69b95d679b95e6e1d22cb7cc678ae77c6a57827303a7e48c/rerankers-0.10.0-py3-none-any.whl", hash = "sha256:634a6befa130a245ed46022ade217ee482869448f01aae2051ed54d7d5bd2791", size = 53084 }, ] [package.optional-dependencies] @@ -7407,9 +7390,9 @@ flashrank = [ name = "rfc3986" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326 }, ] [[package]] @@ -7420,131 +7403,131 @@ dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582 } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458 }, ] [[package]] name = "rpds-py" version = "0.30.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469 } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, - { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, - { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, - { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, - { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, - { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, - { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, - { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, - { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, - { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, - { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, - { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, - { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, - { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, - { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, - { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, - { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, - { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, - { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, - { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, - { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, - { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, - { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, - { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, - { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, - { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, - { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, - { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, - { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, - { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, - { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, - { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, - { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, - { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, - { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, - { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, - { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, - { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, - { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, - { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, - { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, - { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, - { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, - { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, - { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, - { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, - { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, - { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, - { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, - { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086 }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053 }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763 }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951 }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622 }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492 }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080 }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680 }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589 }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289 }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737 }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120 }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782 }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463 }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868 }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887 }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904 }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945 }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783 }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021 }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589 }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025 }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895 }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799 }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731 }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027 }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020 }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139 }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224 }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645 }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443 }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375 }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850 }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812 }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841 }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149 }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843 }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507 }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949 }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790 }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217 }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806 }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341 }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768 }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099 }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192 }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080 }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841 }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670 }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005 }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112 }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049 }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661 }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606 }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126 }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371 }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298 }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604 }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391 }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868 }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747 }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795 }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330 }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194 }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340 }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765 }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834 }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470 }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630 }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148 }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030 }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570 }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532 }, ] [[package]] name = "rtree" version = "1.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/09/7302695875a019514de9a5dd17b8320e7a19d6e7bc8f85dcfb79a4ce2da3/rtree-1.4.1.tar.gz", hash = "sha256:c6b1b3550881e57ebe530cc6cffefc87cd9bf49c30b37b894065a9f810875e46", size = 52425, upload-time = "2025-08-13T19:32:01.413Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/09/7302695875a019514de9a5dd17b8320e7a19d6e7bc8f85dcfb79a4ce2da3/rtree-1.4.1.tar.gz", hash = "sha256:c6b1b3550881e57ebe530cc6cffefc87cd9bf49c30b37b894065a9f810875e46", size = 52425 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/d9/108cd989a4c0954e60b3cdc86fd2826407702b5375f6dfdab2802e5fed98/rtree-1.4.1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:d672184298527522d4914d8ae53bf76982b86ca420b0acde9298a7a87d81d4a4", size = 468484, upload-time = "2025-08-13T19:31:50.593Z" }, - { url = "https://files.pythonhosted.org/packages/f3/cf/2710b6fd6b07ea0aef317b29f335790ba6adf06a28ac236078ed9bd8a91d/rtree-1.4.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a7e48d805e12011c2cf739a29d6a60ae852fb1de9fc84220bbcef67e6e595d7d", size = 436325, upload-time = "2025-08-13T19:31:52.367Z" }, - { url = "https://files.pythonhosted.org/packages/55/e1/4d075268a46e68db3cac51846eb6a3ab96ed481c585c5a1ad411b3c23aad/rtree-1.4.1-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efa8c4496e31e9ad58ff6c7df89abceac7022d906cb64a3e18e4fceae6b77f65", size = 459789, upload-time = "2025-08-13T19:31:53.926Z" }, - { url = "https://files.pythonhosted.org/packages/d1/75/e5d44be90525cd28503e7f836d077ae6663ec0687a13ba7810b4114b3668/rtree-1.4.1-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12de4578f1b3381a93a655846900be4e3d5f4cd5e306b8b00aa77c1121dc7e8c", size = 507644, upload-time = "2025-08-13T19:31:55.164Z" }, - { url = "https://files.pythonhosted.org/packages/fd/85/b8684f769a142163b52859a38a486493b05bafb4f2fb71d4f945de28ebf9/rtree-1.4.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b558edda52eca3e6d1ee629042192c65e6b7f2c150d6d6cd207ce82f85be3967", size = 1454478, upload-time = "2025-08-13T19:31:56.808Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a4/c2292b95246b9165cc43a0c3757e80995d58bc9b43da5cb47ad6e3535213/rtree-1.4.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f155bc8d6bac9dcd383481dee8c130947a4866db1d16cb6dff442329a038a0dc", size = 1555140, upload-time = "2025-08-13T19:31:58.031Z" }, - { url = "https://files.pythonhosted.org/packages/74/25/5282c8270bfcd620d3e73beb35b40ac4ab00f0a898d98ebeb41ef0989ec8/rtree-1.4.1-py3-none-win_amd64.whl", hash = "sha256:efe125f416fd27150197ab8521158662943a40f87acab8028a1aac4ad667a489", size = 389358, upload-time = "2025-08-13T19:31:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/3f/50/0a9e7e7afe7339bd5e36911f0ceb15fed51945836ed803ae5afd661057fd/rtree-1.4.1-py3-none-win_arm64.whl", hash = "sha256:3d46f55729b28138e897ffef32f7ce93ac335cb67f9120125ad3742a220800f0", size = 355253, upload-time = "2025-08-13T19:32:00.296Z" }, + { url = "https://files.pythonhosted.org/packages/04/d9/108cd989a4c0954e60b3cdc86fd2826407702b5375f6dfdab2802e5fed98/rtree-1.4.1-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:d672184298527522d4914d8ae53bf76982b86ca420b0acde9298a7a87d81d4a4", size = 468484 }, + { url = "https://files.pythonhosted.org/packages/f3/cf/2710b6fd6b07ea0aef317b29f335790ba6adf06a28ac236078ed9bd8a91d/rtree-1.4.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a7e48d805e12011c2cf739a29d6a60ae852fb1de9fc84220bbcef67e6e595d7d", size = 436325 }, + { url = "https://files.pythonhosted.org/packages/55/e1/4d075268a46e68db3cac51846eb6a3ab96ed481c585c5a1ad411b3c23aad/rtree-1.4.1-py3-none-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efa8c4496e31e9ad58ff6c7df89abceac7022d906cb64a3e18e4fceae6b77f65", size = 459789 }, + { url = "https://files.pythonhosted.org/packages/d1/75/e5d44be90525cd28503e7f836d077ae6663ec0687a13ba7810b4114b3668/rtree-1.4.1-py3-none-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12de4578f1b3381a93a655846900be4e3d5f4cd5e306b8b00aa77c1121dc7e8c", size = 507644 }, + { url = "https://files.pythonhosted.org/packages/fd/85/b8684f769a142163b52859a38a486493b05bafb4f2fb71d4f945de28ebf9/rtree-1.4.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b558edda52eca3e6d1ee629042192c65e6b7f2c150d6d6cd207ce82f85be3967", size = 1454478 }, + { url = "https://files.pythonhosted.org/packages/e9/a4/c2292b95246b9165cc43a0c3757e80995d58bc9b43da5cb47ad6e3535213/rtree-1.4.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f155bc8d6bac9dcd383481dee8c130947a4866db1d16cb6dff442329a038a0dc", size = 1555140 }, + { url = "https://files.pythonhosted.org/packages/74/25/5282c8270bfcd620d3e73beb35b40ac4ab00f0a898d98ebeb41ef0989ec8/rtree-1.4.1-py3-none-win_amd64.whl", hash = "sha256:efe125f416fd27150197ab8521158662943a40f87acab8028a1aac4ad667a489", size = 389358 }, + { url = "https://files.pythonhosted.org/packages/3f/50/0a9e7e7afe7339bd5e36911f0ceb15fed51945836ed803ae5afd661057fd/rtree-1.4.1-py3-none-win_arm64.whl", hash = "sha256:3d46f55729b28138e897ffef32f7ce93ac335cb67f9120125ad3742a220800f0", size = 355253 }, ] [[package]] name = "ruff" version = "0.15.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921, upload-time = "2026-03-26T18:39:38.675Z" } +sdist = { url = "https://files.pythonhosted.org/packages/14/b0/73cf7550861e2b4824950b8b52eebdcc5adc792a00c514406556c5b80817/ruff-0.15.8.tar.gz", hash = "sha256:995f11f63597ee362130d1d5a327a87cb6f3f5eae3094c620bcc632329a4d26e", size = 4610921 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394, upload-time = "2026-03-26T18:39:41.566Z" }, - { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693, upload-time = "2026-03-26T18:39:30.364Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044, upload-time = "2026-03-26T18:39:33.37Z" }, - { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135, upload-time = "2026-03-26T18:39:44.142Z" }, - { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041, upload-time = "2026-03-26T18:39:52.178Z" }, - { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987, upload-time = "2026-03-26T18:39:55.195Z" }, - { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057, upload-time = "2026-03-26T18:39:19.18Z" }, - { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613, upload-time = "2026-03-26T18:40:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557, upload-time = "2026-03-26T18:39:57.972Z" }, - { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440, upload-time = "2026-03-26T18:39:22.205Z" }, - { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963, upload-time = "2026-03-26T18:39:46.682Z" }, - { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484, upload-time = "2026-03-26T18:39:49.176Z" }, - { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426, upload-time = "2026-03-26T18:40:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125, upload-time = "2026-03-26T18:39:27.799Z" }, - { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959, upload-time = "2026-03-26T18:39:36.117Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893, upload-time = "2026-03-26T18:39:25.01Z" }, - { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175, upload-time = "2026-03-26T18:40:01.06Z" }, + { url = "https://files.pythonhosted.org/packages/4a/92/c445b0cd6da6e7ae51e954939cb69f97e008dbe750cfca89b8cedc081be7/ruff-0.15.8-py3-none-linux_armv6l.whl", hash = "sha256:cbe05adeba76d58162762d6b239c9056f1a15a55bd4b346cfd21e26cd6ad7bc7", size = 10527394 }, + { url = "https://files.pythonhosted.org/packages/eb/92/f1c662784d149ad1414cae450b082cf736430c12ca78367f20f5ed569d65/ruff-0.15.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d3e3d0b6ba8dca1b7ef9ab80a28e840a20070c4b62e56d675c24f366ef330570", size = 10905693 }, + { url = "https://files.pythonhosted.org/packages/ca/f2/7a631a8af6d88bcef997eb1bf87cc3da158294c57044aafd3e17030613de/ruff-0.15.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ee3ae5c65a42f273f126686353f2e08ff29927b7b7e203b711514370d500de3", size = 10323044 }, + { url = "https://files.pythonhosted.org/packages/67/18/1bf38e20914a05e72ef3b9569b1d5c70a7ef26cd188d69e9ca8ef588d5bf/ruff-0.15.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdce027ada77baa448077ccc6ebb2fa9c3c62fd110d8659d601cf2f475858d94", size = 10629135 }, + { url = "https://files.pythonhosted.org/packages/d2/e9/138c150ff9af60556121623d41aba18b7b57d95ac032e177b6a53789d279/ruff-0.15.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12e617fc01a95e5821648a6df341d80456bd627bfab8a829f7cfc26a14a4b4a3", size = 10348041 }, + { url = "https://files.pythonhosted.org/packages/02/f1/5bfb9298d9c323f842c5ddeb85f1f10ef51516ac7a34ba446c9347d898df/ruff-0.15.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:432701303b26416d22ba696c39f2c6f12499b89093b61360abc34bcc9bf07762", size = 11121987 }, + { url = "https://files.pythonhosted.org/packages/10/11/6da2e538704e753c04e8d86b1fc55712fdbdcc266af1a1ece7a51fff0d10/ruff-0.15.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d910ae974b7a06a33a057cb87d2a10792a3b2b3b35e33d2699fdf63ec8f6b17a", size = 11951057 }, + { url = "https://files.pythonhosted.org/packages/83/f0/c9208c5fd5101bf87002fed774ff25a96eea313d305f1e5d5744698dc314/ruff-0.15.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2033f963c43949d51e6fdccd3946633c6b37c484f5f98c3035f49c27395a8ab8", size = 11464613 }, + { url = "https://files.pythonhosted.org/packages/f8/22/d7f2fabdba4fae9f3b570e5605d5eb4500dcb7b770d3217dca4428484b17/ruff-0.15.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f29b989a55572fb885b77464cf24af05500806ab4edf9a0fd8977f9759d85b1", size = 11257557 }, + { url = "https://files.pythonhosted.org/packages/71/8c/382a9620038cf6906446b23ce8632ab8c0811b8f9d3e764f58bedd0c9a6f/ruff-0.15.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:ac51d486bf457cdc985a412fb1801b2dfd1bd8838372fc55de64b1510eff4bec", size = 11169440 }, + { url = "https://files.pythonhosted.org/packages/4d/0d/0994c802a7eaaf99380085e4e40c845f8e32a562e20a38ec06174b52ef24/ruff-0.15.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c9861eb959edab053c10ad62c278835ee69ca527b6dcd72b47d5c1e5648964f6", size = 10605963 }, + { url = "https://files.pythonhosted.org/packages/19/aa/d624b86f5b0aad7cef6bbf9cd47a6a02dfdc4f72c92a337d724e39c9d14b/ruff-0.15.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8d9a5b8ea13f26ae90838afc33f91b547e61b794865374f114f349e9036835fb", size = 10357484 }, + { url = "https://files.pythonhosted.org/packages/35/c3/e0b7835d23001f7d999f3895c6b569927c4d39912286897f625736e1fd04/ruff-0.15.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c2a33a529fb3cbc23a7124b5c6ff121e4d6228029cba374777bd7649cc8598b8", size = 10830426 }, + { url = "https://files.pythonhosted.org/packages/f0/51/ab20b322f637b369383adc341d761eaaa0f0203d6b9a7421cd6e783d81b9/ruff-0.15.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:75e5cd06b1cf3f47a3996cfc999226b19aa92e7cce682dcd62f80d7035f98f49", size = 11345125 }, + { url = "https://files.pythonhosted.org/packages/37/e6/90b2b33419f59d0f2c4c8a48a4b74b460709a557e8e0064cf33ad894f983/ruff-0.15.8-py3-none-win32.whl", hash = "sha256:bc1f0a51254ba21767bfa9a8b5013ca8149dcf38092e6a9eb704d876de94dc34", size = 10571959 }, + { url = "https://files.pythonhosted.org/packages/1f/a2/ef467cb77099062317154c63f234b8a7baf7cb690b99af760c5b68b9ee7f/ruff-0.15.8-py3-none-win_amd64.whl", hash = "sha256:04f79eff02a72db209d47d665ba7ebcad609d8918a134f86cb13dd132159fc89", size = 11743893 }, + { url = "https://files.pythonhosted.org/packages/15/e2/77be4fff062fa78d9b2a4dea85d14785dac5f1d0c1fb58ed52331f0ebe28/ruff-0.15.8-py3-none-win_arm64.whl", hash = "sha256:cf891fa8e3bb430c0e7fac93851a5978fc99c8fa2c053b57b118972866f8e5f2", size = 11048175 }, ] [[package]] @@ -7554,31 +7537,31 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, + { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830 }, ] [[package]] name = "safetensors" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/9c/6e74567782559a63bd040a236edca26fd71bc7ba88de2ef35d75df3bca5e/safetensors-0.7.0.tar.gz", hash = "sha256:07663963b67e8bd9f0b8ad15bb9163606cd27cc5a1b96235a50d8369803b96b0", size = 200878, upload-time = "2025-11-19T15:18:43.199Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/9c/6e74567782559a63bd040a236edca26fd71bc7ba88de2ef35d75df3bca5e/safetensors-0.7.0.tar.gz", hash = "sha256:07663963b67e8bd9f0b8ad15bb9163606cd27cc5a1b96235a50d8369803b96b0", size = 200878 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/47/aef6c06649039accf914afef490268e1067ed82be62bcfa5b7e886ad15e8/safetensors-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c82f4d474cf725255d9e6acf17252991c3c8aac038d6ef363a4bf8be2f6db517", size = 467781, upload-time = "2025-11-19T15:18:35.84Z" }, - { url = "https://files.pythonhosted.org/packages/e8/00/374c0c068e30cd31f1e1b46b4b5738168ec79e7689ca82ee93ddfea05109/safetensors-0.7.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:94fd4858284736bb67a897a41608b5b0c2496c9bdb3bf2af1fa3409127f20d57", size = 447058, upload-time = "2025-11-19T15:18:34.416Z" }, - { url = "https://files.pythonhosted.org/packages/f1/06/578ffed52c2296f93d7fd2d844cabfa92be51a587c38c8afbb8ae449ca89/safetensors-0.7.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e07d91d0c92a31200f25351f4acb2bc6aff7f48094e13ebb1d0fb995b54b6542", size = 491748, upload-time = "2025-11-19T15:18:09.79Z" }, - { url = "https://files.pythonhosted.org/packages/ae/33/1debbbb70e4791dde185edb9413d1fe01619255abb64b300157d7f15dddd/safetensors-0.7.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8469155f4cb518bafb4acf4865e8bb9d6804110d2d9bdcaa78564b9fd841e104", size = 503881, upload-time = "2025-11-19T15:18:16.145Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1c/40c2ca924d60792c3be509833df711b553c60effbd91da6f5284a83f7122/safetensors-0.7.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54bef08bf00a2bff599982f6b08e8770e09cc012d7bba00783fc7ea38f1fb37d", size = 623463, upload-time = "2025-11-19T15:18:21.11Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3a/13784a9364bd43b0d61eef4bea2845039bc2030458b16594a1bd787ae26e/safetensors-0.7.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42cb091236206bb2016d245c377ed383aa7f78691748f3bb6ee1bfa51ae2ce6a", size = 532855, upload-time = "2025-11-19T15:18:25.719Z" }, - { url = "https://files.pythonhosted.org/packages/a0/60/429e9b1cb3fc651937727befe258ea24122d9663e4d5709a48c9cbfceecb/safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac7252938f0696ddea46f5e855dd3138444e82236e3be475f54929f0c510d48", size = 507152, upload-time = "2025-11-19T15:18:33.023Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a8/4b45e4e059270d17af60359713ffd83f97900d45a6afa73aaa0d737d48b6/safetensors-0.7.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1d060c70284127fa805085d8f10fbd0962792aed71879d00864acda69dbab981", size = 541856, upload-time = "2025-11-19T15:18:31.075Z" }, - { url = "https://files.pythonhosted.org/packages/06/87/d26d8407c44175d8ae164a95b5a62707fcc445f3c0c56108e37d98070a3d/safetensors-0.7.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cdab83a366799fa730f90a4ebb563e494f28e9e92c4819e556152ad55e43591b", size = 674060, upload-time = "2025-11-19T15:18:37.211Z" }, - { url = "https://files.pythonhosted.org/packages/11/f5/57644a2ff08dc6325816ba7217e5095f17269dada2554b658442c66aed51/safetensors-0.7.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:672132907fcad9f2aedcb705b2d7b3b93354a2aec1b2f706c4db852abe338f85", size = 771715, upload-time = "2025-11-19T15:18:38.689Z" }, - { url = "https://files.pythonhosted.org/packages/86/31/17883e13a814bd278ae6e266b13282a01049b0c81341da7fd0e3e71a80a3/safetensors-0.7.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:5d72abdb8a4d56d4020713724ba81dac065fedb7f3667151c4a637f1d3fb26c0", size = 714377, upload-time = "2025-11-19T15:18:40.162Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d8/0c8a7dc9b41dcac53c4cbf9df2b9c83e0e0097203de8b37a712b345c0be5/safetensors-0.7.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0f6d66c1c538d5a94a73aa9ddca8ccc4227e6c9ff555322ea40bdd142391dd4", size = 677368, upload-time = "2025-11-19T15:18:41.627Z" }, - { url = "https://files.pythonhosted.org/packages/05/e5/cb4b713c8a93469e3c5be7c3f8d77d307e65fe89673e731f5c2bfd0a9237/safetensors-0.7.0-cp38-abi3-win32.whl", hash = "sha256:c74af94bf3ac15ac4d0f2a7c7b4663a15f8c2ab15ed0fc7531ca61d0835eccba", size = 326423, upload-time = "2025-11-19T15:18:45.74Z" }, - { url = "https://files.pythonhosted.org/packages/5d/e6/ec8471c8072382cb91233ba7267fd931219753bb43814cbc71757bfd4dab/safetensors-0.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:d1239932053f56f3456f32eb9625590cc7582e905021f94636202a864d470755", size = 341380, upload-time = "2025-11-19T15:18:44.427Z" }, + { url = "https://files.pythonhosted.org/packages/fa/47/aef6c06649039accf914afef490268e1067ed82be62bcfa5b7e886ad15e8/safetensors-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c82f4d474cf725255d9e6acf17252991c3c8aac038d6ef363a4bf8be2f6db517", size = 467781 }, + { url = "https://files.pythonhosted.org/packages/e8/00/374c0c068e30cd31f1e1b46b4b5738168ec79e7689ca82ee93ddfea05109/safetensors-0.7.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:94fd4858284736bb67a897a41608b5b0c2496c9bdb3bf2af1fa3409127f20d57", size = 447058 }, + { url = "https://files.pythonhosted.org/packages/f1/06/578ffed52c2296f93d7fd2d844cabfa92be51a587c38c8afbb8ae449ca89/safetensors-0.7.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e07d91d0c92a31200f25351f4acb2bc6aff7f48094e13ebb1d0fb995b54b6542", size = 491748 }, + { url = "https://files.pythonhosted.org/packages/ae/33/1debbbb70e4791dde185edb9413d1fe01619255abb64b300157d7f15dddd/safetensors-0.7.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8469155f4cb518bafb4acf4865e8bb9d6804110d2d9bdcaa78564b9fd841e104", size = 503881 }, + { url = "https://files.pythonhosted.org/packages/8e/1c/40c2ca924d60792c3be509833df711b553c60effbd91da6f5284a83f7122/safetensors-0.7.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54bef08bf00a2bff599982f6b08e8770e09cc012d7bba00783fc7ea38f1fb37d", size = 623463 }, + { url = "https://files.pythonhosted.org/packages/9b/3a/13784a9364bd43b0d61eef4bea2845039bc2030458b16594a1bd787ae26e/safetensors-0.7.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42cb091236206bb2016d245c377ed383aa7f78691748f3bb6ee1bfa51ae2ce6a", size = 532855 }, + { url = "https://files.pythonhosted.org/packages/a0/60/429e9b1cb3fc651937727befe258ea24122d9663e4d5709a48c9cbfceecb/safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac7252938f0696ddea46f5e855dd3138444e82236e3be475f54929f0c510d48", size = 507152 }, + { url = "https://files.pythonhosted.org/packages/3c/a8/4b45e4e059270d17af60359713ffd83f97900d45a6afa73aaa0d737d48b6/safetensors-0.7.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1d060c70284127fa805085d8f10fbd0962792aed71879d00864acda69dbab981", size = 541856 }, + { url = "https://files.pythonhosted.org/packages/06/87/d26d8407c44175d8ae164a95b5a62707fcc445f3c0c56108e37d98070a3d/safetensors-0.7.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cdab83a366799fa730f90a4ebb563e494f28e9e92c4819e556152ad55e43591b", size = 674060 }, + { url = "https://files.pythonhosted.org/packages/11/f5/57644a2ff08dc6325816ba7217e5095f17269dada2554b658442c66aed51/safetensors-0.7.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:672132907fcad9f2aedcb705b2d7b3b93354a2aec1b2f706c4db852abe338f85", size = 771715 }, + { url = "https://files.pythonhosted.org/packages/86/31/17883e13a814bd278ae6e266b13282a01049b0c81341da7fd0e3e71a80a3/safetensors-0.7.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:5d72abdb8a4d56d4020713724ba81dac065fedb7f3667151c4a637f1d3fb26c0", size = 714377 }, + { url = "https://files.pythonhosted.org/packages/4a/d8/0c8a7dc9b41dcac53c4cbf9df2b9c83e0e0097203de8b37a712b345c0be5/safetensors-0.7.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0f6d66c1c538d5a94a73aa9ddca8ccc4227e6c9ff555322ea40bdd142391dd4", size = 677368 }, + { url = "https://files.pythonhosted.org/packages/05/e5/cb4b713c8a93469e3c5be7c3f8d77d307e65fe89673e731f5c2bfd0a9237/safetensors-0.7.0-cp38-abi3-win32.whl", hash = "sha256:c74af94bf3ac15ac4d0f2a7c7b4663a15f8c2ab15ed0fc7531ca61d0835eccba", size = 326423 }, + { url = "https://files.pythonhosted.org/packages/5d/e6/ec8471c8072382cb91233ba7267fd931219753bb43814cbc71757bfd4dab/safetensors-0.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:d1239932053f56f3456f32eb9625590cc7582e905021f94636202a864d470755", size = 341380 }, ] [package.optional-dependencies] @@ -7598,38 +7581,38 @@ dependencies = [ { name = "scipy" }, { name = "threadpoolctl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585 } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242, upload-time = "2025-12-10T07:07:51.568Z" }, - { url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075, upload-time = "2025-12-10T07:07:53.697Z" }, - { url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492, upload-time = "2025-12-10T07:07:55.574Z" }, - { url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904, upload-time = "2025-12-10T07:07:57.666Z" }, - { url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359, upload-time = "2025-12-10T07:07:59.838Z" }, - { url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898, upload-time = "2025-12-10T07:08:01.36Z" }, - { url = "https://files.pythonhosted.org/packages/03/aa/e22e0768512ce9255eba34775be2e85c2048da73da1193e841707f8f039c/scikit_learn-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d6ae97234d5d7079dc0040990a6f7aeb97cb7fa7e8945f1999a429b23569e0a", size = 8513770, upload-time = "2025-12-10T07:08:03.251Z" }, - { url = "https://files.pythonhosted.org/packages/58/37/31b83b2594105f61a381fc74ca19e8780ee923be2d496fcd8d2e1147bd99/scikit_learn-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:edec98c5e7c128328124a029bceb09eda2d526997780fef8d65e9a69eead963e", size = 8044458, upload-time = "2025-12-10T07:08:05.336Z" }, - { url = "https://files.pythonhosted.org/packages/2d/5a/3f1caed8765f33eabb723596666da4ebbf43d11e96550fb18bdec42b467b/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74b66d8689d52ed04c271e1329f0c61635bcaf5b926db9b12d58914cdc01fe57", size = 8610341, upload-time = "2025-12-10T07:08:07.732Z" }, - { url = "https://files.pythonhosted.org/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fdf95767f989b0cfedb85f7ed8ca215d4be728031f56ff5a519ee1e3276dc2e", size = 8900022, upload-time = "2025-12-10T07:08:09.862Z" }, - { url = "https://files.pythonhosted.org/packages/1c/f9/9b7563caf3ec8873e17a31401858efab6b39a882daf6c1bfa88879c0aa11/scikit_learn-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:2de443b9373b3b615aec1bb57f9baa6bb3a9bd093f1269ba95c17d870422b271", size = 7989409, upload-time = "2025-12-10T07:08:12.028Z" }, - { url = "https://files.pythonhosted.org/packages/49/bd/1f4001503650e72c4f6009ac0c4413cb17d2d601cef6f71c0453da2732fc/scikit_learn-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:eddde82a035681427cbedded4e6eff5e57fa59216c2e3e90b10b19ab1d0a65c3", size = 7619760, upload-time = "2025-12-10T07:08:13.688Z" }, - { url = "https://files.pythonhosted.org/packages/d2/7d/a630359fc9dcc95496588c8d8e3245cc8fd81980251079bc09c70d41d951/scikit_learn-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7cc267b6108f0a1499a734167282c00c4ebf61328566b55ef262d48e9849c735", size = 8826045, upload-time = "2025-12-10T07:08:15.215Z" }, - { url = "https://files.pythonhosted.org/packages/cc/56/a0c86f6930cfcd1c7054a2bc417e26960bb88d32444fe7f71d5c2cfae891/scikit_learn-1.8.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:fe1c011a640a9f0791146011dfd3c7d9669785f9fed2b2a5f9e207536cf5c2fd", size = 8420324, upload-time = "2025-12-10T07:08:17.561Z" }, - { url = "https://files.pythonhosted.org/packages/46/1e/05962ea1cebc1cf3876667ecb14c283ef755bf409993c5946ade3b77e303/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72358cce49465d140cc4e7792015bb1f0296a9742d5622c67e31399b75468b9e", size = 8680651, upload-time = "2025-12-10T07:08:19.952Z" }, - { url = "https://files.pythonhosted.org/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045, upload-time = "2025-12-10T07:08:22.11Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994, upload-time = "2025-12-10T07:08:23.943Z" }, - { url = "https://files.pythonhosted.org/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518, upload-time = "2025-12-10T07:08:25.71Z" }, - { url = "https://files.pythonhosted.org/packages/24/05/1af2c186174cc92dcab2233f327336058c077d38f6fe2aceb08e6ab4d509/scikit_learn-1.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c22a2da7a198c28dd1a6e1136f19c830beab7fdca5b3e5c8bba8394f8a5c45b3", size = 8528667, upload-time = "2025-12-10T07:08:27.541Z" }, - { url = "https://files.pythonhosted.org/packages/a8/25/01c0af38fe969473fb292bba9dc2b8f9b451f3112ff242c647fee3d0dfe7/scikit_learn-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:6b595b07a03069a2b1740dc08c2299993850ea81cce4fe19b2421e0c970de6b7", size = 8066524, upload-time = "2025-12-10T07:08:29.822Z" }, - { url = "https://files.pythonhosted.org/packages/be/ce/a0623350aa0b68647333940ee46fe45086c6060ec604874e38e9ab7d8e6c/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29ffc74089f3d5e87dfca4c2c8450f88bdc61b0fc6ed5d267f3988f19a1309f6", size = 8657133, upload-time = "2025-12-10T07:08:31.865Z" }, - { url = "https://files.pythonhosted.org/packages/b8/cb/861b41341d6f1245e6ca80b1c1a8c4dfce43255b03df034429089ca2a2c5/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb65db5d7531bccf3a4f6bec3462223bea71384e2cda41da0f10b7c292b9e7c4", size = 8923223, upload-time = "2025-12-10T07:08:34.166Z" }, - { url = "https://files.pythonhosted.org/packages/76/18/a8def8f91b18cd1ba6e05dbe02540168cb24d47e8dcf69e8d00b7da42a08/scikit_learn-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:56079a99c20d230e873ea40753102102734c5953366972a71d5cb39a32bc40c6", size = 8096518, upload-time = "2025-12-10T07:08:36.339Z" }, - { url = "https://files.pythonhosted.org/packages/d1/77/482076a678458307f0deb44e29891d6022617b2a64c840c725495bee343f/scikit_learn-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3bad7565bc9cf37ce19a7c0d107742b320c1285df7aab1a6e2d28780df167242", size = 7754546, upload-time = "2025-12-10T07:08:38.128Z" }, - { url = "https://files.pythonhosted.org/packages/2d/d1/ef294ca754826daa043b2a104e59960abfab4cf653891037d19dd5b6f3cf/scikit_learn-1.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4511be56637e46c25721e83d1a9cea9614e7badc7040c4d573d75fbe257d6fd7", size = 8848305, upload-time = "2025-12-10T07:08:41.013Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e2/b1f8b05138ee813b8e1a4149f2f0d289547e60851fd1bb268886915adbda/scikit_learn-1.8.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:a69525355a641bf8ef136a7fa447672fb54fe8d60cab5538d9eb7c6438543fb9", size = 8432257, upload-time = "2025-12-10T07:08:42.873Z" }, - { url = "https://files.pythonhosted.org/packages/26/11/c32b2138a85dcb0c99f6afd13a70a951bfdff8a6ab42d8160522542fb647/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2656924ec73e5939c76ac4c8b026fc203b83d8900362eb2599d8aee80e4880f", size = 8678673, upload-time = "2025-12-10T07:08:45.362Z" }, - { url = "https://files.pythonhosted.org/packages/c7/57/51f2384575bdec454f4fe4e7a919d696c9ebce914590abf3e52d47607ab8/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15fc3b5d19cc2be65404786857f2e13c70c83dd4782676dd6814e3b89dc8f5b9", size = 8922467, upload-time = "2025-12-10T07:08:47.408Z" }, - { url = "https://files.pythonhosted.org/packages/35/4d/748c9e2872637a57981a04adc038dacaa16ba8ca887b23e34953f0b3f742/scikit_learn-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:00d6f1d66fbcf4eba6e356e1420d33cc06c70a45bb1363cd6f6a8e4ebbbdece2", size = 8774395, upload-time = "2025-12-10T07:08:49.337Z" }, - { url = "https://files.pythonhosted.org/packages/60/22/d7b2ebe4704a5e50790ba089d5c2ae308ab6bb852719e6c3bd4f04c3a363/scikit_learn-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f28dd15c6bb0b66ba09728cf09fd8736c304be29409bd8445a080c1280619e8c", size = 8002647, upload-time = "2025-12-10T07:08:51.601Z" }, + { url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242 }, + { url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075 }, + { url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492 }, + { url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904 }, + { url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359 }, + { url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898 }, + { url = "https://files.pythonhosted.org/packages/03/aa/e22e0768512ce9255eba34775be2e85c2048da73da1193e841707f8f039c/scikit_learn-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d6ae97234d5d7079dc0040990a6f7aeb97cb7fa7e8945f1999a429b23569e0a", size = 8513770 }, + { url = "https://files.pythonhosted.org/packages/58/37/31b83b2594105f61a381fc74ca19e8780ee923be2d496fcd8d2e1147bd99/scikit_learn-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:edec98c5e7c128328124a029bceb09eda2d526997780fef8d65e9a69eead963e", size = 8044458 }, + { url = "https://files.pythonhosted.org/packages/2d/5a/3f1caed8765f33eabb723596666da4ebbf43d11e96550fb18bdec42b467b/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74b66d8689d52ed04c271e1329f0c61635bcaf5b926db9b12d58914cdc01fe57", size = 8610341 }, + { url = "https://files.pythonhosted.org/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fdf95767f989b0cfedb85f7ed8ca215d4be728031f56ff5a519ee1e3276dc2e", size = 8900022 }, + { url = "https://files.pythonhosted.org/packages/1c/f9/9b7563caf3ec8873e17a31401858efab6b39a882daf6c1bfa88879c0aa11/scikit_learn-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:2de443b9373b3b615aec1bb57f9baa6bb3a9bd093f1269ba95c17d870422b271", size = 7989409 }, + { url = "https://files.pythonhosted.org/packages/49/bd/1f4001503650e72c4f6009ac0c4413cb17d2d601cef6f71c0453da2732fc/scikit_learn-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:eddde82a035681427cbedded4e6eff5e57fa59216c2e3e90b10b19ab1d0a65c3", size = 7619760 }, + { url = "https://files.pythonhosted.org/packages/d2/7d/a630359fc9dcc95496588c8d8e3245cc8fd81980251079bc09c70d41d951/scikit_learn-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7cc267b6108f0a1499a734167282c00c4ebf61328566b55ef262d48e9849c735", size = 8826045 }, + { url = "https://files.pythonhosted.org/packages/cc/56/a0c86f6930cfcd1c7054a2bc417e26960bb88d32444fe7f71d5c2cfae891/scikit_learn-1.8.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:fe1c011a640a9f0791146011dfd3c7d9669785f9fed2b2a5f9e207536cf5c2fd", size = 8420324 }, + { url = "https://files.pythonhosted.org/packages/46/1e/05962ea1cebc1cf3876667ecb14c283ef755bf409993c5946ade3b77e303/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72358cce49465d140cc4e7792015bb1f0296a9742d5622c67e31399b75468b9e", size = 8680651 }, + { url = "https://files.pythonhosted.org/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045 }, + { url = "https://files.pythonhosted.org/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994 }, + { url = "https://files.pythonhosted.org/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518 }, + { url = "https://files.pythonhosted.org/packages/24/05/1af2c186174cc92dcab2233f327336058c077d38f6fe2aceb08e6ab4d509/scikit_learn-1.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c22a2da7a198c28dd1a6e1136f19c830beab7fdca5b3e5c8bba8394f8a5c45b3", size = 8528667 }, + { url = "https://files.pythonhosted.org/packages/a8/25/01c0af38fe969473fb292bba9dc2b8f9b451f3112ff242c647fee3d0dfe7/scikit_learn-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:6b595b07a03069a2b1740dc08c2299993850ea81cce4fe19b2421e0c970de6b7", size = 8066524 }, + { url = "https://files.pythonhosted.org/packages/be/ce/a0623350aa0b68647333940ee46fe45086c6060ec604874e38e9ab7d8e6c/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29ffc74089f3d5e87dfca4c2c8450f88bdc61b0fc6ed5d267f3988f19a1309f6", size = 8657133 }, + { url = "https://files.pythonhosted.org/packages/b8/cb/861b41341d6f1245e6ca80b1c1a8c4dfce43255b03df034429089ca2a2c5/scikit_learn-1.8.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb65db5d7531bccf3a4f6bec3462223bea71384e2cda41da0f10b7c292b9e7c4", size = 8923223 }, + { url = "https://files.pythonhosted.org/packages/76/18/a8def8f91b18cd1ba6e05dbe02540168cb24d47e8dcf69e8d00b7da42a08/scikit_learn-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:56079a99c20d230e873ea40753102102734c5953366972a71d5cb39a32bc40c6", size = 8096518 }, + { url = "https://files.pythonhosted.org/packages/d1/77/482076a678458307f0deb44e29891d6022617b2a64c840c725495bee343f/scikit_learn-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3bad7565bc9cf37ce19a7c0d107742b320c1285df7aab1a6e2d28780df167242", size = 7754546 }, + { url = "https://files.pythonhosted.org/packages/2d/d1/ef294ca754826daa043b2a104e59960abfab4cf653891037d19dd5b6f3cf/scikit_learn-1.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4511be56637e46c25721e83d1a9cea9614e7badc7040c4d573d75fbe257d6fd7", size = 8848305 }, + { url = "https://files.pythonhosted.org/packages/5b/e2/b1f8b05138ee813b8e1a4149f2f0d289547e60851fd1bb268886915adbda/scikit_learn-1.8.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:a69525355a641bf8ef136a7fa447672fb54fe8d60cab5538d9eb7c6438543fb9", size = 8432257 }, + { url = "https://files.pythonhosted.org/packages/26/11/c32b2138a85dcb0c99f6afd13a70a951bfdff8a6ab42d8160522542fb647/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2656924ec73e5939c76ac4c8b026fc203b83d8900362eb2599d8aee80e4880f", size = 8678673 }, + { url = "https://files.pythonhosted.org/packages/c7/57/51f2384575bdec454f4fe4e7a919d696c9ebce914590abf3e52d47607ab8/scikit_learn-1.8.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15fc3b5d19cc2be65404786857f2e13c70c83dd4782676dd6814e3b89dc8f5b9", size = 8922467 }, + { url = "https://files.pythonhosted.org/packages/35/4d/748c9e2872637a57981a04adc038dacaa16ba8ca887b23e34953f0b3f742/scikit_learn-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:00d6f1d66fbcf4eba6e356e1420d33cc06c70a45bb1363cd6f6a8e4ebbbdece2", size = 8774395 }, + { url = "https://files.pythonhosted.org/packages/60/22/d7b2ebe4704a5e50790ba089d5c2ae308ab6bb852719e6c3bd4f04c3a363/scikit_learn-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f28dd15c6bb0b66ba09728cf09fd8736c304be29409bd8445a080c1280619e8c", size = 8002647 }, ] [[package]] @@ -7639,58 +7622,58 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822 } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, - { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, - { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, - { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, - { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, - { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, - { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, - { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, - { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, - { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, - { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, - { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, - { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, - { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, - { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, - { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, - { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, - { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, - { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, - { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, - { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, - { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, - { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, - { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, - { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, - { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, - { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, - { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, - { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, - { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, - { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, - { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, - { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, - { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, - { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954 }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662 }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366 }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017 }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842 }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890 }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557 }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856 }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682 }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340 }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199 }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001 }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719 }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595 }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429 }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952 }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063 }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449 }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943 }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621 }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708 }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135 }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977 }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601 }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667 }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159 }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771 }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910 }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980 }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543 }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510 }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131 }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032 }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766 }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007 }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333 }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066 }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763 }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984 }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877 }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750 }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858 }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723 }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098 }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397 }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163 }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291 }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317 }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327 }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165 }, ] [[package]] @@ -7701,9 +7684,9 @@ dependencies = [ { name = "cryptography", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, { name = "jeepney", marker = "(python_full_version < '3.13' and sys_platform == 'emscripten') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554 }, ] [[package]] @@ -7714,9 +7697,9 @@ dependencies = [ { name = "csvw" }, { name = "regex" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/57/85cac3a8e32370e88fa5fa92812edb6025db7fcbed51452bd56ee1524957/segments-2.4.0.tar.gz", hash = "sha256:bba71f5520ddd54c8aa2f4d765a60618c6862162d6e7356a4a097f2223166f5b", size = 18662, upload-time = "2026-03-07T10:01:28.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/57/85cac3a8e32370e88fa5fa92812edb6025db7fcbed51452bd56ee1524957/segments-2.4.0.tar.gz", hash = "sha256:bba71f5520ddd54c8aa2f4d765a60618c6862162d6e7356a4a097f2223166f5b", size = 18662 } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/60/eef9acce946177f92c9aabf432224d87ab908bafafac516a36ab924199f3/segments-2.4.0-py2.py3-none-any.whl", hash = "sha256:4021dc67f201cc03c864c74c618bdb163b1af629da3040babbaa37d8813f3db0", size = 16321, upload-time = "2026-03-07T10:01:27.885Z" }, + { url = "https://files.pythonhosted.org/packages/be/60/eef9acce946177f92c9aabf432224d87ab908bafafac516a36ab924199f3/segments-2.4.0-py2.py3-none-any.whl", hash = "sha256:4021dc67f201cc03c864c74c618bdb163b1af629da3040babbaa37d8813f3db0", size = 16321 }, ] [[package]] @@ -7727,9 +7710,9 @@ dependencies = [ { name = "mpire", extra = ["dill"] }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/a0/ce7e3d6cc76498fd594e667d10a03f17d7cced129e46869daec23523bf5a/semchunk-3.2.5.tar.gz", hash = "sha256:ee15e9a06a69a411937dd8fcf0a25d7ef389c5195863140436872a02c95b0218", size = 17667, upload-time = "2025-10-28T02:12:38.025Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/a0/ce7e3d6cc76498fd594e667d10a03f17d7cced129e46869daec23523bf5a/semchunk-3.2.5.tar.gz", hash = "sha256:ee15e9a06a69a411937dd8fcf0a25d7ef389c5195863140436872a02c95b0218", size = 17667 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/95/12d226ee4d207cb1f77a216baa7e1a8bae2639733c140abe8d0316d23a18/semchunk-3.2.5-py3-none-any.whl", hash = "sha256:fd09cc5f380bd010b8ca773bd81893f7eaf11d37dd8362a83d46cedaf5dae076", size = 13048, upload-time = "2025-10-28T02:12:36.724Z" }, + { url = "https://files.pythonhosted.org/packages/f8/95/12d226ee4d207cb1f77a216baa7e1a8bae2639733c140abe8d0316d23a18/semchunk-3.2.5-py3-none-any.whl", hash = "sha256:fd09cc5f380bd010b8ca773bd81893f7eaf11d37dd8362a83d46cedaf5dae076", size = 13048 }, ] [[package]] @@ -7746,18 +7729,18 @@ dependencies = [ { name = "transformers" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/26/448453925b6ce0c29d8b54327caa71ee4835511aef02070467402273079c/sentence_transformers-5.3.0.tar.gz", hash = "sha256:414a0a881f53a4df0e6cbace75f823bfcb6b94d674c42a384b498959b7c065e2", size = 403330, upload-time = "2026-03-12T14:53:40.778Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/26/448453925b6ce0c29d8b54327caa71ee4835511aef02070467402273079c/sentence_transformers-5.3.0.tar.gz", hash = "sha256:414a0a881f53a4df0e6cbace75f823bfcb6b94d674c42a384b498959b7c065e2", size = 403330 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/9c/2fa7224058cad8df68d84bafee21716f30892cecc7ad1ad73bde61d23754/sentence_transformers-5.3.0-py3-none-any.whl", hash = "sha256:dca6b98db790274a68185d27a65801b58b4caf653a4e556b5f62827509347c7d", size = 512390, upload-time = "2026-03-12T14:53:39.035Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9c/2fa7224058cad8df68d84bafee21716f30892cecc7ad1ad73bde61d23754/sentence_transformers-5.3.0-py3-none-any.whl", hash = "sha256:dca6b98db790274a68185d27a65801b58b4caf653a4e556b5f62827509347c7d", size = 512390 }, ] [[package]] name = "setuptools" version = "81.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021 }, ] [[package]] @@ -7767,75 +7750,75 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489 } wheels = [ - { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550, upload-time = "2025-09-24T13:50:30.019Z" }, - { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556, upload-time = "2025-09-24T13:50:32.291Z" }, - { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308, upload-time = "2025-09-24T13:50:33.862Z" }, - { url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844, upload-time = "2025-09-24T13:50:35.459Z" }, - { url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842, upload-time = "2025-09-24T13:50:37.478Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714, upload-time = "2025-09-24T13:50:39.9Z" }, - { url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745, upload-time = "2025-09-24T13:50:41.414Z" }, - { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861, upload-time = "2025-09-24T13:50:43.35Z" }, - { url = "https://files.pythonhosted.org/packages/c3/90/98ef257c23c46425dc4d1d31005ad7c8d649fe423a38b917db02c30f1f5a/shapely-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b510dda1a3672d6879beb319bc7c5fd302c6c354584690973c838f46ec3e0fa8", size = 1832644, upload-time = "2025-09-24T13:50:44.886Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ab/0bee5a830d209adcd3a01f2d4b70e587cdd9fd7380d5198c064091005af8/shapely-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8cff473e81017594d20ec55d86b54bc635544897e13a7cfc12e36909c5309a2a", size = 1642887, upload-time = "2025-09-24T13:50:46.735Z" }, - { url = "https://files.pythonhosted.org/packages/2d/5e/7d7f54ba960c13302584c73704d8c4d15404a51024631adb60b126a4ae88/shapely-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe7b77dc63d707c09726b7908f575fc04ff1d1ad0f3fb92aec212396bc6cfe5e", size = 2970931, upload-time = "2025-09-24T13:50:48.374Z" }, - { url = "https://files.pythonhosted.org/packages/f2/a2/83fc37e2a58090e3d2ff79175a95493c664bcd0b653dd75cb9134645a4e5/shapely-2.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ed1a5bbfb386ee8332713bf7508bc24e32d24b74fc9a7b9f8529a55db9f4ee6", size = 3082855, upload-time = "2025-09-24T13:50:50.037Z" }, - { url = "https://files.pythonhosted.org/packages/44/2b/578faf235a5b09f16b5f02833c53822294d7f21b242f8e2d0cf03fb64321/shapely-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a84e0582858d841d54355246ddfcbd1fce3179f185da7470f41ce39d001ee1af", size = 3979960, upload-time = "2025-09-24T13:50:51.74Z" }, - { url = "https://files.pythonhosted.org/packages/4d/04/167f096386120f692cc4ca02f75a17b961858997a95e67a3cb6a7bbd6b53/shapely-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc3487447a43d42adcdf52d7ac73804f2312cbfa5d433a7d2c506dcab0033dfd", size = 4142851, upload-time = "2025-09-24T13:50:53.49Z" }, - { url = "https://files.pythonhosted.org/packages/48/74/fb402c5a6235d1c65a97348b48cdedb75fb19eca2b1d66d04969fc1c6091/shapely-2.1.2-cp313-cp313-win32.whl", hash = "sha256:9c3a3c648aedc9f99c09263b39f2d8252f199cb3ac154fadc173283d7d111350", size = 1541890, upload-time = "2025-09-24T13:50:55.337Z" }, - { url = "https://files.pythonhosted.org/packages/41/47/3647fe7ad990af60ad98b889657a976042c9988c2807cf322a9d6685f462/shapely-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:ca2591bff6645c216695bdf1614fca9c82ea1144d4a7591a466fef64f28f0715", size = 1722151, upload-time = "2025-09-24T13:50:57.153Z" }, - { url = "https://files.pythonhosted.org/packages/3c/49/63953754faa51ffe7d8189bfbe9ca34def29f8c0e34c67cbe2a2795f269d/shapely-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2d93d23bdd2ed9dc157b46bc2f19b7da143ca8714464249bef6771c679d5ff40", size = 1834130, upload-time = "2025-09-24T13:50:58.49Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ee/dce001c1984052970ff60eb4727164892fb2d08052c575042a47f5a9e88f/shapely-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01d0d304b25634d60bd7cf291828119ab55a3bab87dc4af1e44b07fb225f188b", size = 1642802, upload-time = "2025-09-24T13:50:59.871Z" }, - { url = "https://files.pythonhosted.org/packages/da/e7/fc4e9a19929522877fa602f705706b96e78376afb7fad09cad5b9af1553c/shapely-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8d8382dd120d64b03698b7298b89611a6ea6f55ada9d39942838b79c9bc89801", size = 3018460, upload-time = "2025-09-24T13:51:02.08Z" }, - { url = "https://files.pythonhosted.org/packages/a1/18/7519a25db21847b525696883ddc8e6a0ecaa36159ea88e0fef11466384d0/shapely-2.1.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19efa3611eef966e776183e338b2d7ea43569ae99ab34f8d17c2c054d3205cc0", size = 3095223, upload-time = "2025-09-24T13:51:04.472Z" }, - { url = "https://files.pythonhosted.org/packages/48/de/b59a620b1f3a129c3fecc2737104a0a7e04e79335bd3b0a1f1609744cf17/shapely-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:346ec0c1a0fcd32f57f00e4134d1200e14bf3f5ae12af87ba83ca275c502498c", size = 4030760, upload-time = "2025-09-24T13:51:06.455Z" }, - { url = "https://files.pythonhosted.org/packages/96/b3/c6655ee7232b417562bae192ae0d3ceaadb1cc0ffc2088a2ddf415456cc2/shapely-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6305993a35989391bd3476ee538a5c9a845861462327efe00dd11a5c8c709a99", size = 4170078, upload-time = "2025-09-24T13:51:08.584Z" }, - { url = "https://files.pythonhosted.org/packages/a0/8e/605c76808d73503c9333af8f6cbe7e1354d2d238bda5f88eea36bfe0f42a/shapely-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:c8876673449f3401f278c86eb33224c5764582f72b653a415d0e6672fde887bf", size = 1559178, upload-time = "2025-09-24T13:51:10.73Z" }, - { url = "https://files.pythonhosted.org/packages/36/f7/d317eb232352a1f1444d11002d477e54514a4a6045536d49d0c59783c0da/shapely-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:4a44bc62a10d84c11a7a3d7c1c4fe857f7477c3506e24c9062da0db0ae0c449c", size = 1739756, upload-time = "2025-09-24T13:51:12.105Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c4/3ce4c2d9b6aabd27d26ec988f08cb877ba9e6e96086eff81bfea93e688c7/shapely-2.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9a522f460d28e2bf4e12396240a5fc1518788b2fcd73535166d748399ef0c223", size = 1831290, upload-time = "2025-09-24T13:51:13.56Z" }, - { url = "https://files.pythonhosted.org/packages/17/b9/f6ab8918fc15429f79cb04afa9f9913546212d7fb5e5196132a2af46676b/shapely-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ff629e00818033b8d71139565527ced7d776c269a49bd78c9df84e8f852190c", size = 1641463, upload-time = "2025-09-24T13:51:14.972Z" }, - { url = "https://files.pythonhosted.org/packages/a5/57/91d59ae525ca641e7ac5551c04c9503aee6f29b92b392f31790fcb1a4358/shapely-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f67b34271dedc3c653eba4e3d7111aa421d5be9b4c4c7d38d30907f796cb30df", size = 2970145, upload-time = "2025-09-24T13:51:16.961Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cb/4948be52ee1da6927831ab59e10d4c29baa2a714f599f1f0d1bc747f5777/shapely-2.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21952dc00df38a2c28375659b07a3979d22641aeb104751e769c3ee825aadecf", size = 3073806, upload-time = "2025-09-24T13:51:18.712Z" }, - { url = "https://files.pythonhosted.org/packages/03/83/f768a54af775eb41ef2e7bec8a0a0dbe7d2431c3e78c0a8bdba7ab17e446/shapely-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1f2f33f486777456586948e333a56ae21f35ae273be99255a191f5c1fa302eb4", size = 3980803, upload-time = "2025-09-24T13:51:20.37Z" }, - { url = "https://files.pythonhosted.org/packages/9f/cb/559c7c195807c91c79d38a1f6901384a2878a76fbdf3f1048893a9b7534d/shapely-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cf831a13e0d5a7eb519e96f58ec26e049b1fad411fc6fc23b162a7ce04d9cffc", size = 4133301, upload-time = "2025-09-24T13:51:21.887Z" }, - { url = "https://files.pythonhosted.org/packages/80/cd/60d5ae203241c53ef3abd2ef27c6800e21afd6c94e39db5315ea0cbafb4a/shapely-2.1.2-cp314-cp314-win32.whl", hash = "sha256:61edcd8d0d17dd99075d320a1dd39c0cb9616f7572f10ef91b4b5b00c4aeb566", size = 1583247, upload-time = "2025-09-24T13:51:23.401Z" }, - { url = "https://files.pythonhosted.org/packages/74/d4/135684f342e909330e50d31d441ace06bf83c7dc0777e11043f99167b123/shapely-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:a444e7afccdb0999e203b976adb37ea633725333e5b119ad40b1ca291ecf311c", size = 1773019, upload-time = "2025-09-24T13:51:24.873Z" }, - { url = "https://files.pythonhosted.org/packages/a3/05/a44f3f9f695fa3ada22786dc9da33c933da1cbc4bfe876fe3a100bafe263/shapely-2.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5ebe3f84c6112ad3d4632b1fd2290665aa75d4cef5f6c5d77c4c95b324527c6a", size = 1834137, upload-time = "2025-09-24T13:51:26.665Z" }, - { url = "https://files.pythonhosted.org/packages/52/7e/4d57db45bf314573427b0a70dfca15d912d108e6023f623947fa69f39b72/shapely-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5860eb9f00a1d49ebb14e881f5caf6c2cf472c7fd38bd7f253bbd34f934eb076", size = 1642884, upload-time = "2025-09-24T13:51:28.029Z" }, - { url = "https://files.pythonhosted.org/packages/5a/27/4e29c0a55d6d14ad7422bf86995d7ff3f54af0eba59617eb95caf84b9680/shapely-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b705c99c76695702656327b819c9660768ec33f5ce01fa32b2af62b56ba400a1", size = 3018320, upload-time = "2025-09-24T13:51:29.903Z" }, - { url = "https://files.pythonhosted.org/packages/9f/bb/992e6a3c463f4d29d4cd6ab8963b75b1b1040199edbd72beada4af46bde5/shapely-2.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a1fd0ea855b2cf7c9cddaf25543e914dd75af9de08785f20ca3085f2c9ca60b0", size = 3094931, upload-time = "2025-09-24T13:51:32.699Z" }, - { url = "https://files.pythonhosted.org/packages/9c/16/82e65e21070e473f0ed6451224ed9fa0be85033d17e0c6e7213a12f59d12/shapely-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:df90e2db118c3671a0754f38e36802db75fe0920d211a27481daf50a711fdf26", size = 4030406, upload-time = "2025-09-24T13:51:34.189Z" }, - { url = "https://files.pythonhosted.org/packages/7c/75/c24ed871c576d7e2b64b04b1fe3d075157f6eb54e59670d3f5ffb36e25c7/shapely-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:361b6d45030b4ac64ddd0a26046906c8202eb60d0f9f53085f5179f1d23021a0", size = 4169511, upload-time = "2025-09-24T13:51:36.297Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f7/b3d1d6d18ebf55236eec1c681ce5e665742aab3c0b7b232720a7d43df7b6/shapely-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735", size = 1602607, upload-time = "2025-09-24T13:51:37.757Z" }, - { url = "https://files.pythonhosted.org/packages/9a/f6/f09272a71976dfc138129b8faf435d064a811ae2f708cb147dccdf7aacdb/shapely-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9", size = 1796682, upload-time = "2025-09-24T13:51:39.233Z" }, + { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550 }, + { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556 }, + { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308 }, + { url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844 }, + { url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842 }, + { url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714 }, + { url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745 }, + { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861 }, + { url = "https://files.pythonhosted.org/packages/c3/90/98ef257c23c46425dc4d1d31005ad7c8d649fe423a38b917db02c30f1f5a/shapely-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b510dda1a3672d6879beb319bc7c5fd302c6c354584690973c838f46ec3e0fa8", size = 1832644 }, + { url = "https://files.pythonhosted.org/packages/6d/ab/0bee5a830d209adcd3a01f2d4b70e587cdd9fd7380d5198c064091005af8/shapely-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8cff473e81017594d20ec55d86b54bc635544897e13a7cfc12e36909c5309a2a", size = 1642887 }, + { url = "https://files.pythonhosted.org/packages/2d/5e/7d7f54ba960c13302584c73704d8c4d15404a51024631adb60b126a4ae88/shapely-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe7b77dc63d707c09726b7908f575fc04ff1d1ad0f3fb92aec212396bc6cfe5e", size = 2970931 }, + { url = "https://files.pythonhosted.org/packages/f2/a2/83fc37e2a58090e3d2ff79175a95493c664bcd0b653dd75cb9134645a4e5/shapely-2.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ed1a5bbfb386ee8332713bf7508bc24e32d24b74fc9a7b9f8529a55db9f4ee6", size = 3082855 }, + { url = "https://files.pythonhosted.org/packages/44/2b/578faf235a5b09f16b5f02833c53822294d7f21b242f8e2d0cf03fb64321/shapely-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a84e0582858d841d54355246ddfcbd1fce3179f185da7470f41ce39d001ee1af", size = 3979960 }, + { url = "https://files.pythonhosted.org/packages/4d/04/167f096386120f692cc4ca02f75a17b961858997a95e67a3cb6a7bbd6b53/shapely-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc3487447a43d42adcdf52d7ac73804f2312cbfa5d433a7d2c506dcab0033dfd", size = 4142851 }, + { url = "https://files.pythonhosted.org/packages/48/74/fb402c5a6235d1c65a97348b48cdedb75fb19eca2b1d66d04969fc1c6091/shapely-2.1.2-cp313-cp313-win32.whl", hash = "sha256:9c3a3c648aedc9f99c09263b39f2d8252f199cb3ac154fadc173283d7d111350", size = 1541890 }, + { url = "https://files.pythonhosted.org/packages/41/47/3647fe7ad990af60ad98b889657a976042c9988c2807cf322a9d6685f462/shapely-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:ca2591bff6645c216695bdf1614fca9c82ea1144d4a7591a466fef64f28f0715", size = 1722151 }, + { url = "https://files.pythonhosted.org/packages/3c/49/63953754faa51ffe7d8189bfbe9ca34def29f8c0e34c67cbe2a2795f269d/shapely-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2d93d23bdd2ed9dc157b46bc2f19b7da143ca8714464249bef6771c679d5ff40", size = 1834130 }, + { url = "https://files.pythonhosted.org/packages/7f/ee/dce001c1984052970ff60eb4727164892fb2d08052c575042a47f5a9e88f/shapely-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01d0d304b25634d60bd7cf291828119ab55a3bab87dc4af1e44b07fb225f188b", size = 1642802 }, + { url = "https://files.pythonhosted.org/packages/da/e7/fc4e9a19929522877fa602f705706b96e78376afb7fad09cad5b9af1553c/shapely-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8d8382dd120d64b03698b7298b89611a6ea6f55ada9d39942838b79c9bc89801", size = 3018460 }, + { url = "https://files.pythonhosted.org/packages/a1/18/7519a25db21847b525696883ddc8e6a0ecaa36159ea88e0fef11466384d0/shapely-2.1.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19efa3611eef966e776183e338b2d7ea43569ae99ab34f8d17c2c054d3205cc0", size = 3095223 }, + { url = "https://files.pythonhosted.org/packages/48/de/b59a620b1f3a129c3fecc2737104a0a7e04e79335bd3b0a1f1609744cf17/shapely-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:346ec0c1a0fcd32f57f00e4134d1200e14bf3f5ae12af87ba83ca275c502498c", size = 4030760 }, + { url = "https://files.pythonhosted.org/packages/96/b3/c6655ee7232b417562bae192ae0d3ceaadb1cc0ffc2088a2ddf415456cc2/shapely-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6305993a35989391bd3476ee538a5c9a845861462327efe00dd11a5c8c709a99", size = 4170078 }, + { url = "https://files.pythonhosted.org/packages/a0/8e/605c76808d73503c9333af8f6cbe7e1354d2d238bda5f88eea36bfe0f42a/shapely-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:c8876673449f3401f278c86eb33224c5764582f72b653a415d0e6672fde887bf", size = 1559178 }, + { url = "https://files.pythonhosted.org/packages/36/f7/d317eb232352a1f1444d11002d477e54514a4a6045536d49d0c59783c0da/shapely-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:4a44bc62a10d84c11a7a3d7c1c4fe857f7477c3506e24c9062da0db0ae0c449c", size = 1739756 }, + { url = "https://files.pythonhosted.org/packages/fc/c4/3ce4c2d9b6aabd27d26ec988f08cb877ba9e6e96086eff81bfea93e688c7/shapely-2.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9a522f460d28e2bf4e12396240a5fc1518788b2fcd73535166d748399ef0c223", size = 1831290 }, + { url = "https://files.pythonhosted.org/packages/17/b9/f6ab8918fc15429f79cb04afa9f9913546212d7fb5e5196132a2af46676b/shapely-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ff629e00818033b8d71139565527ced7d776c269a49bd78c9df84e8f852190c", size = 1641463 }, + { url = "https://files.pythonhosted.org/packages/a5/57/91d59ae525ca641e7ac5551c04c9503aee6f29b92b392f31790fcb1a4358/shapely-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f67b34271dedc3c653eba4e3d7111aa421d5be9b4c4c7d38d30907f796cb30df", size = 2970145 }, + { url = "https://files.pythonhosted.org/packages/8a/cb/4948be52ee1da6927831ab59e10d4c29baa2a714f599f1f0d1bc747f5777/shapely-2.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21952dc00df38a2c28375659b07a3979d22641aeb104751e769c3ee825aadecf", size = 3073806 }, + { url = "https://files.pythonhosted.org/packages/03/83/f768a54af775eb41ef2e7bec8a0a0dbe7d2431c3e78c0a8bdba7ab17e446/shapely-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1f2f33f486777456586948e333a56ae21f35ae273be99255a191f5c1fa302eb4", size = 3980803 }, + { url = "https://files.pythonhosted.org/packages/9f/cb/559c7c195807c91c79d38a1f6901384a2878a76fbdf3f1048893a9b7534d/shapely-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cf831a13e0d5a7eb519e96f58ec26e049b1fad411fc6fc23b162a7ce04d9cffc", size = 4133301 }, + { url = "https://files.pythonhosted.org/packages/80/cd/60d5ae203241c53ef3abd2ef27c6800e21afd6c94e39db5315ea0cbafb4a/shapely-2.1.2-cp314-cp314-win32.whl", hash = "sha256:61edcd8d0d17dd99075d320a1dd39c0cb9616f7572f10ef91b4b5b00c4aeb566", size = 1583247 }, + { url = "https://files.pythonhosted.org/packages/74/d4/135684f342e909330e50d31d441ace06bf83c7dc0777e11043f99167b123/shapely-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:a444e7afccdb0999e203b976adb37ea633725333e5b119ad40b1ca291ecf311c", size = 1773019 }, + { url = "https://files.pythonhosted.org/packages/a3/05/a44f3f9f695fa3ada22786dc9da33c933da1cbc4bfe876fe3a100bafe263/shapely-2.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5ebe3f84c6112ad3d4632b1fd2290665aa75d4cef5f6c5d77c4c95b324527c6a", size = 1834137 }, + { url = "https://files.pythonhosted.org/packages/52/7e/4d57db45bf314573427b0a70dfca15d912d108e6023f623947fa69f39b72/shapely-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5860eb9f00a1d49ebb14e881f5caf6c2cf472c7fd38bd7f253bbd34f934eb076", size = 1642884 }, + { url = "https://files.pythonhosted.org/packages/5a/27/4e29c0a55d6d14ad7422bf86995d7ff3f54af0eba59617eb95caf84b9680/shapely-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b705c99c76695702656327b819c9660768ec33f5ce01fa32b2af62b56ba400a1", size = 3018320 }, + { url = "https://files.pythonhosted.org/packages/9f/bb/992e6a3c463f4d29d4cd6ab8963b75b1b1040199edbd72beada4af46bde5/shapely-2.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a1fd0ea855b2cf7c9cddaf25543e914dd75af9de08785f20ca3085f2c9ca60b0", size = 3094931 }, + { url = "https://files.pythonhosted.org/packages/9c/16/82e65e21070e473f0ed6451224ed9fa0be85033d17e0c6e7213a12f59d12/shapely-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:df90e2db118c3671a0754f38e36802db75fe0920d211a27481daf50a711fdf26", size = 4030406 }, + { url = "https://files.pythonhosted.org/packages/7c/75/c24ed871c576d7e2b64b04b1fe3d075157f6eb54e59670d3f5ffb36e25c7/shapely-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:361b6d45030b4ac64ddd0a26046906c8202eb60d0f9f53085f5179f1d23021a0", size = 4169511 }, + { url = "https://files.pythonhosted.org/packages/b1/f7/b3d1d6d18ebf55236eec1c681ce5e665742aab3c0b7b232720a7d43df7b6/shapely-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735", size = 1602607 }, + { url = "https://files.pythonhosted.org/packages/9a/f6/f09272a71976dfc138129b8faf435d064a811ae2f708cb147dccdf7aacdb/shapely-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9", size = 1796682 }, ] [[package]] name = "shellingham" version = "1.5.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, ] [[package]] name = "six" version = "1.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, ] [[package]] name = "slack-sdk" version = "3.41.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/35/fc009118a13187dd9731657c60138e5a7c2dea88681a7f04dc406af5da7d/slack_sdk-3.41.0.tar.gz", hash = "sha256:eb61eb12a65bebeca9cb5d36b3f799e836ed2be21b456d15df2627cfe34076ca", size = 250568, upload-time = "2026-03-12T16:10:11.381Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/35/fc009118a13187dd9731657c60138e5a7c2dea88681a7f04dc406af5da7d/slack_sdk-3.41.0.tar.gz", hash = "sha256:eb61eb12a65bebeca9cb5d36b3f799e836ed2be21b456d15df2627cfe34076ca", size = 250568 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/df/2e4be347ff98281b505cc0ccf141408cdd25eb5ca9f3830deb361b2472d3/slack_sdk-3.41.0-py2.py3-none-any.whl", hash = "sha256:bb18dcdfff1413ec448e759cf807ec3324090993d8ab9111c74081623b692a89", size = 313885, upload-time = "2026-03-12T16:10:09.811Z" }, + { url = "https://files.pythonhosted.org/packages/a1/df/2e4be347ff98281b505cc0ccf141408cdd25eb5ca9f3830deb361b2472d3/slack_sdk-3.41.0-py2.py3-none-any.whl", hash = "sha256:bb18dcdfff1413ec448e759cf807ec3324090993d8ab9111c74081623b692a89", size = 313885 }, ] [[package]] @@ -7845,9 +7828,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "limits" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a0/99/adfc7f94ca024736f061257d39118e1542bade7a52e86415a4c4ae92d8ff/slowapi-0.1.9.tar.gz", hash = "sha256:639192d0f1ca01b1c6d95bf6c71d794c3a9ee189855337b4821f7f457dddad77", size = 14028, upload-time = "2024-02-05T12:11:52.13Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/99/adfc7f94ca024736f061257d39118e1542bade7a52e86415a4c4ae92d8ff/slowapi-0.1.9.tar.gz", hash = "sha256:639192d0f1ca01b1c6d95bf6c71d794c3a9ee189855337b4821f7f457dddad77", size = 14028 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/bb/f71c4b7d7e7eb3fc1e8c0458a8979b912f40b58002b9fbf37729b8cb464b/slowapi-0.1.9-py3-none-any.whl", hash = "sha256:cfad116cfb84ad9d763ee155c1e5c5cbf00b0d47399a769b227865f5df576e36", size = 14670, upload-time = "2024-02-05T12:11:50.898Z" }, + { url = "https://files.pythonhosted.org/packages/2b/bb/f71c4b7d7e7eb3fc1e8c0458a8979b912f40b58002b9fbf37729b8cb464b/slowapi-0.1.9-py3-none-any.whl", hash = "sha256:cfad116cfb84ad9d763ee155c1e5c5cbf00b0d47399a769b227865f5df576e36", size = 14670 }, ] [[package]] @@ -7857,18 +7840,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e8/be/a66598b305763861a9ab15ff0f2fbc44e47b1ce7a776797337a4eef37c66/smart_open-7.5.1.tar.gz", hash = "sha256:3f08e16827c4733699e6b2cc40328a3568f900cb12ad9a3ad233ba6c872d9fe7", size = 54034, upload-time = "2026-02-23T11:01:28.979Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/be/a66598b305763861a9ab15ff0f2fbc44e47b1ce7a776797337a4eef37c66/smart_open-7.5.1.tar.gz", hash = "sha256:3f08e16827c4733699e6b2cc40328a3568f900cb12ad9a3ad233ba6c872d9fe7", size = 54034 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/ea/dcdecd68acebb49d3fd560473a43499b1635076f7f1ae8641c060fe7ce74/smart_open-7.5.1-py3-none-any.whl", hash = "sha256:3e07cbbd9c8a908bcb8e25d48becf1a5cbb4886fa975e9f34c672ed171df2318", size = 64108, upload-time = "2026-02-23T11:01:27.429Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ea/dcdecd68acebb49d3fd560473a43499b1635076f7f1ae8641c060fe7ce74/smart_open-7.5.1-py3-none-any.whl", hash = "sha256:3e07cbbd9c8a908bcb8e25d48becf1a5cbb4886fa975e9f34c672ed171df2318", size = 64108 }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] [[package]] @@ -7879,24 +7862,24 @@ dependencies = [ { name = "cffi" }, { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/41/9b873a8c055582859b239be17902a85339bec6a30ad162f98c9b0288a2cc/soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b", size = 46156, upload-time = "2025-01-25T09:17:04.831Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/41/9b873a8c055582859b239be17902a85339bec6a30ad162f98c9b0288a2cc/soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b", size = 46156 } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/28/e2a36573ccbcf3d57c00626a21fe51989380636e821b341d36ccca0c1c3a/soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445", size = 25751, upload-time = "2025-01-25T09:16:44.235Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ab/73e97a5b3cc46bba7ff8650a1504348fa1863a6f9d57d7001c6b67c5f20e/soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33", size = 1142250, upload-time = "2025-01-25T09:16:47.583Z" }, - { url = "https://files.pythonhosted.org/packages/a0/e5/58fd1a8d7b26fc113af244f966ee3aecf03cb9293cb935daaddc1e455e18/soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593", size = 1101406, upload-time = "2025-01-25T09:16:49.662Z" }, - { url = "https://files.pythonhosted.org/packages/58/ae/c0e4a53d77cf6e9a04179535766b3321b0b9ced5f70522e4caf9329f0046/soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb", size = 1235729, upload-time = "2025-01-25T09:16:53.018Z" }, - { url = "https://files.pythonhosted.org/packages/57/5e/70bdd9579b35003a489fc850b5047beeda26328053ebadc1fb60f320f7db/soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618", size = 1313646, upload-time = "2025-01-25T09:16:54.872Z" }, - { url = "https://files.pythonhosted.org/packages/fe/df/8c11dc4dfceda14e3003bb81a0d0edcaaf0796dd7b4f826ea3e532146bba/soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5", size = 899881, upload-time = "2025-01-25T09:16:56.663Z" }, - { url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162, upload-time = "2025-01-25T09:16:59.573Z" }, + { url = "https://files.pythonhosted.org/packages/64/28/e2a36573ccbcf3d57c00626a21fe51989380636e821b341d36ccca0c1c3a/soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445", size = 25751 }, + { url = "https://files.pythonhosted.org/packages/ea/ab/73e97a5b3cc46bba7ff8650a1504348fa1863a6f9d57d7001c6b67c5f20e/soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33", size = 1142250 }, + { url = "https://files.pythonhosted.org/packages/a0/e5/58fd1a8d7b26fc113af244f966ee3aecf03cb9293cb935daaddc1e455e18/soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593", size = 1101406 }, + { url = "https://files.pythonhosted.org/packages/58/ae/c0e4a53d77cf6e9a04179535766b3321b0b9ced5f70522e4caf9329f0046/soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb", size = 1235729 }, + { url = "https://files.pythonhosted.org/packages/57/5e/70bdd9579b35003a489fc850b5047beeda26328053ebadc1fb60f320f7db/soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618", size = 1313646 }, + { url = "https://files.pythonhosted.org/packages/fe/df/8c11dc4dfceda14e3003bb81a0d0edcaaf0796dd7b4f826ea3e532146bba/soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5", size = 899881 }, + { url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162 }, ] [[package]] name = "soupsieve" version = "2.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627 } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016 }, ] [[package]] @@ -7924,32 +7907,32 @@ dependencies = [ { name = "wasabi" }, { name = "weasel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/d7/1924f32272f50d2f13275f16d6f869fcec47b2b676b64f91fa206bd30ef4/spacy-3.8.13.tar.gz", hash = "sha256:eb7c03c2bb16593c34d4c91974118f0931c6e6969dbfe895b1a026c6714176cf", size = 1328016, upload-time = "2026-03-23T17:44:32.042Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/d7/1924f32272f50d2f13275f16d6f869fcec47b2b676b64f91fa206bd30ef4/spacy-3.8.13.tar.gz", hash = "sha256:eb7c03c2bb16593c34d4c91974118f0931c6e6969dbfe895b1a026c6714176cf", size = 1328016 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/e1/e8f6dc7fc00582b8dcbf40fac5bce6c0ff366cf6db212decdacfaf13e584/spacy-3.8.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dd8ab0deefe9d2c7dccce1be1e65660a32cfc3cc114d0aa222ff52ae11be58ba", size = 6218311, upload-time = "2026-03-23T17:42:51.938Z" }, - { url = "https://files.pythonhosted.org/packages/31/fe/517de9e25a6ec469738c9e886c15cd96649b338c6ae475b9b3e4c4f5e03d/spacy-3.8.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eab059bc668336d0034f3f69aed6b661b626c174e97cc1b52296d0d923c24405", size = 6033843, upload-time = "2026-03-23T17:42:54.133Z" }, - { url = "https://files.pythonhosted.org/packages/2c/a8/9a33c69f772f5c32a57c9ef7e692293b558e7bad202264d5586ab087fd29/spacy-3.8.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96a8e141fd7ff749fa6693c9c72187f802d334bcadf48b6b9d6ca9bff315ca73", size = 32725183, upload-time = "2026-03-23T17:42:58.847Z" }, - { url = "https://files.pythonhosted.org/packages/ba/cf/dcc0ea61270cb23a46d3efc437fe760e7a9c3cabcfaa770591329d1430f2/spacy-3.8.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:32c16f5be8c8006197554da3647d83311fb89bb31bb417d38f0d1da585877b0d", size = 33205816, upload-time = "2026-03-23T17:43:03.717Z" }, - { url = "https://files.pythonhosted.org/packages/90/6b/ce94ba2a166add3376aa2c976cb1a34d6387e9dc61103cfbe9192675d0b6/spacy-3.8.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7402154eae9d2c34d097f3f7f2bf3ed4ce6ef9d44e3035af15ccc7f357d122cb", size = 32090348, upload-time = "2026-03-23T17:43:08.809Z" }, - { url = "https://files.pythonhosted.org/packages/4d/20/df5076a162bda36b04cdfa9cd544ac2be4b47aed957b3922e4c8f75dc25a/spacy-3.8.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:879f467578569db73b5811aa0f0b9a3b3fb81a125c2f7a433dd5626bcaca53f6", size = 32991911, upload-time = "2026-03-23T17:43:14.543Z" }, - { url = "https://files.pythonhosted.org/packages/41/17/316dfdf5f8d1dd33a205e4f5e7190b67db73802180f7d08c8b306eb44375/spacy-3.8.13-cp312-cp312-win_amd64.whl", hash = "sha256:a0c849b5c9cee5a930a5e8a63d0b07e095d4e3d371af6a70c496efa1d0851257", size = 14226861, upload-time = "2026-03-23T17:43:18.122Z" }, - { url = "https://files.pythonhosted.org/packages/be/a8/a794d5bbf7bc2df6770df2b14cd6811420d0e10fe81830920bf0e891c19a/spacy-3.8.13-cp312-cp312-win_arm64.whl", hash = "sha256:5d7f660f64c7d09778600b27b881a367075fe792cb5cc6932737aeda71a9aa09", size = 13628855, upload-time = "2026-03-23T17:43:22.162Z" }, - { url = "https://files.pythonhosted.org/packages/a7/2d/dc15440fa58c12bab25cbd368bbf3b754eb11d1d3cc37f3aee47caa63695/spacy-3.8.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6db8903cf3fd8b40db9dab669c1e823a8884100f223dc8ec63c3744ff505d5fd", size = 6202080, upload-time = "2026-03-23T17:43:25.044Z" }, - { url = "https://files.pythonhosted.org/packages/1b/27/6d723fa0c3c9872d7b8c3fb1c76edef65c1db2de441ee87d119128cb82f7/spacy-3.8.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c26258bfed2f3bf5563c5994ee752ea8662bbac70924d28fbea4d1c10df48fd0", size = 6015435, upload-time = "2026-03-23T17:43:28.281Z" }, - { url = "https://files.pythonhosted.org/packages/c0/36/d13f36204290e7753ce849106fb732a1a1ff6c1af0a200f2c2f493a56e60/spacy-3.8.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a5f02eab8e6e8e9e790b1e8da05b70b3b1d4b1fb10dc4f44203da96939fdb977", size = 32510675, upload-time = "2026-03-23T17:43:32.657Z" }, - { url = "https://files.pythonhosted.org/packages/19/37/f2a0c986bc2d59b18547d5ffaabf57fd9d88e129ad7df5ebc9ccdc91e643/spacy-3.8.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:17b14f28d83fe85390f420f89760ae36d515f3e0a236f8266c46335549806e3a", size = 32841103, upload-time = "2026-03-23T17:43:38.139Z" }, - { url = "https://files.pythonhosted.org/packages/37/31/b4a86cc013e4ac3c3c290ecade748c4623709a78db8a368ee0e152931c0b/spacy-3.8.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:10fffb387e6afeba582e01efd9b5e0c5431ac323af134f4a71a749255182f4c8", size = 31763223, upload-time = "2026-03-23T17:43:42.823Z" }, - { url = "https://files.pythonhosted.org/packages/4f/fa/b3a406bdc2636aaa315b8539b88f262e78ca391afc4083e3e9806953b24a/spacy-3.8.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:500f6eb9e4711e75fc07d517962dbfb553ba144ccf1b1e8ffc71b014c9ad91b2", size = 32717863, upload-time = "2026-03-23T17:43:47.939Z" }, - { url = "https://files.pythonhosted.org/packages/d9/97/11ed2a980e7225b0a1a4041cb1cba44f40bd83682a08e450dd6171f054e4/spacy-3.8.13-cp313-cp313-win_amd64.whl", hash = "sha256:1e679a8c5dd86c564a1185d894b1b8e50b52fa81bee518120fc2f86349d1879c", size = 14220413, upload-time = "2026-03-23T17:43:51.922Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e2/06633e779486f62b3ec7ddeeb4accc10fb9a356b59f5bed3771dfb89931b/spacy-3.8.13-cp313-cp313-win_arm64.whl", hash = "sha256:c086bff909d73be892b4eb6883070dd3e328592b8933f0059a6569c8c7904928", size = 13619060, upload-time = "2026-03-23T17:43:55.449Z" }, - { url = "https://files.pythonhosted.org/packages/f2/02/bf2943f61a8bd21cca90e4fe19c4da8752c386f02a76e326b33199e09953/spacy-3.8.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:edcdd4f99e2711fc90c6ba3e8e07f7dc9753d111377ba7b7b5eb0d7259b0d211", size = 6209920, upload-time = "2026-03-23T17:43:58.441Z" }, - { url = "https://files.pythonhosted.org/packages/e7/87/fa5e4eb2ccb660d5042eb9144134dc6238c132ec812131bb0aca296bcdd9/spacy-3.8.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b8fd0188f78e032ac58f70a00748d591e244f3d4718e0ead2d9418b4148c744d", size = 6040696, upload-time = "2026-03-23T17:44:00.704Z" }, - { url = "https://files.pythonhosted.org/packages/ed/f3/9132878e0c18d627adffc1d23129a3706385d4f0fbe2ea5839523519452c/spacy-3.8.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0dc2d058cb919102bf0634b1b41aff64b867d9bab36bd35979ab658b5b2e4c27", size = 32431416, upload-time = "2026-03-23T17:44:05.435Z" }, - { url = "https://files.pythonhosted.org/packages/80/73/396c5c3aaef5004d04abbda4ec736ce4d33944aff6722e974cebdd4023fd/spacy-3.8.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ef884df658d3b60d91cbc1e9b62a6d932589d2fa3f322c3f739b6ffdf3303e0c", size = 32483676, upload-time = "2026-03-23T17:44:10.913Z" }, - { url = "https://files.pythonhosted.org/packages/71/01/15ceb2097a817c2912297eac29f801bd71d895e93d7557110937c88f7c77/spacy-3.8.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:86f58d686d1ba6f0dc818005ee6ad87ade8dde7224012f1870a0d31abccd349e", size = 31732468, upload-time = "2026-03-23T17:44:15.967Z" }, - { url = "https://files.pythonhosted.org/packages/03/c4/b4c39083df6209414faec7d7471994316dc6fca68c4d2f1f8f099f25c2e3/spacy-3.8.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbcb411b9749a1cd5e4325953212b3984d8c0f60b4976943e77fb0c7d5027383", size = 32473870, upload-time = "2026-03-23T17:44:21.36Z" }, - { url = "https://files.pythonhosted.org/packages/f2/5c/9b0fa420b01809c0170b3cf0bf509ec06b4da96214995360815615b8e8fa/spacy-3.8.13-cp314-cp314-win_amd64.whl", hash = "sha256:bf9b6879d70201acb73fc5f07d550ff488d3b5f15a959e9896717b2635c8e137", size = 14405303, upload-time = "2026-03-23T17:44:25.83Z" }, - { url = "https://files.pythonhosted.org/packages/fa/58/0001fd8124b62a2a9984278d793e3abfa1b70e969fc26a8668755178db84/spacy-3.8.13-cp314-cp314-win_arm64.whl", hash = "sha256:b2a402f229fcb5dba5454c346468757bd3a5215809e784b85d739ca84916f05b", size = 13834663, upload-time = "2026-03-23T17:44:29.43Z" }, + { url = "https://files.pythonhosted.org/packages/d8/e1/e8f6dc7fc00582b8dcbf40fac5bce6c0ff366cf6db212decdacfaf13e584/spacy-3.8.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dd8ab0deefe9d2c7dccce1be1e65660a32cfc3cc114d0aa222ff52ae11be58ba", size = 6218311 }, + { url = "https://files.pythonhosted.org/packages/31/fe/517de9e25a6ec469738c9e886c15cd96649b338c6ae475b9b3e4c4f5e03d/spacy-3.8.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eab059bc668336d0034f3f69aed6b661b626c174e97cc1b52296d0d923c24405", size = 6033843 }, + { url = "https://files.pythonhosted.org/packages/2c/a8/9a33c69f772f5c32a57c9ef7e692293b558e7bad202264d5586ab087fd29/spacy-3.8.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96a8e141fd7ff749fa6693c9c72187f802d334bcadf48b6b9d6ca9bff315ca73", size = 32725183 }, + { url = "https://files.pythonhosted.org/packages/ba/cf/dcc0ea61270cb23a46d3efc437fe760e7a9c3cabcfaa770591329d1430f2/spacy-3.8.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:32c16f5be8c8006197554da3647d83311fb89bb31bb417d38f0d1da585877b0d", size = 33205816 }, + { url = "https://files.pythonhosted.org/packages/90/6b/ce94ba2a166add3376aa2c976cb1a34d6387e9dc61103cfbe9192675d0b6/spacy-3.8.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7402154eae9d2c34d097f3f7f2bf3ed4ce6ef9d44e3035af15ccc7f357d122cb", size = 32090348 }, + { url = "https://files.pythonhosted.org/packages/4d/20/df5076a162bda36b04cdfa9cd544ac2be4b47aed957b3922e4c8f75dc25a/spacy-3.8.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:879f467578569db73b5811aa0f0b9a3b3fb81a125c2f7a433dd5626bcaca53f6", size = 32991911 }, + { url = "https://files.pythonhosted.org/packages/41/17/316dfdf5f8d1dd33a205e4f5e7190b67db73802180f7d08c8b306eb44375/spacy-3.8.13-cp312-cp312-win_amd64.whl", hash = "sha256:a0c849b5c9cee5a930a5e8a63d0b07e095d4e3d371af6a70c496efa1d0851257", size = 14226861 }, + { url = "https://files.pythonhosted.org/packages/be/a8/a794d5bbf7bc2df6770df2b14cd6811420d0e10fe81830920bf0e891c19a/spacy-3.8.13-cp312-cp312-win_arm64.whl", hash = "sha256:5d7f660f64c7d09778600b27b881a367075fe792cb5cc6932737aeda71a9aa09", size = 13628855 }, + { url = "https://files.pythonhosted.org/packages/a7/2d/dc15440fa58c12bab25cbd368bbf3b754eb11d1d3cc37f3aee47caa63695/spacy-3.8.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6db8903cf3fd8b40db9dab669c1e823a8884100f223dc8ec63c3744ff505d5fd", size = 6202080 }, + { url = "https://files.pythonhosted.org/packages/1b/27/6d723fa0c3c9872d7b8c3fb1c76edef65c1db2de441ee87d119128cb82f7/spacy-3.8.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c26258bfed2f3bf5563c5994ee752ea8662bbac70924d28fbea4d1c10df48fd0", size = 6015435 }, + { url = "https://files.pythonhosted.org/packages/c0/36/d13f36204290e7753ce849106fb732a1a1ff6c1af0a200f2c2f493a56e60/spacy-3.8.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a5f02eab8e6e8e9e790b1e8da05b70b3b1d4b1fb10dc4f44203da96939fdb977", size = 32510675 }, + { url = "https://files.pythonhosted.org/packages/19/37/f2a0c986bc2d59b18547d5ffaabf57fd9d88e129ad7df5ebc9ccdc91e643/spacy-3.8.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:17b14f28d83fe85390f420f89760ae36d515f3e0a236f8266c46335549806e3a", size = 32841103 }, + { url = "https://files.pythonhosted.org/packages/37/31/b4a86cc013e4ac3c3c290ecade748c4623709a78db8a368ee0e152931c0b/spacy-3.8.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:10fffb387e6afeba582e01efd9b5e0c5431ac323af134f4a71a749255182f4c8", size = 31763223 }, + { url = "https://files.pythonhosted.org/packages/4f/fa/b3a406bdc2636aaa315b8539b88f262e78ca391afc4083e3e9806953b24a/spacy-3.8.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:500f6eb9e4711e75fc07d517962dbfb553ba144ccf1b1e8ffc71b014c9ad91b2", size = 32717863 }, + { url = "https://files.pythonhosted.org/packages/d9/97/11ed2a980e7225b0a1a4041cb1cba44f40bd83682a08e450dd6171f054e4/spacy-3.8.13-cp313-cp313-win_amd64.whl", hash = "sha256:1e679a8c5dd86c564a1185d894b1b8e50b52fa81bee518120fc2f86349d1879c", size = 14220413 }, + { url = "https://files.pythonhosted.org/packages/ca/e2/06633e779486f62b3ec7ddeeb4accc10fb9a356b59f5bed3771dfb89931b/spacy-3.8.13-cp313-cp313-win_arm64.whl", hash = "sha256:c086bff909d73be892b4eb6883070dd3e328592b8933f0059a6569c8c7904928", size = 13619060 }, + { url = "https://files.pythonhosted.org/packages/f2/02/bf2943f61a8bd21cca90e4fe19c4da8752c386f02a76e326b33199e09953/spacy-3.8.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:edcdd4f99e2711fc90c6ba3e8e07f7dc9753d111377ba7b7b5eb0d7259b0d211", size = 6209920 }, + { url = "https://files.pythonhosted.org/packages/e7/87/fa5e4eb2ccb660d5042eb9144134dc6238c132ec812131bb0aca296bcdd9/spacy-3.8.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b8fd0188f78e032ac58f70a00748d591e244f3d4718e0ead2d9418b4148c744d", size = 6040696 }, + { url = "https://files.pythonhosted.org/packages/ed/f3/9132878e0c18d627adffc1d23129a3706385d4f0fbe2ea5839523519452c/spacy-3.8.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0dc2d058cb919102bf0634b1b41aff64b867d9bab36bd35979ab658b5b2e4c27", size = 32431416 }, + { url = "https://files.pythonhosted.org/packages/80/73/396c5c3aaef5004d04abbda4ec736ce4d33944aff6722e974cebdd4023fd/spacy-3.8.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ef884df658d3b60d91cbc1e9b62a6d932589d2fa3f322c3f739b6ffdf3303e0c", size = 32483676 }, + { url = "https://files.pythonhosted.org/packages/71/01/15ceb2097a817c2912297eac29f801bd71d895e93d7557110937c88f7c77/spacy-3.8.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:86f58d686d1ba6f0dc818005ee6ad87ade8dde7224012f1870a0d31abccd349e", size = 31732468 }, + { url = "https://files.pythonhosted.org/packages/03/c4/b4c39083df6209414faec7d7471994316dc6fca68c4d2f1f8f099f25c2e3/spacy-3.8.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbcb411b9749a1cd5e4325953212b3984d8c0f60b4976943e77fb0c7d5027383", size = 32473870 }, + { url = "https://files.pythonhosted.org/packages/f2/5c/9b0fa420b01809c0170b3cf0bf509ec06b4da96214995360815615b8e8fa/spacy-3.8.13-cp314-cp314-win_amd64.whl", hash = "sha256:bf9b6879d70201acb73fc5f07d550ff488d3b5f15a959e9896717b2635c8e137", size = 14405303 }, + { url = "https://files.pythonhosted.org/packages/fa/58/0001fd8124b62a2a9984278d793e3abfa1b70e969fc26a8668755178db84/spacy-3.8.13-cp314-cp314-win_arm64.whl", hash = "sha256:b2a402f229fcb5dba5454c346468757bd3a5215809e784b85d739ca84916f05b", size = 13834663 }, ] [[package]] @@ -7961,27 +7944,27 @@ dependencies = [ { name = "curated-transformers" }, { name = "torch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d8/b3/a4fd3cf28008cbe1d95463b5c76a0d9c8da7b9ad4f06289c2be4aae62052/spacy_curated_transformers-0.3.1.tar.gz", hash = "sha256:7e53fccf64260e641b0a3f2b65b6d98381b86cef6eeb21ce279e8db849e8525d", size = 218990, upload-time = "2025-05-28T10:29:32.69Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/b3/a4fd3cf28008cbe1d95463b5c76a0d9c8da7b9ad4f06289c2be4aae62052/spacy_curated_transformers-0.3.1.tar.gz", hash = "sha256:7e53fccf64260e641b0a3f2b65b6d98381b86cef6eeb21ce279e8db849e8525d", size = 218990 } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d8/f053d43125ae4ad14f3e2a12a475a656128233f1f40a272c6e09a05c73e8/spacy_curated_transformers-0.3.1-py2.py3-none-any.whl", hash = "sha256:503559b6a1d6e44ec2c978e18ed871ce5c3d56871dc9216c0e1523428204e610", size = 237943, upload-time = "2025-05-28T10:29:31.058Z" }, + { url = "https://files.pythonhosted.org/packages/42/d8/f053d43125ae4ad14f3e2a12a475a656128233f1f40a272c6e09a05c73e8/spacy_curated_transformers-0.3.1-py2.py3-none-any.whl", hash = "sha256:503559b6a1d6e44ec2c978e18ed871ce5c3d56871dc9216c0e1523428204e610", size = 237943 }, ] [[package]] name = "spacy-legacy" version = "3.0.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d9/79/91f9d7cc8db5642acad830dcc4b49ba65a7790152832c4eceb305e46d681/spacy-legacy-3.0.12.tar.gz", hash = "sha256:b37d6e0c9b6e1d7ca1cf5bc7152ab64a4c4671f59c85adaf7a3fcb870357a774", size = 23806, upload-time = "2023-01-23T09:04:15.104Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/79/91f9d7cc8db5642acad830dcc4b49ba65a7790152832c4eceb305e46d681/spacy-legacy-3.0.12.tar.gz", hash = "sha256:b37d6e0c9b6e1d7ca1cf5bc7152ab64a4c4671f59c85adaf7a3fcb870357a774", size = 23806 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/55/12e842c70ff8828e34e543a2c7176dac4da006ca6901c9e8b43efab8bc6b/spacy_legacy-3.0.12-py2.py3-none-any.whl", hash = "sha256:476e3bd0d05f8c339ed60f40986c07387c0a71479245d6d0f4298dbd52cda55f", size = 29971, upload-time = "2023-01-23T09:04:13.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/55/12e842c70ff8828e34e543a2c7176dac4da006ca6901c9e8b43efab8bc6b/spacy_legacy-3.0.12-py2.py3-none-any.whl", hash = "sha256:476e3bd0d05f8c339ed60f40986c07387c0a71479245d6d0f4298dbd52cda55f", size = 29971 }, ] [[package]] name = "spacy-loggers" version = "1.0.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/67/3d/926db774c9c98acf66cb4ed7faf6c377746f3e00b84b700d0868b95d0712/spacy-loggers-1.0.5.tar.gz", hash = "sha256:d60b0bdbf915a60e516cc2e653baeff946f0cfc461b452d11a4d5458c6fe5f24", size = 20811, upload-time = "2023-09-11T12:26:52.323Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/3d/926db774c9c98acf66cb4ed7faf6c377746f3e00b84b700d0868b95d0712/spacy-loggers-1.0.5.tar.gz", hash = "sha256:d60b0bdbf915a60e516cc2e653baeff946f0cfc461b452d11a4d5458c6fe5f24", size = 20811 } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/78/d1a1a026ef3af911159398c939b1509d5c36fe524c7b644f34a5146c4e16/spacy_loggers-1.0.5-py3-none-any.whl", hash = "sha256:196284c9c446cc0cdb944005384270d775fdeaf4f494d8e269466cfa497ef645", size = 22343, upload-time = "2023-09-11T12:26:50.586Z" }, + { url = "https://files.pythonhosted.org/packages/33/78/d1a1a026ef3af911159398c939b1509d5c36fe524c7b644f34a5146c4e16/spacy_loggers-1.0.5-py3-none-any.whl", hash = "sha256:196284c9c446cc0cdb944005384270d775fdeaf4f494d8e269466cfa497ef645", size = 22343 }, ] [[package]] @@ -7992,42 +7975,42 @@ dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/73/b4a9737255583b5fa858e0bb8e116eb94b88c910164ed2ed719147bde3de/sqlalchemy-2.0.48.tar.gz", hash = "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7", size = 9886075, upload-time = "2026-03-02T15:28:51.474Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/73/b4a9737255583b5fa858e0bb8e116eb94b88c910164ed2ed719147bde3de/sqlalchemy-2.0.48.tar.gz", hash = "sha256:5ca74f37f3369b45e1f6b7b06afb182af1fd5dde009e4ffd831830d98cbe5fe7", size = 9886075 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/91/a42ae716f8925e9659df2da21ba941f158686856107a61cc97a95e7647a3/sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b", size = 2155737, upload-time = "2026-03-02T15:49:13.207Z" }, - { url = "https://files.pythonhosted.org/packages/b9/52/f75f516a1f3888f027c1cfb5d22d4376f4b46236f2e8669dcb0cddc60275/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb", size = 3337020, upload-time = "2026-03-02T15:50:34.547Z" }, - { url = "https://files.pythonhosted.org/packages/37/9a/0c28b6371e0cdcb14f8f1930778cb3123acfcbd2c95bb9cf6b4a2ba0cce3/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894", size = 3349983, upload-time = "2026-03-02T15:53:25.542Z" }, - { url = "https://files.pythonhosted.org/packages/1c/46/0aee8f3ff20b1dcbceb46ca2d87fcc3d48b407925a383ff668218509d132/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9", size = 3279690, upload-time = "2026-03-02T15:50:36.277Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8c/a957bc91293b49181350bfd55e6dfc6e30b7f7d83dc6792d72043274a390/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e", size = 3314738, upload-time = "2026-03-02T15:53:27.519Z" }, - { url = "https://files.pythonhosted.org/packages/4b/44/1d257d9f9556661e7bdc83667cc414ba210acfc110c82938cb3611eea58f/sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99", size = 2115546, upload-time = "2026-03-02T15:54:31.591Z" }, - { url = "https://files.pythonhosted.org/packages/f2/af/c3c7e1f3a2b383155a16454df62ae8c62a30dd238e42e68c24cebebbfae6/sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a", size = 2142484, upload-time = "2026-03-02T15:54:34.072Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4", size = 2152599, upload-time = "2026-03-02T15:49:14.41Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ff/f4e04a4bd5a24304f38cb0d4aa2ad4c0fb34999f8b884c656535e1b2b74c/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f", size = 3278825, upload-time = "2026-03-02T15:50:38.269Z" }, - { url = "https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed", size = 3295200, upload-time = "2026-03-02T15:53:29.366Z" }, - { url = "https://files.pythonhosted.org/packages/87/dc/1609a4442aefd750ea2f32629559394ec92e89ac1d621a7f462b70f736ff/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b193a7e29fd9fa56e502920dca47dffe60f97c863494946bd698c6058a55658", size = 3226876, upload-time = "2026-03-02T15:50:39.802Z" }, - { url = "https://files.pythonhosted.org/packages/37/c3/6ae2ab5ea2fa989fbac4e674de01224b7a9d744becaf59bb967d62e99bed/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36ac4ddc3d33e852da9cb00ffb08cea62ca05c39711dc67062ca2bb1fae35fd8", size = 3265045, upload-time = "2026-03-02T15:53:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/6f/82/ea4665d1bb98c50c19666e672f21b81356bd6077c4574e3d2bbb84541f53/sqlalchemy-2.0.48-cp313-cp313-win32.whl", hash = "sha256:389b984139278f97757ea9b08993e7b9d1142912e046ab7d82b3fbaeb0209131", size = 2113700, upload-time = "2026-03-02T15:54:35.825Z" }, - { url = "https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl", hash = "sha256:d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2", size = 2139487, upload-time = "2026-03-02T15:54:37.079Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/7b17bd50244b78a49d22cc63c969d71dc4de54567dc152a9b46f6fae40ce/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69f5bc24904d3bc3640961cddd2523e361257ef68585d6e364166dfbe8c78fae", size = 3558851, upload-time = "2026-03-02T15:57:48.607Z" }, - { url = "https://files.pythonhosted.org/packages/20/0d/213668e9aca61d370f7d2a6449ea4ec699747fac67d4bda1bb3d129025be/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd08b90d211c086181caed76931ecfa2bdfc83eea3cfccdb0f82abc6c4b876cb", size = 3525525, upload-time = "2026-03-02T16:04:38.058Z" }, - { url = "https://files.pythonhosted.org/packages/85/d7/a84edf412979e7d59c69b89a5871f90a49228360594680e667cb2c46a828/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1ccd42229aaac2df431562117ac7e667d702e8e44afdb6cf0e50fa3f18160f0b", size = 3466611, upload-time = "2026-03-02T15:57:50.759Z" }, - { url = "https://files.pythonhosted.org/packages/86/55/42404ce5770f6be26a2b0607e7866c31b9a4176c819e9a7a5e0a055770be/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0dcbc588cd5b725162c076eb9119342f6579c7f7f55057bb7e3c6ff27e13121", size = 3475812, upload-time = "2026-03-02T16:04:40.092Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ae/29b87775fadc43e627cf582fe3bda4d02e300f6b8f2747c764950d13784c/sqlalchemy-2.0.48-cp313-cp313t-win32.whl", hash = "sha256:9764014ef5e58aab76220c5664abb5d47d5bc858d9debf821e55cfdd0f128485", size = 2141335, upload-time = "2026-03-02T15:52:51.518Z" }, - { url = "https://files.pythonhosted.org/packages/91/44/f39d063c90f2443e5b46ec4819abd3d8de653893aae92df42a5c4f5843de/sqlalchemy-2.0.48-cp313-cp313t-win_amd64.whl", hash = "sha256:e2f35b4cccd9ed286ad62e0a3c3ac21e06c02abc60e20aa51a3e305a30f5fa79", size = 2173095, upload-time = "2026-03-02T15:52:52.79Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b3/f437eaa1cf028bb3c927172c7272366393e73ccd104dcf5b6963f4ab5318/sqlalchemy-2.0.48-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e2d0d88686e3d35a76f3e15a34e8c12d73fc94c1dea1cd55782e695cc14086dd", size = 2154401, upload-time = "2026-03-02T15:49:17.24Z" }, - { url = "https://files.pythonhosted.org/packages/6c/1c/b3abdf0f402aa3f60f0df6ea53d92a162b458fca2321d8f1f00278506402/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49b7bddc1eebf011ea5ab722fdbe67a401caa34a350d278cc7733c0e88fecb1f", size = 3274528, upload-time = "2026-03-02T15:50:41.489Z" }, - { url = "https://files.pythonhosted.org/packages/f2/5e/327428a034407651a048f5e624361adf3f9fbac9d0fa98e981e9c6ff2f5e/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:426c5ca86415d9b8945c7073597e10de9644802e2ff502b8e1f11a7a2642856b", size = 3279523, upload-time = "2026-03-02T15:53:32.962Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ca/ece73c81a918add0965b76b868b7b5359e068380b90ef1656ee995940c02/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:288937433bd44e3990e7da2402fabc44a3c6c25d3704da066b85b89a85474ae0", size = 3224312, upload-time = "2026-03-02T15:50:42.996Z" }, - { url = "https://files.pythonhosted.org/packages/88/11/fbaf1ae91fa4ee43f4fe79661cead6358644824419c26adb004941bdce7c/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8183dc57ae7d9edc1346e007e840a9f3d6aa7b7f165203a99e16f447150140d2", size = 3246304, upload-time = "2026-03-02T15:53:34.937Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5fb0deb13930b4f2f698c5541ae076c18981173e27dd00376dbaea7a9c82/sqlalchemy-2.0.48-cp314-cp314-win32.whl", hash = "sha256:1182437cb2d97988cfea04cf6cdc0b0bb9c74f4d56ec3d08b81e23d621a28cc6", size = 2116565, upload-time = "2026-03-02T15:54:38.321Z" }, - { url = "https://files.pythonhosted.org/packages/95/7e/e83615cb63f80047f18e61e31e8e32257d39458426c23006deeaf48f463b/sqlalchemy-2.0.48-cp314-cp314-win_amd64.whl", hash = "sha256:144921da96c08feb9e2b052c5c5c1d0d151a292c6135623c6b2c041f2a45f9e0", size = 2142205, upload-time = "2026-03-02T15:54:39.831Z" }, - { url = "https://files.pythonhosted.org/packages/83/e3/69d8711b3f2c5135e9cde5f063bc1605860f0b2c53086d40c04017eb1f77/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5aee45fd2c6c0f2b9cdddf48c48535e7471e42d6fb81adfde801da0bd5b93241", size = 3563519, upload-time = "2026-03-02T15:57:52.387Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4f/a7cce98facca73c149ea4578981594aaa5fd841e956834931de503359336/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cddca31edf8b0653090cbb54562ca027c421c58ddde2c0685f49ff56a1690e0", size = 3528611, upload-time = "2026-03-02T16:04:42.097Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7d/5936c7a03a0b0cb0fa0cc425998821c6029756b0855a8f7ee70fba1de955/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7a936f1bb23d370b7c8cc079d5fce4c7d18da87a33c6744e51a93b0f9e97e9b3", size = 3472326, upload-time = "2026-03-02T15:57:54.423Z" }, - { url = "https://files.pythonhosted.org/packages/f4/33/cea7dfc31b52904efe3dcdc169eb4514078887dff1f5ae28a7f4c5d54b3c/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e004aa9248e8cb0a5f9b96d003ca7c1c0a5da8decd1066e7b53f59eb8ce7c62b", size = 3478453, upload-time = "2026-03-02T16:04:44.584Z" }, - { url = "https://files.pythonhosted.org/packages/c8/95/32107c4d13be077a9cae61e9ae49966a35dc4bf442a8852dd871db31f62e/sqlalchemy-2.0.48-cp314-cp314t-win32.whl", hash = "sha256:b8438ec5594980d405251451c5b7ea9aa58dda38eb7ac35fb7e4c696712ee24f", size = 2147209, upload-time = "2026-03-02T15:52:54.274Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d7/1e073da7a4bc645eb83c76067284a0374e643bc4be57f14cc6414656f92c/sqlalchemy-2.0.48-cp314-cp314t-win_amd64.whl", hash = "sha256:d854b3970067297f3a7fbd7a4683587134aa9b3877ee15aa29eea478dc68f933", size = 2182198, upload-time = "2026-03-02T15:52:55.606Z" }, - { url = "https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096", size = 1940202, upload-time = "2026-03-02T15:52:43.285Z" }, + { url = "https://files.pythonhosted.org/packages/ef/91/a42ae716f8925e9659df2da21ba941f158686856107a61cc97a95e7647a3/sqlalchemy-2.0.48-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:348174f228b99f33ca1f773e85510e08927620caa59ffe7803b37170df30332b", size = 2155737 }, + { url = "https://files.pythonhosted.org/packages/b9/52/f75f516a1f3888f027c1cfb5d22d4376f4b46236f2e8669dcb0cddc60275/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53667b5f668991e279d21f94ccfa6e45b4e3f4500e7591ae59a8012d0f010dcb", size = 3337020 }, + { url = "https://files.pythonhosted.org/packages/37/9a/0c28b6371e0cdcb14f8f1930778cb3123acfcbd2c95bb9cf6b4a2ba0cce3/sqlalchemy-2.0.48-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34634e196f620c7a61d18d5cf7dc841ca6daa7961aed75d532b7e58b309ac894", size = 3349983 }, + { url = "https://files.pythonhosted.org/packages/1c/46/0aee8f3ff20b1dcbceb46ca2d87fcc3d48b407925a383ff668218509d132/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:546572a1793cc35857a2ffa1fe0e58571af1779bcc1ffa7c9fb0839885ed69a9", size = 3279690 }, + { url = "https://files.pythonhosted.org/packages/ce/8c/a957bc91293b49181350bfd55e6dfc6e30b7f7d83dc6792d72043274a390/sqlalchemy-2.0.48-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07edba08061bc277bfdc772dd2a1a43978f5a45994dd3ede26391b405c15221e", size = 3314738 }, + { url = "https://files.pythonhosted.org/packages/4b/44/1d257d9f9556661e7bdc83667cc414ba210acfc110c82938cb3611eea58f/sqlalchemy-2.0.48-cp312-cp312-win32.whl", hash = "sha256:908a3fa6908716f803b86896a09a2c4dde5f5ce2bb07aacc71ffebb57986ce99", size = 2115546 }, + { url = "https://files.pythonhosted.org/packages/f2/af/c3c7e1f3a2b383155a16454df62ae8c62a30dd238e42e68c24cebebbfae6/sqlalchemy-2.0.48-cp312-cp312-win_amd64.whl", hash = "sha256:68549c403f79a8e25984376480959975212a670405e3913830614432b5daa07a", size = 2142484 }, + { url = "https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4", size = 2152599 }, + { url = "https://files.pythonhosted.org/packages/6d/ff/f4e04a4bd5a24304f38cb0d4aa2ad4c0fb34999f8b884c656535e1b2b74c/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f", size = 3278825 }, + { url = "https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed", size = 3295200 }, + { url = "https://files.pythonhosted.org/packages/87/dc/1609a4442aefd750ea2f32629559394ec92e89ac1d621a7f462b70f736ff/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b193a7e29fd9fa56e502920dca47dffe60f97c863494946bd698c6058a55658", size = 3226876 }, + { url = "https://files.pythonhosted.org/packages/37/c3/6ae2ab5ea2fa989fbac4e674de01224b7a9d744becaf59bb967d62e99bed/sqlalchemy-2.0.48-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:36ac4ddc3d33e852da9cb00ffb08cea62ca05c39711dc67062ca2bb1fae35fd8", size = 3265045 }, + { url = "https://files.pythonhosted.org/packages/6f/82/ea4665d1bb98c50c19666e672f21b81356bd6077c4574e3d2bbb84541f53/sqlalchemy-2.0.48-cp313-cp313-win32.whl", hash = "sha256:389b984139278f97757ea9b08993e7b9d1142912e046ab7d82b3fbaeb0209131", size = 2113700 }, + { url = "https://files.pythonhosted.org/packages/b7/2b/b9040bec58c58225f073f5b0c1870defe1940835549dafec680cbd58c3c3/sqlalchemy-2.0.48-cp313-cp313-win_amd64.whl", hash = "sha256:d612c976cbc2d17edfcc4c006874b764e85e990c29ce9bd411f926bbfb02b9a2", size = 2139487 }, + { url = "https://files.pythonhosted.org/packages/f4/f4/7b17bd50244b78a49d22cc63c969d71dc4de54567dc152a9b46f6fae40ce/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69f5bc24904d3bc3640961cddd2523e361257ef68585d6e364166dfbe8c78fae", size = 3558851 }, + { url = "https://files.pythonhosted.org/packages/20/0d/213668e9aca61d370f7d2a6449ea4ec699747fac67d4bda1bb3d129025be/sqlalchemy-2.0.48-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd08b90d211c086181caed76931ecfa2bdfc83eea3cfccdb0f82abc6c4b876cb", size = 3525525 }, + { url = "https://files.pythonhosted.org/packages/85/d7/a84edf412979e7d59c69b89a5871f90a49228360594680e667cb2c46a828/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1ccd42229aaac2df431562117ac7e667d702e8e44afdb6cf0e50fa3f18160f0b", size = 3466611 }, + { url = "https://files.pythonhosted.org/packages/86/55/42404ce5770f6be26a2b0607e7866c31b9a4176c819e9a7a5e0a055770be/sqlalchemy-2.0.48-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0dcbc588cd5b725162c076eb9119342f6579c7f7f55057bb7e3c6ff27e13121", size = 3475812 }, + { url = "https://files.pythonhosted.org/packages/ae/ae/29b87775fadc43e627cf582fe3bda4d02e300f6b8f2747c764950d13784c/sqlalchemy-2.0.48-cp313-cp313t-win32.whl", hash = "sha256:9764014ef5e58aab76220c5664abb5d47d5bc858d9debf821e55cfdd0f128485", size = 2141335 }, + { url = "https://files.pythonhosted.org/packages/91/44/f39d063c90f2443e5b46ec4819abd3d8de653893aae92df42a5c4f5843de/sqlalchemy-2.0.48-cp313-cp313t-win_amd64.whl", hash = "sha256:e2f35b4cccd9ed286ad62e0a3c3ac21e06c02abc60e20aa51a3e305a30f5fa79", size = 2173095 }, + { url = "https://files.pythonhosted.org/packages/f7/b3/f437eaa1cf028bb3c927172c7272366393e73ccd104dcf5b6963f4ab5318/sqlalchemy-2.0.48-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e2d0d88686e3d35a76f3e15a34e8c12d73fc94c1dea1cd55782e695cc14086dd", size = 2154401 }, + { url = "https://files.pythonhosted.org/packages/6c/1c/b3abdf0f402aa3f60f0df6ea53d92a162b458fca2321d8f1f00278506402/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49b7bddc1eebf011ea5ab722fdbe67a401caa34a350d278cc7733c0e88fecb1f", size = 3274528 }, + { url = "https://files.pythonhosted.org/packages/f2/5e/327428a034407651a048f5e624361adf3f9fbac9d0fa98e981e9c6ff2f5e/sqlalchemy-2.0.48-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:426c5ca86415d9b8945c7073597e10de9644802e2ff502b8e1f11a7a2642856b", size = 3279523 }, + { url = "https://files.pythonhosted.org/packages/2a/ca/ece73c81a918add0965b76b868b7b5359e068380b90ef1656ee995940c02/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:288937433bd44e3990e7da2402fabc44a3c6c25d3704da066b85b89a85474ae0", size = 3224312 }, + { url = "https://files.pythonhosted.org/packages/88/11/fbaf1ae91fa4ee43f4fe79661cead6358644824419c26adb004941bdce7c/sqlalchemy-2.0.48-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8183dc57ae7d9edc1346e007e840a9f3d6aa7b7f165203a99e16f447150140d2", size = 3246304 }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5fb0deb13930b4f2f698c5541ae076c18981173e27dd00376dbaea7a9c82/sqlalchemy-2.0.48-cp314-cp314-win32.whl", hash = "sha256:1182437cb2d97988cfea04cf6cdc0b0bb9c74f4d56ec3d08b81e23d621a28cc6", size = 2116565 }, + { url = "https://files.pythonhosted.org/packages/95/7e/e83615cb63f80047f18e61e31e8e32257d39458426c23006deeaf48f463b/sqlalchemy-2.0.48-cp314-cp314-win_amd64.whl", hash = "sha256:144921da96c08feb9e2b052c5c5c1d0d151a292c6135623c6b2c041f2a45f9e0", size = 2142205 }, + { url = "https://files.pythonhosted.org/packages/83/e3/69d8711b3f2c5135e9cde5f063bc1605860f0b2c53086d40c04017eb1f77/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5aee45fd2c6c0f2b9cdddf48c48535e7471e42d6fb81adfde801da0bd5b93241", size = 3563519 }, + { url = "https://files.pythonhosted.org/packages/f8/4f/a7cce98facca73c149ea4578981594aaa5fd841e956834931de503359336/sqlalchemy-2.0.48-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cddca31edf8b0653090cbb54562ca027c421c58ddde2c0685f49ff56a1690e0", size = 3528611 }, + { url = "https://files.pythonhosted.org/packages/cd/7d/5936c7a03a0b0cb0fa0cc425998821c6029756b0855a8f7ee70fba1de955/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7a936f1bb23d370b7c8cc079d5fce4c7d18da87a33c6744e51a93b0f9e97e9b3", size = 3472326 }, + { url = "https://files.pythonhosted.org/packages/f4/33/cea7dfc31b52904efe3dcdc169eb4514078887dff1f5ae28a7f4c5d54b3c/sqlalchemy-2.0.48-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e004aa9248e8cb0a5f9b96d003ca7c1c0a5da8decd1066e7b53f59eb8ce7c62b", size = 3478453 }, + { url = "https://files.pythonhosted.org/packages/c8/95/32107c4d13be077a9cae61e9ae49966a35dc4bf442a8852dd871db31f62e/sqlalchemy-2.0.48-cp314-cp314t-win32.whl", hash = "sha256:b8438ec5594980d405251451c5b7ea9aa58dda38eb7ac35fb7e4c696712ee24f", size = 2147209 }, + { url = "https://files.pythonhosted.org/packages/d2/d7/1e073da7a4bc645eb83c76067284a0374e643bc4be57f14cc6414656f92c/sqlalchemy-2.0.48-cp314-cp314t-win_amd64.whl", hash = "sha256:d854b3970067297f3a7fbd7a4683587134aa9b3877ee15aa29eea478dc68f933", size = 2182198 }, + { url = "https://files.pythonhosted.org/packages/46/2c/9664130905f03db57961b8980b05cab624afd114bf2be2576628a9f22da4/sqlalchemy-2.0.48-py3-none-any.whl", hash = "sha256:a66fe406437dd65cacd96a72689a3aaaecaebbcd62d81c5ac1c0fdbeac835096", size = 1940202 }, ] [package.optional-dependencies] @@ -8042,40 +8025,40 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "catalogue" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2b/db/f794f219a6c788b881252d2536a8c4a97d2bdaadc690391e1cb53d123d71/srsly-2.5.3.tar.gz", hash = "sha256:08f98dbecbff3a31466c4ae7c833131f59d3655a0ad8ac749e6e2c149e2b0680", size = 490881, upload-time = "2026-03-23T11:56:59.865Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/db/f794f219a6c788b881252d2536a8c4a97d2bdaadc690391e1cb53d123d71/srsly-2.5.3.tar.gz", hash = "sha256:08f98dbecbff3a31466c4ae7c833131f59d3655a0ad8ac749e6e2c149e2b0680", size = 490881 } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/e9f7fcec4cc92ad8bad6316c4241638b8cf7380382d4489d94ec6c436452/srsly-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:71e51c046ccbeefb86524c6b1e17574f579c6ac4dc8ea4a09437d3e8f88342d3", size = 658379, upload-time = "2026-03-23T11:55:59.85Z" }, - { url = "https://files.pythonhosted.org/packages/21/e4/fea4512e9785f58509b2cf67d993323848e583161b5fcfdc7dd9d7c1f3df/srsly-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f73c0db911552e94fe2016e1759d261d2f47926f68826664cada3723c87006a", size = 658513, upload-time = "2026-03-23T11:56:01.239Z" }, - { url = "https://files.pythonhosted.org/packages/20/b1/53591681b6ff2699a4f97b2d5552ba196eaa6a979b0873605f4c04b5f7ee/srsly-2.5.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c1ac27ae5f4bb9163c7d2c45fc8ec173aac3d92e32086d9472b326c5c6e570e", size = 1172265, upload-time = "2026-03-23T11:56:02.589Z" }, - { url = "https://files.pythonhosted.org/packages/4e/c9/741e29f534919a944a16da4184924b1d3404c4bf60716ab2b91be771d1e3/srsly-2.5.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:99026bcd9cbd3211cc36517400b04ca0fc5d3e412b14daf84ee6e65f67d9a2d8", size = 1180873, upload-time = "2026-03-23T11:56:03.944Z" }, - { url = "https://files.pythonhosted.org/packages/89/57/5554f786eccf78b2750d6ac63be126e1b67badec2cb409dd611cf6f8c52b/srsly-2.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:07d682679e639eb46ff7e6da4a92714f4d5ffe351d088ee66f221e9b1f8865bb", size = 1120437, upload-time = "2026-03-23T11:56:05.283Z" }, - { url = "https://files.pythonhosted.org/packages/eb/95/9b4f73b1be3692f86d72ccc131c8e50f26f824d5c8830a59390bcc5b60ef/srsly-2.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8e0542d85d6b55cf2934050d6ffcb1cd76c768dcf9572e7467002cf087bb366d", size = 1137376, upload-time = "2026-03-23T11:56:06.613Z" }, - { url = "https://files.pythonhosted.org/packages/5a/de/89ca640ca1953c4612279ce515d0af35658df3c06cdb324329bc91b4a7e1/srsly-2.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:598f1e494c18cacb978299d77125415a586417081959f8ec3f068b32d97f8933", size = 652459, upload-time = "2026-03-23T11:56:07.994Z" }, - { url = "https://files.pythonhosted.org/packages/6d/4f/7ab6d49e36d9cc72ee15746cabd116eb6f338be8a06c1882968ee9d6c7d7/srsly-2.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:4b1b721cd3ad1a9b2343519aadc786a4d09d5c0666962d49852eb12d6ec3fe26", size = 638411, upload-time = "2026-03-23T11:56:09.31Z" }, - { url = "https://files.pythonhosted.org/packages/9d/5c/12901e3794f4158abc6da750725aad6c2afddb1e4227b300fe7c71f66957/srsly-2.5.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e67b6bbacbfadea5e100266d2797f2d4cec9883ea4dc84a5537673850036a8d8", size = 656750, upload-time = "2026-03-23T11:56:10.708Z" }, - { url = "https://files.pythonhosted.org/packages/04/61/181c26370995f96f56f1b64b801e3ca1e0d703fc36506ae28606d62369fb/srsly-2.5.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:348c231b4477d8fe86603131d0f166d2feac9c372704dfc4398be71cc5b6fb07", size = 656746, upload-time = "2026-03-23T11:56:12.28Z" }, - { url = "https://files.pythonhosted.org/packages/77/c6/35876c78889f8ffe11ed3521644e666c3aef20ea31527b70f47456cf35c2/srsly-2.5.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b0938c2978c91ae1ef9c1f2ba35abb86330e198fb23469e356eba311e02233ee", size = 1155762, upload-time = "2026-03-23T11:56:14.075Z" }, - { url = "https://files.pythonhosted.org/packages/3e/da/40b71ca9906c8eb8f8feb6ac11d33dad458c85a56e1de764b96d402168a0/srsly-2.5.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f6a837954429ecbe6dcdd27390d2fb4c7d01a3f99c9ffcf9ce66b2a6dd1b738", size = 1161092, upload-time = "2026-03-23T11:56:15.778Z" }, - { url = "https://files.pythonhosted.org/packages/dc/14/c0dd30cc8b93ce8137ff4766f743c882440ce49195fffc5d50eaeef311a6/srsly-2.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3576c125c486ce2958c2047e8858fe3cfc9ea877adfa05203b0986f9badee355", size = 1109984, upload-time = "2026-03-23T11:56:17.056Z" }, - { url = "https://files.pythonhosted.org/packages/08/f3/34354f183d8faafc631585571224b54d1b4b67e796972c36519c074ca355/srsly-2.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fb59c42922e095d1ea36085c55bc16e2adb06a7bfe57b24d381e0194ae699f2", size = 1128409, upload-time = "2026-03-23T11:56:18.761Z" }, - { url = "https://files.pythonhosted.org/packages/a4/d9/5531f8a19492060b4e76e4ab06aca6f096fb5128fe18cc813d1772daf653/srsly-2.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:111805927f05f5db440aeeacb85ce43da0b19ce7b2a09567a9ef8d30f3cc4d83", size = 650820, upload-time = "2026-03-23T11:56:20.096Z" }, - { url = "https://files.pythonhosted.org/packages/8e/8a/62fb7a971eca29e12f03fb9ddacb058548c14d33e5b5675ff0f85839cc7b/srsly-2.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:0f106b0a700ab56e4a7c431b0f1444009ab6cb332edc7bbf6811c2a43f4722cb", size = 637278, upload-time = "2026-03-23T11:56:21.439Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5b/e4ef43c2a381711230af98d4c94a5323df48d6a7899ee652e05bf889290e/srsly-2.5.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:39c13d552a9f9674a12cdcdc66b0c2f02f3430d0cd04c5f9cf598824c2bd3d65", size = 661294, upload-time = "2026-03-23T11:56:23.29Z" }, - { url = "https://files.pythonhosted.org/packages/92/2d/ebce7f3717e52cd0a01f4ec570f388f3b7098526794fcf1ad734e0b8f852/srsly-2.5.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:14c930767cc169611a2dc14e23bc7638cfb616d6f79029700ade033607343540", size = 660952, upload-time = "2026-03-23T11:56:24.908Z" }, - { url = "https://files.pythonhosted.org/packages/22/47/a8f3e9b214be2624c8e8a78d38ca7b1d4e26b92d57018412e4bfc4abe89a/srsly-2.5.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2f2d464f0d0237e32fb53f0ec6f05418652c550e772b50e9918e83a1577cba4d", size = 1154554, upload-time = "2026-03-23T11:56:26.608Z" }, - { url = "https://files.pythonhosted.org/packages/d6/71/2a89dc3180a51e633a87a079ca064225f4aaf46c7b2a5fc720e28f261d98/srsly-2.5.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d18933248a5bb0ad56a1bae6003a9a7f37daac2ecb0c5bcbfaaf081b317e1c84", size = 1155746, upload-time = "2026-03-23T11:56:28.102Z" }, - { url = "https://files.pythonhosted.org/packages/b8/36/72e5ce3153927ca404b6f5bf5280e6ff3399c11557df472b153945468e0a/srsly-2.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7ea5412ea229e571ac9738cbe14f845cc06c8e4e956afb5f42061ccd087ef31f", size = 1112374, upload-time = "2026-03-23T11:56:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/04/b2/0895de109c28eca0d41a811ab7c076d4e4a505e8466f06bae22f5180a1dd/srsly-2.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8d3988970b4cf7d03bdd5b5169302ff84562dd2e1e0f84aeb34df3e5b5dc19bf", size = 1127732, upload-time = "2026-03-23T11:56:31.458Z" }, - { url = "https://files.pythonhosted.org/packages/c7/79/a37fa7759797fbdfe0a2e029ab13e78b1e81e191220d2bb8ff57d869aefb/srsly-2.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:6a02d7dcc16126c8fae1c1c09b2072798a1dc482ab5f9c52b12c7114dac47325", size = 656467, upload-time = "2026-03-23T11:56:33.14Z" }, - { url = "https://files.pythonhosted.org/packages/d7/25/0dae019b3b90ad9037f91de4c390555cdaac9460a93ad62b02b03babdff5/srsly-2.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:1c9129c4abe31903ff7996904a51afdd5428060de6c3d12af49a4da5e8df2821", size = 643040, upload-time = "2026-03-23T11:56:34.448Z" }, - { url = "https://files.pythonhosted.org/packages/3a/44/72dd5285b2e05435d98b0797f101d91d9b345d491ddc1fdb9bd09e27ccb8/srsly-2.5.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:29d5d01ba4c2e9c01f936e5e6d5babc4a47b38c9cbd6e1ec23f6d5a49df32605", size = 666200, upload-time = "2026-03-23T11:56:35.753Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ad/002c71b87fc3f648c9bf0ec47de0c3822bf2c95c8896a589dd03e7fd3977/srsly-2.5.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5c8df4039426d99f0148b5743542842ab96b82daded0b342555e15a639927757", size = 667409, upload-time = "2026-03-23T11:56:37.172Z" }, - { url = "https://files.pythonhosted.org/packages/2a/35/2cea3d5e80aeecfc4ece9e7e1783e7792cc3bad7ab85ab585882e1db4e38/srsly-2.5.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:06a43d63bde2e8cccadb953d7fff70b18196ca286b65dd2ad16006d65f3f8166", size = 1265941, upload-time = "2026-03-23T11:56:38.825Z" }, - { url = "https://files.pythonhosted.org/packages/aa/38/8a4d7e86dd0370a2e5af251b646000197bb5b7e0f9aa360c71bbfb253d0d/srsly-2.5.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:808cfafc047f0dec507a34c8fa8e4cda5722737fd33577df73452f52f7aca644", size = 1250693, upload-time = "2026-03-23T11:56:40.449Z" }, - { url = "https://files.pythonhosted.org/packages/99/05/340129de5ea7b237271b12f8a6962cfa7eb0c5a3056794626d348c5ae7c7/srsly-2.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:71d4cbe2b2a1335c76ed0acae2dc862163787d8b01a705e1949796907ed94ccd", size = 1242408, upload-time = "2026-03-23T11:56:41.8Z" }, - { url = "https://files.pythonhosted.org/packages/01/cb/d7fee7ab27c6aa2e3f865fb7b50ba18c81a4c763bba12bdf53df246441bc/srsly-2.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:565f69083d33cb329cfc74317da937fb3270c0f40fabc1b4488702d8074b4a3e", size = 1242749, upload-time = "2026-03-23T11:56:43.246Z" }, - { url = "https://files.pythonhosted.org/packages/d8/d1/9bad3a0f2fa7b72f4e0cf1d267b00513092d20ef538c47f72823ae4f7656/srsly-2.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:8ac016ffaeac35bc010992b71bf8afdd39d458f201c8138d84cf78778a936e6c", size = 673783, upload-time = "2026-03-23T11:56:44.875Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ae/57d1d7af907e20c077e113e0e4976f87b82c0a415403d99284a262229dd0/srsly-2.5.3-cp314-cp314t-win_arm64.whl", hash = "sha256:d822083fe26ec6728bd8c273ac121fc4ab3864a0fdf0cf0ff3efb188fcd209ed", size = 650229, upload-time = "2026-03-23T11:56:46.148Z" }, + { url = "https://files.pythonhosted.org/packages/02/cc/e9f7fcec4cc92ad8bad6316c4241638b8cf7380382d4489d94ec6c436452/srsly-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:71e51c046ccbeefb86524c6b1e17574f579c6ac4dc8ea4a09437d3e8f88342d3", size = 658379 }, + { url = "https://files.pythonhosted.org/packages/21/e4/fea4512e9785f58509b2cf67d993323848e583161b5fcfdc7dd9d7c1f3df/srsly-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f73c0db911552e94fe2016e1759d261d2f47926f68826664cada3723c87006a", size = 658513 }, + { url = "https://files.pythonhosted.org/packages/20/b1/53591681b6ff2699a4f97b2d5552ba196eaa6a979b0873605f4c04b5f7ee/srsly-2.5.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c1ac27ae5f4bb9163c7d2c45fc8ec173aac3d92e32086d9472b326c5c6e570e", size = 1172265 }, + { url = "https://files.pythonhosted.org/packages/4e/c9/741e29f534919a944a16da4184924b1d3404c4bf60716ab2b91be771d1e3/srsly-2.5.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:99026bcd9cbd3211cc36517400b04ca0fc5d3e412b14daf84ee6e65f67d9a2d8", size = 1180873 }, + { url = "https://files.pythonhosted.org/packages/89/57/5554f786eccf78b2750d6ac63be126e1b67badec2cb409dd611cf6f8c52b/srsly-2.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:07d682679e639eb46ff7e6da4a92714f4d5ffe351d088ee66f221e9b1f8865bb", size = 1120437 }, + { url = "https://files.pythonhosted.org/packages/eb/95/9b4f73b1be3692f86d72ccc131c8e50f26f824d5c8830a59390bcc5b60ef/srsly-2.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8e0542d85d6b55cf2934050d6ffcb1cd76c768dcf9572e7467002cf087bb366d", size = 1137376 }, + { url = "https://files.pythonhosted.org/packages/5a/de/89ca640ca1953c4612279ce515d0af35658df3c06cdb324329bc91b4a7e1/srsly-2.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:598f1e494c18cacb978299d77125415a586417081959f8ec3f068b32d97f8933", size = 652459 }, + { url = "https://files.pythonhosted.org/packages/6d/4f/7ab6d49e36d9cc72ee15746cabd116eb6f338be8a06c1882968ee9d6c7d7/srsly-2.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:4b1b721cd3ad1a9b2343519aadc786a4d09d5c0666962d49852eb12d6ec3fe26", size = 638411 }, + { url = "https://files.pythonhosted.org/packages/9d/5c/12901e3794f4158abc6da750725aad6c2afddb1e4227b300fe7c71f66957/srsly-2.5.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e67b6bbacbfadea5e100266d2797f2d4cec9883ea4dc84a5537673850036a8d8", size = 656750 }, + { url = "https://files.pythonhosted.org/packages/04/61/181c26370995f96f56f1b64b801e3ca1e0d703fc36506ae28606d62369fb/srsly-2.5.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:348c231b4477d8fe86603131d0f166d2feac9c372704dfc4398be71cc5b6fb07", size = 656746 }, + { url = "https://files.pythonhosted.org/packages/77/c6/35876c78889f8ffe11ed3521644e666c3aef20ea31527b70f47456cf35c2/srsly-2.5.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b0938c2978c91ae1ef9c1f2ba35abb86330e198fb23469e356eba311e02233ee", size = 1155762 }, + { url = "https://files.pythonhosted.org/packages/3e/da/40b71ca9906c8eb8f8feb6ac11d33dad458c85a56e1de764b96d402168a0/srsly-2.5.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f6a837954429ecbe6dcdd27390d2fb4c7d01a3f99c9ffcf9ce66b2a6dd1b738", size = 1161092 }, + { url = "https://files.pythonhosted.org/packages/dc/14/c0dd30cc8b93ce8137ff4766f743c882440ce49195fffc5d50eaeef311a6/srsly-2.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3576c125c486ce2958c2047e8858fe3cfc9ea877adfa05203b0986f9badee355", size = 1109984 }, + { url = "https://files.pythonhosted.org/packages/08/f3/34354f183d8faafc631585571224b54d1b4b67e796972c36519c074ca355/srsly-2.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fb59c42922e095d1ea36085c55bc16e2adb06a7bfe57b24d381e0194ae699f2", size = 1128409 }, + { url = "https://files.pythonhosted.org/packages/a4/d9/5531f8a19492060b4e76e4ab06aca6f096fb5128fe18cc813d1772daf653/srsly-2.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:111805927f05f5db440aeeacb85ce43da0b19ce7b2a09567a9ef8d30f3cc4d83", size = 650820 }, + { url = "https://files.pythonhosted.org/packages/8e/8a/62fb7a971eca29e12f03fb9ddacb058548c14d33e5b5675ff0f85839cc7b/srsly-2.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:0f106b0a700ab56e4a7c431b0f1444009ab6cb332edc7bbf6811c2a43f4722cb", size = 637278 }, + { url = "https://files.pythonhosted.org/packages/e1/5b/e4ef43c2a381711230af98d4c94a5323df48d6a7899ee652e05bf889290e/srsly-2.5.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:39c13d552a9f9674a12cdcdc66b0c2f02f3430d0cd04c5f9cf598824c2bd3d65", size = 661294 }, + { url = "https://files.pythonhosted.org/packages/92/2d/ebce7f3717e52cd0a01f4ec570f388f3b7098526794fcf1ad734e0b8f852/srsly-2.5.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:14c930767cc169611a2dc14e23bc7638cfb616d6f79029700ade033607343540", size = 660952 }, + { url = "https://files.pythonhosted.org/packages/22/47/a8f3e9b214be2624c8e8a78d38ca7b1d4e26b92d57018412e4bfc4abe89a/srsly-2.5.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2f2d464f0d0237e32fb53f0ec6f05418652c550e772b50e9918e83a1577cba4d", size = 1154554 }, + { url = "https://files.pythonhosted.org/packages/d6/71/2a89dc3180a51e633a87a079ca064225f4aaf46c7b2a5fc720e28f261d98/srsly-2.5.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d18933248a5bb0ad56a1bae6003a9a7f37daac2ecb0c5bcbfaaf081b317e1c84", size = 1155746 }, + { url = "https://files.pythonhosted.org/packages/b8/36/72e5ce3153927ca404b6f5bf5280e6ff3399c11557df472b153945468e0a/srsly-2.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7ea5412ea229e571ac9738cbe14f845cc06c8e4e956afb5f42061ccd087ef31f", size = 1112374 }, + { url = "https://files.pythonhosted.org/packages/04/b2/0895de109c28eca0d41a811ab7c076d4e4a505e8466f06bae22f5180a1dd/srsly-2.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8d3988970b4cf7d03bdd5b5169302ff84562dd2e1e0f84aeb34df3e5b5dc19bf", size = 1127732 }, + { url = "https://files.pythonhosted.org/packages/c7/79/a37fa7759797fbdfe0a2e029ab13e78b1e81e191220d2bb8ff57d869aefb/srsly-2.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:6a02d7dcc16126c8fae1c1c09b2072798a1dc482ab5f9c52b12c7114dac47325", size = 656467 }, + { url = "https://files.pythonhosted.org/packages/d7/25/0dae019b3b90ad9037f91de4c390555cdaac9460a93ad62b02b03babdff5/srsly-2.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:1c9129c4abe31903ff7996904a51afdd5428060de6c3d12af49a4da5e8df2821", size = 643040 }, + { url = "https://files.pythonhosted.org/packages/3a/44/72dd5285b2e05435d98b0797f101d91d9b345d491ddc1fdb9bd09e27ccb8/srsly-2.5.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:29d5d01ba4c2e9c01f936e5e6d5babc4a47b38c9cbd6e1ec23f6d5a49df32605", size = 666200 }, + { url = "https://files.pythonhosted.org/packages/d2/ad/002c71b87fc3f648c9bf0ec47de0c3822bf2c95c8896a589dd03e7fd3977/srsly-2.5.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5c8df4039426d99f0148b5743542842ab96b82daded0b342555e15a639927757", size = 667409 }, + { url = "https://files.pythonhosted.org/packages/2a/35/2cea3d5e80aeecfc4ece9e7e1783e7792cc3bad7ab85ab585882e1db4e38/srsly-2.5.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:06a43d63bde2e8cccadb953d7fff70b18196ca286b65dd2ad16006d65f3f8166", size = 1265941 }, + { url = "https://files.pythonhosted.org/packages/aa/38/8a4d7e86dd0370a2e5af251b646000197bb5b7e0f9aa360c71bbfb253d0d/srsly-2.5.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:808cfafc047f0dec507a34c8fa8e4cda5722737fd33577df73452f52f7aca644", size = 1250693 }, + { url = "https://files.pythonhosted.org/packages/99/05/340129de5ea7b237271b12f8a6962cfa7eb0c5a3056794626d348c5ae7c7/srsly-2.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:71d4cbe2b2a1335c76ed0acae2dc862163787d8b01a705e1949796907ed94ccd", size = 1242408 }, + { url = "https://files.pythonhosted.org/packages/01/cb/d7fee7ab27c6aa2e3f865fb7b50ba18c81a4c763bba12bdf53df246441bc/srsly-2.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:565f69083d33cb329cfc74317da937fb3270c0f40fabc1b4488702d8074b4a3e", size = 1242749 }, + { url = "https://files.pythonhosted.org/packages/d8/d1/9bad3a0f2fa7b72f4e0cf1d267b00513092d20ef538c47f72823ae4f7656/srsly-2.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:8ac016ffaeac35bc010992b71bf8afdd39d458f201c8138d84cf78778a936e6c", size = 673783 }, + { url = "https://files.pythonhosted.org/packages/2a/ae/57d1d7af907e20c077e113e0e4976f87b82c0a415403d99284a262229dd0/srsly-2.5.3-cp314-cp314t-win_arm64.whl", hash = "sha256:d822083fe26ec6728bd8c273ac121fc4ab3864a0fdf0cf0ff3efb188fcd209ed", size = 650229 }, ] [[package]] @@ -8086,9 +8069,9 @@ dependencies = [ { name = "anyio" }, { name = "starlette" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/08/8f554b0e5bad3e4e880521a1686d96c05198471eed860b0eb89b57ea3636/sse_starlette-3.1.1.tar.gz", hash = "sha256:bffa531420c1793ab224f63648c059bcadc412bf9fdb1301ac8de1cf9a67b7fb", size = 24306, upload-time = "2025-12-26T15:22:53.836Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/08/8f554b0e5bad3e4e880521a1686d96c05198471eed860b0eb89b57ea3636/sse_starlette-3.1.1.tar.gz", hash = "sha256:bffa531420c1793ab224f63648c059bcadc412bf9fdb1301ac8de1cf9a67b7fb", size = 24306 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/31/4c281581a0f8de137b710a07f65518b34bcf333b201cfa06cfda9af05f8a/sse_starlette-3.1.1-py3-none-any.whl", hash = "sha256:bb38f71ae74cfd86b529907a9fda5632195dfa6ae120f214ea4c890c7ee9d436", size = 12442, upload-time = "2025-12-26T15:22:52.911Z" }, + { url = "https://files.pythonhosted.org/packages/e3/31/4c281581a0f8de137b710a07f65518b34bcf333b201cfa06cfda9af05f8a/sse_starlette-3.1.1-py3-none-any.whl", hash = "sha256:bb38f71ae74cfd86b529907a9fda5632195dfa6ae120f214ea4c890c7ee9d436", size = 12442 }, ] [[package]] @@ -8099,9 +8082,9 @@ dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, + { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033 }, ] [[package]] @@ -8115,7 +8098,7 @@ dependencies = [ { name = "twine" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/d3/c238124fbf2dbe5eda203f0a1c4cd6c210e27993ed9780c4c1bf2ab0efbe/static_ffmpeg-3.0-py3-none-any.whl", hash = "sha256:79d9067264cefbb05e6b847be7d6cb7410b7b25adce40178a787f0137567c89f", size = 7927, upload-time = "2026-01-16T21:17:10.643Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d3/c238124fbf2dbe5eda203f0a1c4cd6c210e27993ed9780c4c1bf2ab0efbe/static_ffmpeg-3.0-py3-none-any.whl", hash = "sha256:79d9067264cefbb05e6b847be7d6cb7410b7b25adce40178a787f0137567c89f", size = 7927 }, ] [[package]] @@ -8126,14 +8109,14 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/5a/0cdea4b7911b8012936c765544109da27c0728f6911ec7aefe9d59e7a4f9/stripe-15.0.0.tar.gz", hash = "sha256:0717cd9ba8e8193cef8b1c488ce27836754df496ab6fb75864096e0cdf15e52d", size = 1486873, upload-time = "2026-03-26T01:39:04.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/5a/0cdea4b7911b8012936c765544109da27c0728f6911ec7aefe9d59e7a4f9/stripe-15.0.0.tar.gz", hash = "sha256:0717cd9ba8e8193cef8b1c488ce27836754df496ab6fb75864096e0cdf15e52d", size = 1486873 } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/4a/4431c998c451cf07f8b4fed98f425b4aaf3d59cc4fb1e6f54d7713606688/stripe-15.0.0-py3-none-any.whl", hash = "sha256:434ec5267a7402a30b76786d159c18d0e138f89195969d6c56bea2e08d353be0", size = 2125454, upload-time = "2026-03-26T01:39:01.801Z" }, + { url = "https://files.pythonhosted.org/packages/03/4a/4431c998c451cf07f8b4fed98f425b4aaf3d59cc4fb1e6f54d7713606688/stripe-15.0.0-py3-none-any.whl", hash = "sha256:434ec5267a7402a30b76786d159c18d0e138f89195969d6c56bea2e08d353be0", size = 2125454 }, ] [[package]] name = "surf-new-backend" -version = "0.0.25" +version = "0.0.26" source = { editable = "." } dependencies = [ { name = "alembic" }, @@ -8335,18 +8318,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mpmath" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353 }, ] [[package]] name = "tabulate" version = "0.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754 } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814 }, ] [[package]] @@ -8358,18 +8341,18 @@ dependencies = [ { name = "requests" }, { name = "tiktoken" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/d1/197419d6133643848514e5e84e8f41886e825b73bf91ae235a1595c964f5/tavily_python-0.7.23.tar.gz", hash = "sha256:3b92232e0e29ab68898b765f281bb4f2c650b02210b64affbc48e15292e96161", size = 25968, upload-time = "2026-03-09T19:17:32.333Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/d1/197419d6133643848514e5e84e8f41886e825b73bf91ae235a1595c964f5/tavily_python-0.7.23.tar.gz", hash = "sha256:3b92232e0e29ab68898b765f281bb4f2c650b02210b64affbc48e15292e96161", size = 25968 } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/27/f9c6e9249367be0772fb754849e03cbbc6ad8d80a479bf30ea8811828b2e/tavily_python-0.7.23-py3-none-any.whl", hash = "sha256:52ef85c44b926bce3f257570cd32bc1bd4db54666acf3105617f27411a59e188", size = 19079, upload-time = "2026-03-09T19:17:29.593Z" }, + { url = "https://files.pythonhosted.org/packages/64/27/f9c6e9249367be0772fb754849e03cbbc6ad8d80a479bf30ea8811828b2e/tavily_python-0.7.23-py3-none-any.whl", hash = "sha256:52ef85c44b926bce3f257570cd32bc1bd4db54666acf3105617f27411a59e188", size = 19079 }, ] [[package]] name = "tenacity" version = "9.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926 }, ] [[package]] @@ -8390,41 +8373,41 @@ dependencies = [ { name = "srsly" }, { name = "wasabi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/13/46/76df95f2c327f9a9cef30c1523bf285627897097163584dcf5f77b2ebce2/thinc-8.3.13.tar.gz", hash = "sha256:68e658549fc1eb3ff92aed5147fcbb9c15d6e9cc0e623b4d0998d16522ffb4f9", size = 194640, upload-time = "2026-03-23T07:22:36.41Z" } +sdist = { url = "https://files.pythonhosted.org/packages/13/46/76df95f2c327f9a9cef30c1523bf285627897097163584dcf5f77b2ebce2/thinc-8.3.13.tar.gz", hash = "sha256:68e658549fc1eb3ff92aed5147fcbb9c15d6e9cc0e623b4d0998d16522ffb4f9", size = 194640 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/af/f7c1ebfe92eb5d27d7f2f3da67a11e2eb57bc30ab1553279af6dc65b65a8/thinc-8.3.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:77a41f66285321d20aaedaea1e87d7cd48dca6d2427bed1867ec7cba7109fc8d", size = 821097, upload-time = "2026-03-23T07:21:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/45/8f/69d7338575d98df85d0b54c0f5fc277dba72587fe9ab846ecdd12a998bcb/thinc-8.3.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3710d318b4e5460cf366a6f7b5ddbefb5d39dbd4cfa408222750fdc6c27c4411", size = 791932, upload-time = "2026-03-23T07:21:58.38Z" }, - { url = "https://files.pythonhosted.org/packages/4b/a5/21d010c81e81e1589e5ccb4950e521804d13726e541e87f644c51815673b/thinc-8.3.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5a08c87143a6d20177652dca1ec0dc815d88216d8fc62594a57e8bc45bf5ed49", size = 3854219, upload-time = "2026-03-23T07:21:59.819Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ff/6914bf370bd1d604d89e6dfb46b97d10cd9b00d42ff8c036283e92314a8c/thinc-8.3.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b5ec9ff313819e7d8667794a3559463fa89ff45aaa73e3fd8d6273b1e0d7a7f", size = 3903307, upload-time = "2026-03-23T07:22:01.652Z" }, - { url = "https://files.pythonhosted.org/packages/f3/3d/5572b47fa155fb3388c071515b74024fa17a6efd1df9406da378f0aa84ef/thinc-8.3.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5c9a48f2bc1e04f138240ed5f9b815a9141a5de26accd0f08fa0137fcefed258", size = 4836882, upload-time = "2026-03-23T07:22:03.565Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f0/a8d77c7bac089697c6df302cc3c936a1ab36a4720deae889e6f1dbcbd0eb/thinc-8.3.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:79a29a44d76bd02f5ac0624268c6e42b3576ae472c791a8ae9c2d813ae789b59", size = 5033398, upload-time = "2026-03-23T07:22:05.045Z" }, - { url = "https://files.pythonhosted.org/packages/21/82/5651bb1f904d04220fc7670035ada921bf0638e2cff6444d67c12887a968/thinc-8.3.13-cp312-cp312-win_amd64.whl", hash = "sha256:ed1dc709ac4f2f03b710457889e4e02f05de51bc8456980c241d0b28798bc7cb", size = 1721248, upload-time = "2026-03-23T07:22:06.749Z" }, - { url = "https://files.pythonhosted.org/packages/94/8d/683703de021ffbe46833d722b70f49ffbbca8e5bd6876256977555d92d7d/thinc-8.3.13-cp312-cp312-win_arm64.whl", hash = "sha256:c6a049703a6011c8fe26ee41af7e70272145594140d82f79bb23de619c6a6525", size = 1645777, upload-time = "2026-03-23T07:22:08.104Z" }, - { url = "https://files.pythonhosted.org/packages/af/b9/7b46942176df459d1804a9e77b0976f7c56f3abf3ec7485d0e5f836a0382/thinc-8.3.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2811dfd8d46d8b5d3b39051b23e64006b2994a5143b1978b436938018792af8", size = 817337, upload-time = "2026-03-23T07:22:09.538Z" }, - { url = "https://files.pythonhosted.org/packages/a7/79/53085a72cd8f4fc4e6e313d05ea5aa98e870684f4a0fb318a9875fc0a964/thinc-8.3.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5593e6300cb1ebe0c0e546e9c9fb49e7c2627a0aa688795cd4f995a8b820d2ec", size = 788120, upload-time = "2026-03-23T07:22:11.215Z" }, - { url = "https://files.pythonhosted.org/packages/9e/3e/d61b462b16da95ac6885f95bb395e672040ee594833e571a6edcffd234f5/thinc-8.3.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f697174d3fb474966ce50b430bbafa101a6d2f7ffb559dac4b5c59389ef72d22", size = 3844666, upload-time = "2026-03-23T07:22:12.67Z" }, - { url = "https://files.pythonhosted.org/packages/78/4c/898cc654bb123734c71ec5a425c02ca34439517d01ce1c95a6563295580e/thinc-8.3.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9c7c5c104737b414c8c4ec578e67d78b6c859afe25cbc0684402e721415bd7f", size = 3890658, upload-time = "2026-03-23T07:22:14.668Z" }, - { url = "https://files.pythonhosted.org/packages/cd/56/1abdbf0a4ad628e8a05d6516fe0745969649d805367a3dccad8ee872981b/thinc-8.3.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a99d0e242d1ccd23f9ae6bea7cd502f8626efa65c156b91d84581d0356696c3", size = 4819933, upload-time = "2026-03-23T07:22:16.85Z" }, - { url = "https://files.pythonhosted.org/packages/f1/22/b84dbdc6be5055bbdb2a7352e2c393f67e8593c137f1b83c82bf1e062b6e/thinc-8.3.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e676edd21a747afbe3e6b9f3fca8b962e36d146ded03b070cb0c28e2dfbe9499", size = 5018099, upload-time = "2026-03-23T07:22:18.356Z" }, - { url = "https://files.pythonhosted.org/packages/0f/a8/763cd7ba949334c9d2cddc92dadb68b344cb9546dc01b8d4a733dcaa16c1/thinc-8.3.13-cp313-cp313-win_amd64.whl", hash = "sha256:8ad40307f20e83f77af28ff5c6be0b86af7a8b251d1231c545508d2763157d8f", size = 1720309, upload-time = "2026-03-23T07:22:19.81Z" }, - { url = "https://files.pythonhosted.org/packages/f5/15/a11f7bb3cbc97dfecf32a90552f5a8f8a5c99316a99c6c17bdabf5baf256/thinc-8.3.13-cp313-cp313-win_arm64.whl", hash = "sha256:723949cab11d1925c15447928513a718276316cec6e0de28337cca0a62be0521", size = 1644606, upload-time = "2026-03-23T07:22:21.339Z" }, - { url = "https://files.pythonhosted.org/packages/80/40/f4937d113912c6d669ffe982356ab29dcb6c7fe3be926a15981dbbb6a91c/thinc-8.3.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7badb0be4825535e6362c19e8a41872b65409e9da46d3453a391b843a0720865", size = 817024, upload-time = "2026-03-23T07:22:23.005Z" }, - { url = "https://files.pythonhosted.org/packages/d2/00/4d4ed1a11ba2920b85a03a0683b16d97dc5beb2e78078dbf0e13e43bcea7/thinc-8.3.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:565300b7e13de799e5abff00d445f537e9256cf7da4dcb0d0f005fc16748a29e", size = 792096, upload-time = "2026-03-23T07:22:24.349Z" }, - { url = "https://files.pythonhosted.org/packages/44/5d/dc33d6932be8721af2ef76b4a3a6e8020648630eabae61fb916d2a861d1d/thinc-8.3.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c17cef1900a1aba7e1487493d16b8aa0a8633116f1b2a51c6649a4000697f17b", size = 3842215, upload-time = "2026-03-23T07:22:25.836Z" }, - { url = "https://files.pythonhosted.org/packages/af/bc/a6d37d8dadc2c5b524f51192413481160c42c9dd6105e8d5551531623225/thinc-8.3.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f4f26d1eec9b2a6a8f2e0298a5515d13eb06d70730d0d9e1040bb329e12bf3fb", size = 3849253, upload-time = "2026-03-23T07:22:27.845Z" }, - { url = "https://files.pythonhosted.org/packages/7a/59/ce9c7067f1dfe5985875927de9cf7a79f9dae3e69487fd650dfba558029d/thinc-8.3.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a61a31fd0ce3c2771cf4901ba6df70e774ffe32febf1024c5b43d63575cd58fe", size = 4831163, upload-time = "2026-03-23T07:22:29.395Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a8/f57819347fc4d8bef2204d15fcbb9d7dff2d6cdd5f83d5ed91456ddacc55/thinc-8.3.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba8119daf84a12259ae4d251d36426417bafa0b34108890b4b7e2b50966bd990", size = 4986051, upload-time = "2026-03-23T07:22:30.933Z" }, - { url = "https://files.pythonhosted.org/packages/05/ef/a82214bb7c7c1e2d92b69e1a7654be90cfab180082c6108e45a98af2422c/thinc-8.3.13-cp314-cp314-win_amd64.whl", hash = "sha256:433e3826e018da489f1a8068e6de677f6eff3cc93991a599d90f12cd1bc26cdc", size = 1740382, upload-time = "2026-03-23T07:22:32.869Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ef/1648fda54e9689058335ff54f650a7a314db2a42e21af1b83949b2dc748e/thinc-8.3.13-cp314-cp314-win_arm64.whl", hash = "sha256:11754fada9ad5ba2e02d5f3f234f940e24015b82333db58372f4a6aedad9b43f", size = 1667687, upload-time = "2026-03-23T07:22:34.967Z" }, + { url = "https://files.pythonhosted.org/packages/3e/af/f7c1ebfe92eb5d27d7f2f3da67a11e2eb57bc30ab1553279af6dc65b65a8/thinc-8.3.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:77a41f66285321d20aaedaea1e87d7cd48dca6d2427bed1867ec7cba7109fc8d", size = 821097 }, + { url = "https://files.pythonhosted.org/packages/45/8f/69d7338575d98df85d0b54c0f5fc277dba72587fe9ab846ecdd12a998bcb/thinc-8.3.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3710d318b4e5460cf366a6f7b5ddbefb5d39dbd4cfa408222750fdc6c27c4411", size = 791932 }, + { url = "https://files.pythonhosted.org/packages/4b/a5/21d010c81e81e1589e5ccb4950e521804d13726e541e87f644c51815673b/thinc-8.3.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5a08c87143a6d20177652dca1ec0dc815d88216d8fc62594a57e8bc45bf5ed49", size = 3854219 }, + { url = "https://files.pythonhosted.org/packages/f9/ff/6914bf370bd1d604d89e6dfb46b97d10cd9b00d42ff8c036283e92314a8c/thinc-8.3.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b5ec9ff313819e7d8667794a3559463fa89ff45aaa73e3fd8d6273b1e0d7a7f", size = 3903307 }, + { url = "https://files.pythonhosted.org/packages/f3/3d/5572b47fa155fb3388c071515b74024fa17a6efd1df9406da378f0aa84ef/thinc-8.3.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5c9a48f2bc1e04f138240ed5f9b815a9141a5de26accd0f08fa0137fcefed258", size = 4836882 }, + { url = "https://files.pythonhosted.org/packages/f0/f0/a8d77c7bac089697c6df302cc3c936a1ab36a4720deae889e6f1dbcbd0eb/thinc-8.3.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:79a29a44d76bd02f5ac0624268c6e42b3576ae472c791a8ae9c2d813ae789b59", size = 5033398 }, + { url = "https://files.pythonhosted.org/packages/21/82/5651bb1f904d04220fc7670035ada921bf0638e2cff6444d67c12887a968/thinc-8.3.13-cp312-cp312-win_amd64.whl", hash = "sha256:ed1dc709ac4f2f03b710457889e4e02f05de51bc8456980c241d0b28798bc7cb", size = 1721248 }, + { url = "https://files.pythonhosted.org/packages/94/8d/683703de021ffbe46833d722b70f49ffbbca8e5bd6876256977555d92d7d/thinc-8.3.13-cp312-cp312-win_arm64.whl", hash = "sha256:c6a049703a6011c8fe26ee41af7e70272145594140d82f79bb23de619c6a6525", size = 1645777 }, + { url = "https://files.pythonhosted.org/packages/af/b9/7b46942176df459d1804a9e77b0976f7c56f3abf3ec7485d0e5f836a0382/thinc-8.3.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2811dfd8d46d8b5d3b39051b23e64006b2994a5143b1978b436938018792af8", size = 817337 }, + { url = "https://files.pythonhosted.org/packages/a7/79/53085a72cd8f4fc4e6e313d05ea5aa98e870684f4a0fb318a9875fc0a964/thinc-8.3.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5593e6300cb1ebe0c0e546e9c9fb49e7c2627a0aa688795cd4f995a8b820d2ec", size = 788120 }, + { url = "https://files.pythonhosted.org/packages/9e/3e/d61b462b16da95ac6885f95bb395e672040ee594833e571a6edcffd234f5/thinc-8.3.13-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f697174d3fb474966ce50b430bbafa101a6d2f7ffb559dac4b5c59389ef72d22", size = 3844666 }, + { url = "https://files.pythonhosted.org/packages/78/4c/898cc654bb123734c71ec5a425c02ca34439517d01ce1c95a6563295580e/thinc-8.3.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9c7c5c104737b414c8c4ec578e67d78b6c859afe25cbc0684402e721415bd7f", size = 3890658 }, + { url = "https://files.pythonhosted.org/packages/cd/56/1abdbf0a4ad628e8a05d6516fe0745969649d805367a3dccad8ee872981b/thinc-8.3.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a99d0e242d1ccd23f9ae6bea7cd502f8626efa65c156b91d84581d0356696c3", size = 4819933 }, + { url = "https://files.pythonhosted.org/packages/f1/22/b84dbdc6be5055bbdb2a7352e2c393f67e8593c137f1b83c82bf1e062b6e/thinc-8.3.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e676edd21a747afbe3e6b9f3fca8b962e36d146ded03b070cb0c28e2dfbe9499", size = 5018099 }, + { url = "https://files.pythonhosted.org/packages/0f/a8/763cd7ba949334c9d2cddc92dadb68b344cb9546dc01b8d4a733dcaa16c1/thinc-8.3.13-cp313-cp313-win_amd64.whl", hash = "sha256:8ad40307f20e83f77af28ff5c6be0b86af7a8b251d1231c545508d2763157d8f", size = 1720309 }, + { url = "https://files.pythonhosted.org/packages/f5/15/a11f7bb3cbc97dfecf32a90552f5a8f8a5c99316a99c6c17bdabf5baf256/thinc-8.3.13-cp313-cp313-win_arm64.whl", hash = "sha256:723949cab11d1925c15447928513a718276316cec6e0de28337cca0a62be0521", size = 1644606 }, + { url = "https://files.pythonhosted.org/packages/80/40/f4937d113912c6d669ffe982356ab29dcb6c7fe3be926a15981dbbb6a91c/thinc-8.3.13-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7badb0be4825535e6362c19e8a41872b65409e9da46d3453a391b843a0720865", size = 817024 }, + { url = "https://files.pythonhosted.org/packages/d2/00/4d4ed1a11ba2920b85a03a0683b16d97dc5beb2e78078dbf0e13e43bcea7/thinc-8.3.13-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:565300b7e13de799e5abff00d445f537e9256cf7da4dcb0d0f005fc16748a29e", size = 792096 }, + { url = "https://files.pythonhosted.org/packages/44/5d/dc33d6932be8721af2ef76b4a3a6e8020648630eabae61fb916d2a861d1d/thinc-8.3.13-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c17cef1900a1aba7e1487493d16b8aa0a8633116f1b2a51c6649a4000697f17b", size = 3842215 }, + { url = "https://files.pythonhosted.org/packages/af/bc/a6d37d8dadc2c5b524f51192413481160c42c9dd6105e8d5551531623225/thinc-8.3.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f4f26d1eec9b2a6a8f2e0298a5515d13eb06d70730d0d9e1040bb329e12bf3fb", size = 3849253 }, + { url = "https://files.pythonhosted.org/packages/7a/59/ce9c7067f1dfe5985875927de9cf7a79f9dae3e69487fd650dfba558029d/thinc-8.3.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a61a31fd0ce3c2771cf4901ba6df70e774ffe32febf1024c5b43d63575cd58fe", size = 4831163 }, + { url = "https://files.pythonhosted.org/packages/4f/a8/f57819347fc4d8bef2204d15fcbb9d7dff2d6cdd5f83d5ed91456ddacc55/thinc-8.3.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba8119daf84a12259ae4d251d36426417bafa0b34108890b4b7e2b50966bd990", size = 4986051 }, + { url = "https://files.pythonhosted.org/packages/05/ef/a82214bb7c7c1e2d92b69e1a7654be90cfab180082c6108e45a98af2422c/thinc-8.3.13-cp314-cp314-win_amd64.whl", hash = "sha256:433e3826e018da489f1a8068e6de677f6eff3cc93991a599d90f12cd1bc26cdc", size = 1740382 }, + { url = "https://files.pythonhosted.org/packages/9f/ef/1648fda54e9689058335ff54f650a7a314db2a42e21af1b83949b2dc748e/thinc-8.3.13-cp314-cp314-win_arm64.whl", hash = "sha256:11754fada9ad5ba2e02d5f3f234f940e24015b82333db58372f4a6aedad9b43f", size = 1667687 }, ] [[package]] name = "threadpoolctl" version = "3.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274 } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638 }, ] [[package]] @@ -8435,43 +8418,43 @@ dependencies = [ { name = "regex" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806, upload-time = "2025-10-06T20:22:45.419Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", size = 1050728, upload-time = "2025-10-06T20:21:52.756Z" }, - { url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", size = 994049, upload-time = "2025-10-06T20:21:53.782Z" }, - { url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", size = 1129008, upload-time = "2025-10-06T20:21:54.832Z" }, - { url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", size = 1152665, upload-time = "2025-10-06T20:21:56.129Z" }, - { url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", size = 1194230, upload-time = "2025-10-06T20:21:57.546Z" }, - { url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", size = 1254688, upload-time = "2025-10-06T20:21:58.619Z" }, - { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694, upload-time = "2025-10-06T20:21:59.876Z" }, - { url = "https://files.pythonhosted.org/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", size = 1050802, upload-time = "2025-10-06T20:22:00.96Z" }, - { url = "https://files.pythonhosted.org/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", size = 993995, upload-time = "2025-10-06T20:22:02.788Z" }, - { url = "https://files.pythonhosted.org/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", size = 1128948, upload-time = "2025-10-06T20:22:03.814Z" }, - { url = "https://files.pythonhosted.org/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", size = 1151986, upload-time = "2025-10-06T20:22:05.173Z" }, - { url = "https://files.pythonhosted.org/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", size = 1194222, upload-time = "2025-10-06T20:22:06.265Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", size = 1255097, upload-time = "2025-10-06T20:22:07.403Z" }, - { url = "https://files.pythonhosted.org/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", size = 879117, upload-time = "2025-10-06T20:22:08.418Z" }, - { url = "https://files.pythonhosted.org/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", size = 1050309, upload-time = "2025-10-06T20:22:10.939Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", size = 993712, upload-time = "2025-10-06T20:22:12.115Z" }, - { url = "https://files.pythonhosted.org/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", size = 1128725, upload-time = "2025-10-06T20:22:13.541Z" }, - { url = "https://files.pythonhosted.org/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", size = 1151875, upload-time = "2025-10-06T20:22:14.559Z" }, - { url = "https://files.pythonhosted.org/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", size = 1194451, upload-time = "2025-10-06T20:22:15.545Z" }, - { url = "https://files.pythonhosted.org/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", size = 1253794, upload-time = "2025-10-06T20:22:16.624Z" }, - { url = "https://files.pythonhosted.org/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", size = 878777, upload-time = "2025-10-06T20:22:18.036Z" }, - { url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188, upload-time = "2025-10-06T20:22:19.563Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978, upload-time = "2025-10-06T20:22:20.702Z" }, - { url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271, upload-time = "2025-10-06T20:22:22.06Z" }, - { url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216, upload-time = "2025-10-06T20:22:23.085Z" }, - { url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860, upload-time = "2025-10-06T20:22:24.602Z" }, - { url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567, upload-time = "2025-10-06T20:22:25.671Z" }, - { url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067, upload-time = "2025-10-06T20:22:26.753Z" }, - { url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473, upload-time = "2025-10-06T20:22:27.775Z" }, - { url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855, upload-time = "2025-10-06T20:22:28.799Z" }, - { url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022, upload-time = "2025-10-06T20:22:29.981Z" }, - { url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736, upload-time = "2025-10-06T20:22:30.996Z" }, - { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908, upload-time = "2025-10-06T20:22:32.073Z" }, - { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" }, - { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" }, + { url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", size = 1050728 }, + { url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", size = 994049 }, + { url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", size = 1129008 }, + { url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", size = 1152665 }, + { url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", size = 1194230 }, + { url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", size = 1254688 }, + { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694 }, + { url = "https://files.pythonhosted.org/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", size = 1050802 }, + { url = "https://files.pythonhosted.org/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", size = 993995 }, + { url = "https://files.pythonhosted.org/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", size = 1128948 }, + { url = "https://files.pythonhosted.org/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", size = 1151986 }, + { url = "https://files.pythonhosted.org/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", size = 1194222 }, + { url = "https://files.pythonhosted.org/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", size = 1255097 }, + { url = "https://files.pythonhosted.org/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", size = 879117 }, + { url = "https://files.pythonhosted.org/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", size = 1050309 }, + { url = "https://files.pythonhosted.org/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", size = 993712 }, + { url = "https://files.pythonhosted.org/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", size = 1128725 }, + { url = "https://files.pythonhosted.org/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", size = 1151875 }, + { url = "https://files.pythonhosted.org/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", size = 1194451 }, + { url = "https://files.pythonhosted.org/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", size = 1253794 }, + { url = "https://files.pythonhosted.org/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", size = 878777 }, + { url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188 }, + { url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978 }, + { url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271 }, + { url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216 }, + { url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860 }, + { url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567 }, + { url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067 }, + { url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473 }, + { url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855 }, + { url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022 }, + { url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736 }, + { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908 }, + { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706 }, + { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667 }, ] [[package]] @@ -8485,27 +8468,27 @@ dependencies = [ { name = "torch", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, { name = "torchvision", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7b/1e/e924b3b2326a856aaf68586f9c52a5fc81ef45715eca408393b68c597e0e/timm-1.0.26.tar.gz", hash = "sha256:f66f082f2f381cf68431c22714c8b70f723837fa2a185b155961eab90f2d5b10", size = 2419859, upload-time = "2026-03-23T18:12:10.272Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/1e/e924b3b2326a856aaf68586f9c52a5fc81ef45715eca408393b68c597e0e/timm-1.0.26.tar.gz", hash = "sha256:f66f082f2f381cf68431c22714c8b70f723837fa2a185b155961eab90f2d5b10", size = 2419859 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/e9/bebf3d50e3fc847378988235f87c37ad3ac26d386041ab915d15e92025cd/timm-1.0.26-py3-none-any.whl", hash = "sha256:985c330de5ccc3a2aa0224eb7272e6a336084702390bb7e3801f3c91603d3683", size = 2568766, upload-time = "2026-03-23T18:12:08.062Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e9/bebf3d50e3fc847378988235f87c37ad3ac26d386041ab915d15e92025cd/timm-1.0.26-py3-none-any.whl", hash = "sha256:985c330de5ccc3a2aa0224eb7272e6a336084702390bb7e3801f3c91603d3683", size = 2568766 }, ] [[package]] name = "tinytag" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/59/8a8cb2331e2602b53e4dc06960f57d1387a2b18e7efd24e5f9cb60ea4925/tinytag-2.2.1.tar.gz", hash = "sha256:e6d06610ebe7cd66fd07be2d3b9495914ab32654a5e47657bb8cd44c2484523c", size = 38214, upload-time = "2026-03-15T18:48:01.11Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/59/8a8cb2331e2602b53e4dc06960f57d1387a2b18e7efd24e5f9cb60ea4925/tinytag-2.2.1.tar.gz", hash = "sha256:e6d06610ebe7cd66fd07be2d3b9495914ab32654a5e47657bb8cd44c2484523c", size = 38214 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/34/d50e338631baaf65ec5396e70085e5de0b52b24b28db1ffbc1c6e82190dc/tinytag-2.2.1-py3-none-any.whl", hash = "sha256:ed8b1e6d25367937e3321e054f4974f9abfde1a3e0a538824c87da377130c2b6", size = 32927, upload-time = "2026-03-15T18:47:59.613Z" }, + { url = "https://files.pythonhosted.org/packages/ce/34/d50e338631baaf65ec5396e70085e5de0b52b24b28db1ffbc1c6e82190dc/tinytag-2.2.1-py3-none-any.whl", hash = "sha256:ed8b1e6d25367937e3321e054f4974f9abfde1a3e0a538824c87da377130c2b6", size = 32927 }, ] [[package]] name = "tld" version = "0.13.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/5d/76b4383ac4e5b5e254e50c09807b3e13820bed6d6c11cd540264988d6802/tld-0.13.2.tar.gz", hash = "sha256:d983fa92b9d717400742fca844e29d5e18271079c7bcfabf66d01b39b4a14345", size = 467175, upload-time = "2026-03-06T23:50:34.498Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5d/76b4383ac4e5b5e254e50c09807b3e13820bed6d6c11cd540264988d6802/tld-0.13.2.tar.gz", hash = "sha256:d983fa92b9d717400742fca844e29d5e18271079c7bcfabf66d01b39b4a14345", size = 467175 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/90/39a85a4b63c84213e78b3c17d22e1bf45328acf8ebb33ef93be30d0a3911/tld-0.13.2-py2.py3-none-any.whl", hash = "sha256:9b8fdbdb880e7ba65b216a4937f2c94c49a7226723783d5838fc958ac76f4e0c", size = 296743, upload-time = "2026-03-06T23:50:32.465Z" }, + { url = "https://files.pythonhosted.org/packages/9e/90/39a85a4b63c84213e78b3c17d22e1bf45328acf8ebb33ef93be30d0a3911/tld-0.13.2-py2.py3-none-any.whl", hash = "sha256:9b8fdbdb880e7ba65b216a4937f2c94c49a7226723783d5838fc958ac76f4e0c", size = 296743 }, ] [[package]] @@ -8515,32 +8498,32 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115 } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, - { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, - { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, - { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, - { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, - { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, - { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, - { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, - { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, - { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, - { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, - { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, - { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, - { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, + { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275 }, + { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472 }, + { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736 }, + { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835 }, + { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673 }, + { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818 }, + { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195 }, + { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982 }, + { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245 }, + { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069 }, + { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263 }, + { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429 }, + { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363 }, + { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786 }, + { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133 }, ] [[package]] name = "toml" version = "0.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 }, ] [[package]] @@ -8564,26 +8547,26 @@ dependencies = [ { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/8b/69e3008d78e5cee2b30183340cc425081b78afc5eff3d080daab0adda9aa/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b5866312ee6e52ea625cd211dcb97d6a2cdc1131a5f15cc0d87eec948f6dd34", size = 80606338, upload-time = "2026-03-23T18:11:34.781Z" }, - { url = "https://files.pythonhosted.org/packages/13/16/42e5915ebe4868caa6bac83a8ed59db57f12e9a61b7d749d584776ed53d5/torch-2.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f99924682ef0aa6a4ab3b1b76f40dc6e273fca09f367d15a524266db100a723f", size = 419731115, upload-time = "2026-03-23T18:11:06.944Z" }, - { url = "https://files.pythonhosted.org/packages/1a/c9/82638ef24d7877510f83baf821f5619a61b45568ce21c0a87a91576510aa/torch-2.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0f68f4ac6d95d12e896c3b7a912b5871619542ec54d3649cf48cc1edd4dd2756", size = 530712279, upload-time = "2026-03-23T18:10:31.481Z" }, - { url = "https://files.pythonhosted.org/packages/1c/ff/6756f1c7ee302f6d202120e0f4f05b432b839908f9071157302cedfc5232/torch-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbf39280699d1b869f55eac536deceaa1b60bd6788ba74f399cc67e60a5fab10", size = 114556047, upload-time = "2026-03-23T18:10:55.931Z" }, - { url = "https://files.pythonhosted.org/packages/87/89/5ea6722763acee56b045435fb84258db7375c48165ec8be7880ab2b281c5/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6debd97ccd3205bbb37eb806a9d8219e1139d15419982c09e23ef7d4369d18", size = 80606801, upload-time = "2026-03-23T18:10:18.649Z" }, - { url = "https://files.pythonhosted.org/packages/32/d1/8ed2173589cbfe744ed54e5a73efc107c0085ba5777ee93a5f4c1ab90553/torch-2.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:63a68fa59de8f87acc7e85a5478bb2dddbb3392b7593ec3e78827c793c4b73fd", size = 419732382, upload-time = "2026-03-23T18:08:30.835Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e1/b73f7c575a4b8f87a5928f50a1e35416b5e27295d8be9397d5293e7e8d4c/torch-2.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cc89b9b173d9adfab59fd227f0ab5e5516d9a52b658ae41d64e59d2e55a418db", size = 530711509, upload-time = "2026-03-23T18:08:47.213Z" }, - { url = "https://files.pythonhosted.org/packages/66/82/3e3fcdd388fbe54e29fd3f991f36846ff4ac90b0d0181e9c8f7236565f82/torch-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dda3b3f52d121063a731ddb835f010dc137b920d7fec2778e52f60d8e4bf0cd", size = 114555842, upload-time = "2026-03-23T18:09:52.111Z" }, - { url = "https://files.pythonhosted.org/packages/db/38/8ac78069621b8c2b4979c2f96dc8409ef5e9c4189f6aac629189a78677ca/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8b394322f49af4362d4f80e424bcaca7efcd049619af03a4cf4501520bdf0fb4", size = 80959574, upload-time = "2026-03-23T18:10:14.214Z" }, - { url = "https://files.pythonhosted.org/packages/6d/6c/56bfb37073e7136e6dd86bfc6af7339946dd684e0ecf2155ac0eee687ae1/torch-2.11.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2658f34ce7e2dabf4ec73b45e2ca68aedad7a5be87ea756ad656eaf32bf1e1ea", size = 419732324, upload-time = "2026-03-23T18:09:36.604Z" }, - { url = "https://files.pythonhosted.org/packages/07/f4/1b666b6d61d3394cca306ea543ed03a64aad0a201b6cd159f1d41010aeb1/torch-2.11.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:98bb213c3084cfe176302949bdc360074b18a9da7ab59ef2edc9d9f742504778", size = 530596026, upload-time = "2026-03-23T18:09:20.842Z" }, - { url = "https://files.pythonhosted.org/packages/48/6b/30d1459fa7e4b67e9e3fe1685ca1d8bb4ce7c62ef436c3a615963c6c866c/torch-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a97b94bbf62992949b4730c6cd2cc9aee7b335921ee8dc207d930f2ed09ae2db", size = 114793702, upload-time = "2026-03-23T18:09:47.304Z" }, - { url = "https://files.pythonhosted.org/packages/26/0d/8603382f61abd0db35841148ddc1ffd607bf3100b11c6e1dab6d2fc44e72/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01018087326984a33b64e04c8cb5c2795f9120e0d775ada1f6638840227b04d7", size = 80573442, upload-time = "2026-03-23T18:09:10.117Z" }, - { url = "https://files.pythonhosted.org/packages/c7/86/7cd7c66cb9cec6be330fff36db5bd0eef386d80c031b581ec81be1d4b26c/torch-2.11.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:2bb3cc54bd0dea126b0060bb1ec9de0f9c7f7342d93d436646516b0330cd5be7", size = 419749385, upload-time = "2026-03-23T18:07:33.77Z" }, - { url = "https://files.pythonhosted.org/packages/47/e8/b98ca2d39b2e0e4730c0ee52537e488e7008025bc77ca89552ff91021f7c/torch-2.11.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4dc8b3809469b6c30b411bb8c4cad3828efd26236153d9beb6a3ec500f211a60", size = 530716756, upload-time = "2026-03-23T18:07:50.02Z" }, - { url = "https://files.pythonhosted.org/packages/78/88/d4a4cda8362f8a30d1ed428564878c3cafb0d87971fbd3947d4c84552095/torch-2.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b4e811728bd0cc58fb2b0948fe939a1ee2bf1422f6025be2fca4c7bd9d79718", size = 114552300, upload-time = "2026-03-23T18:09:05.617Z" }, - { url = "https://files.pythonhosted.org/packages/bf/46/4419098ed6d801750f26567b478fc185c3432e11e2cad712bc6b4c2ab0d0/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8245477871c3700d4370352ffec94b103cfcb737229445cf9946cddb7b2ca7cd", size = 80959460, upload-time = "2026-03-23T18:09:00.818Z" }, - { url = "https://files.pythonhosted.org/packages/fd/66/54a56a4a6ceaffb567231994a9745821d3af922a854ed33b0b3a278e0a99/torch-2.11.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:ab9a8482f475f9ba20e12db84b0e55e2f58784bdca43a854a6ccd3fd4b9f75e6", size = 419735835, upload-time = "2026-03-23T18:07:18.974Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e7/0b6665f533aa9e337662dc190425abc0af1fe3234088f4454c52393ded61/torch-2.11.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:563ed3d25542d7e7bbc5b235ccfacfeb97fb470c7fee257eae599adb8005c8a2", size = 530613405, upload-time = "2026-03-23T18:08:07.014Z" }, - { url = "https://files.pythonhosted.org/packages/cf/bf/c8d12a2c86dbfd7f40fb2f56fbf5a505ccf2d9ce131eb559dfc7c51e1a04/torch-2.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b2a43985ff5ef6ddd923bbcf99943e5f58059805787c5c9a2622bf05ca2965b0", size = 114792991, upload-time = "2026-03-23T18:08:19.216Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8b/69e3008d78e5cee2b30183340cc425081b78afc5eff3d080daab0adda9aa/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b5866312ee6e52ea625cd211dcb97d6a2cdc1131a5f15cc0d87eec948f6dd34", size = 80606338 }, + { url = "https://files.pythonhosted.org/packages/13/16/42e5915ebe4868caa6bac83a8ed59db57f12e9a61b7d749d584776ed53d5/torch-2.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f99924682ef0aa6a4ab3b1b76f40dc6e273fca09f367d15a524266db100a723f", size = 419731115 }, + { url = "https://files.pythonhosted.org/packages/1a/c9/82638ef24d7877510f83baf821f5619a61b45568ce21c0a87a91576510aa/torch-2.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0f68f4ac6d95d12e896c3b7a912b5871619542ec54d3649cf48cc1edd4dd2756", size = 530712279 }, + { url = "https://files.pythonhosted.org/packages/1c/ff/6756f1c7ee302f6d202120e0f4f05b432b839908f9071157302cedfc5232/torch-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbf39280699d1b869f55eac536deceaa1b60bd6788ba74f399cc67e60a5fab10", size = 114556047 }, + { url = "https://files.pythonhosted.org/packages/87/89/5ea6722763acee56b045435fb84258db7375c48165ec8be7880ab2b281c5/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6debd97ccd3205bbb37eb806a9d8219e1139d15419982c09e23ef7d4369d18", size = 80606801 }, + { url = "https://files.pythonhosted.org/packages/32/d1/8ed2173589cbfe744ed54e5a73efc107c0085ba5777ee93a5f4c1ab90553/torch-2.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:63a68fa59de8f87acc7e85a5478bb2dddbb3392b7593ec3e78827c793c4b73fd", size = 419732382 }, + { url = "https://files.pythonhosted.org/packages/3d/e1/b73f7c575a4b8f87a5928f50a1e35416b5e27295d8be9397d5293e7e8d4c/torch-2.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cc89b9b173d9adfab59fd227f0ab5e5516d9a52b658ae41d64e59d2e55a418db", size = 530711509 }, + { url = "https://files.pythonhosted.org/packages/66/82/3e3fcdd388fbe54e29fd3f991f36846ff4ac90b0d0181e9c8f7236565f82/torch-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dda3b3f52d121063a731ddb835f010dc137b920d7fec2778e52f60d8e4bf0cd", size = 114555842 }, + { url = "https://files.pythonhosted.org/packages/db/38/8ac78069621b8c2b4979c2f96dc8409ef5e9c4189f6aac629189a78677ca/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8b394322f49af4362d4f80e424bcaca7efcd049619af03a4cf4501520bdf0fb4", size = 80959574 }, + { url = "https://files.pythonhosted.org/packages/6d/6c/56bfb37073e7136e6dd86bfc6af7339946dd684e0ecf2155ac0eee687ae1/torch-2.11.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2658f34ce7e2dabf4ec73b45e2ca68aedad7a5be87ea756ad656eaf32bf1e1ea", size = 419732324 }, + { url = "https://files.pythonhosted.org/packages/07/f4/1b666b6d61d3394cca306ea543ed03a64aad0a201b6cd159f1d41010aeb1/torch-2.11.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:98bb213c3084cfe176302949bdc360074b18a9da7ab59ef2edc9d9f742504778", size = 530596026 }, + { url = "https://files.pythonhosted.org/packages/48/6b/30d1459fa7e4b67e9e3fe1685ca1d8bb4ce7c62ef436c3a615963c6c866c/torch-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a97b94bbf62992949b4730c6cd2cc9aee7b335921ee8dc207d930f2ed09ae2db", size = 114793702 }, + { url = "https://files.pythonhosted.org/packages/26/0d/8603382f61abd0db35841148ddc1ffd607bf3100b11c6e1dab6d2fc44e72/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01018087326984a33b64e04c8cb5c2795f9120e0d775ada1f6638840227b04d7", size = 80573442 }, + { url = "https://files.pythonhosted.org/packages/c7/86/7cd7c66cb9cec6be330fff36db5bd0eef386d80c031b581ec81be1d4b26c/torch-2.11.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:2bb3cc54bd0dea126b0060bb1ec9de0f9c7f7342d93d436646516b0330cd5be7", size = 419749385 }, + { url = "https://files.pythonhosted.org/packages/47/e8/b98ca2d39b2e0e4730c0ee52537e488e7008025bc77ca89552ff91021f7c/torch-2.11.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4dc8b3809469b6c30b411bb8c4cad3828efd26236153d9beb6a3ec500f211a60", size = 530716756 }, + { url = "https://files.pythonhosted.org/packages/78/88/d4a4cda8362f8a30d1ed428564878c3cafb0d87971fbd3947d4c84552095/torch-2.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b4e811728bd0cc58fb2b0948fe939a1ee2bf1422f6025be2fca4c7bd9d79718", size = 114552300 }, + { url = "https://files.pythonhosted.org/packages/bf/46/4419098ed6d801750f26567b478fc185c3432e11e2cad712bc6b4c2ab0d0/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8245477871c3700d4370352ffec94b103cfcb737229445cf9946cddb7b2ca7cd", size = 80959460 }, + { url = "https://files.pythonhosted.org/packages/fd/66/54a56a4a6ceaffb567231994a9745821d3af922a854ed33b0b3a278e0a99/torch-2.11.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:ab9a8482f475f9ba20e12db84b0e55e2f58784bdca43a854a6ccd3fd4b9f75e6", size = 419735835 }, + { url = "https://files.pythonhosted.org/packages/b1/e7/0b6665f533aa9e337662dc190425abc0af1fe3234088f4454c52393ded61/torch-2.11.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:563ed3d25542d7e7bbc5b235ccfacfeb97fb470c7fee257eae599adb8005c8a2", size = 530613405 }, + { url = "https://files.pythonhosted.org/packages/cf/bf/c8d12a2c86dbfd7f40fb2f56fbf5a505ccf2d9ce131eb559dfc7c51e1a04/torch-2.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b2a43985ff5ef6ddd923bbcf99943e5f58059805787c5c9a2622bf05ca2965b0", size = 114792991 }, ] [[package]] @@ -8596,43 +8579,43 @@ dependencies = [ { name = "torch" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/e7/56b47cc3b132aea90ccce22bcb8975dec688b002150012acc842846039d0/torchvision-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c409e1c3fdebec7a3834465086dbda8bf7680eff79abf7fd2f10c6b59520a7a4", size = 1863502, upload-time = "2026-03-23T18:12:57.326Z" }, - { url = "https://files.pythonhosted.org/packages/f4/ec/5c31c92c08b65662fe9604a4067ae8232582805949f11ddc042cebe818ed/torchvision-0.26.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:406557718e62fdf10f5706e88d8a5ec000f872da913bf629aab9297622585547", size = 7767944, upload-time = "2026-03-23T18:12:42.805Z" }, - { url = "https://files.pythonhosted.org/packages/f5/d8/cb6ccda1a1f35a6597645818641701207b3e8e13553e75fce5d86bac74b2/torchvision-0.26.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d61a5abb6b42a0c0c311996c2ac4b83a94418a97182c83b055a2a4ae985e05aa", size = 7522205, upload-time = "2026-03-23T18:12:54.654Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a9/c272623a0f735c35f0f6cd6dc74784d4f970e800cf063bb76687895a2ab9/torchvision-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:7993c01648e7c61d191b018e84d38fe0825c8fcb2720cd0f37caf7ba14404aa1", size = 4255155, upload-time = "2026-03-23T18:12:32.652Z" }, - { url = "https://files.pythonhosted.org/packages/da/80/0762f77f53605d10c9477be39bb47722cc8e383bbbc2531471ce0e396c07/torchvision-0.26.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5d63dd43162691258b1b3529b9041bac7d54caa37eae0925f997108268cbf7c4", size = 1860809, upload-time = "2026-03-23T18:12:47.629Z" }, - { url = "https://files.pythonhosted.org/packages/e6/81/0b3e58d1478c660a5af4268713486b2df7203f35abd9195fea87348a5178/torchvision-0.26.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a39c7a26538c41fda453f9a9692b5ff9b35a5437db1d94f3027f6f509c160eac", size = 7727494, upload-time = "2026-03-23T18:12:46.062Z" }, - { url = "https://files.pythonhosted.org/packages/b6/dc/d9ab5d29115aa05e12e30f1397a3eeae1d88a511241dc3bce48dc4342675/torchvision-0.26.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:b7e6213620bbf97742e5f79832f9e9d769e6cf0f744c5b53dad80b76db633691", size = 7521747, upload-time = "2026-03-23T18:12:36.815Z" }, - { url = "https://files.pythonhosted.org/packages/a9/1b/f1bc86a918c5f6feab1eeff11982e2060f4704332e96185463d27855bdf5/torchvision-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:4280c35ec8cba1fcc8294fb87e136924708726864c379e4c54494797d86bc474", size = 4319880, upload-time = "2026-03-23T18:12:38.168Z" }, - { url = "https://files.pythonhosted.org/packages/66/28/b4ad0a723ed95b003454caffcc41894b34bd8379df340848cae2c33871de/torchvision-0.26.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:358fc4726d0c08615b6d83b3149854f11efb2a564ed1acb6fce882e151412d23", size = 1951973, upload-time = "2026-03-23T18:12:48.781Z" }, - { url = "https://files.pythonhosted.org/packages/71/e2/7a89096e6cf2f3336353b5338ba925e0addf9d8601920340e6bdf47e8eb3/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:3daf9cc149cf3cdcbd4df9c59dae69ffca86c6823250442c3bbfd63fc2e26c61", size = 7728679, upload-time = "2026-03-23T18:12:26.196Z" }, - { url = "https://files.pythonhosted.org/packages/69/1d/4e1eebc17d18ce080a11dcf3df3f8f717f0efdfa00983f06e8ba79259f61/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:82c3965eca27e86a316e31e4c3e5a16d353e0bcbe0ef8efa2e66502c54493c4b", size = 7609138, upload-time = "2026-03-23T18:12:35.327Z" }, - { url = "https://files.pythonhosted.org/packages/f3/a4/f1155e943ae5b32400d7000adc81c79bb0392b16ceb33bcf13e02e48cced/torchvision-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ebc043cc5a4f0bf22e7680806dbba37ffb19e70f6953bbb44ed1a90aeb5c9bea", size = 4248202, upload-time = "2026-03-23T18:12:41.423Z" }, - { url = "https://files.pythonhosted.org/packages/7f/c8/9bffa9c7f7bdf95b2a0a2dc535c290b9f1cc580c3fb3033ab1246ffffdeb/torchvision-0.26.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:eb61804eb9dbe88c5a2a6c4da8dec1d80d2d0a6f18c999c524e32266cb1ebcd3", size = 1860813, upload-time = "2026-03-23T18:12:39.636Z" }, - { url = "https://files.pythonhosted.org/packages/7b/ac/48f28ffd227991f2e14f4392dde7e8dc14352bb9428c1ef4a4bbf5f7ed85/torchvision-0.26.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:9a904f2131cbfadab4df828088a9f66291ad33f49ff853872aed1f86848ef776", size = 7727777, upload-time = "2026-03-23T18:12:22.549Z" }, - { url = "https://files.pythonhosted.org/packages/a4/21/a2266f7f1b0e58e624ff15fd6f01041f59182c49551ece0db9a183071329/torchvision-0.26.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:0f3e572efe62ad645017ea847e0b5e4f2f638d4e39f05bc011d1eb9ac68d4806", size = 7522174, upload-time = "2026-03-23T18:12:29.565Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ba/1666f90bc0bdd77aaa11dcc42bb9f621a9c3668819c32430452e3d404730/torchvision-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:114bec0c0e98aa4ba446f63e2fe7a2cbca37b39ac933987ee4804f65de121800", size = 4348469, upload-time = "2026-03-23T18:12:24.44Z" }, - { url = "https://files.pythonhosted.org/packages/45/8f/1f0402ac55c2ae15651ff831957d083fe70b2d12282e72612a30ba601512/torchvision-0.26.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:b7d3e295624a28b3b1769228ce1345d94cf4d390dd31136766f76f2d20f718da", size = 1860826, upload-time = "2026-03-23T18:12:34.1Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6a/18a582fe3c5ee26f49b5c9fb21ad8016b4d1c06d10178894a58653946fda/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:7058c5878262937e876f20c25867b33724586aa4499e2853b2d52b99a5e51953", size = 7729089, upload-time = "2026-03-23T18:12:31.394Z" }, - { url = "https://files.pythonhosted.org/packages/c5/9b/f7e119b59499edc00c55c03adc9ec3bd96144d9b81c46852c431f9c64a9a/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8008474855623c6ba52876589dc52df0aa66e518c25eca841445348e5f79844c", size = 7522704, upload-time = "2026-03-23T18:12:20.301Z" }, - { url = "https://files.pythonhosted.org/packages/d0/6a/09f3844c10643f6c0de5d95abc863420cfaf194c88c7dffd0ac523e2015f/torchvision-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e9d0e022c19a78552fb055d0414d47fecb4a649309b9968573daea160ba6869c", size = 4454275, upload-time = "2026-03-23T18:12:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e7/56b47cc3b132aea90ccce22bcb8975dec688b002150012acc842846039d0/torchvision-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c409e1c3fdebec7a3834465086dbda8bf7680eff79abf7fd2f10c6b59520a7a4", size = 1863502 }, + { url = "https://files.pythonhosted.org/packages/f4/ec/5c31c92c08b65662fe9604a4067ae8232582805949f11ddc042cebe818ed/torchvision-0.26.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:406557718e62fdf10f5706e88d8a5ec000f872da913bf629aab9297622585547", size = 7767944 }, + { url = "https://files.pythonhosted.org/packages/f5/d8/cb6ccda1a1f35a6597645818641701207b3e8e13553e75fce5d86bac74b2/torchvision-0.26.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d61a5abb6b42a0c0c311996c2ac4b83a94418a97182c83b055a2a4ae985e05aa", size = 7522205 }, + { url = "https://files.pythonhosted.org/packages/1c/a9/c272623a0f735c35f0f6cd6dc74784d4f970e800cf063bb76687895a2ab9/torchvision-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:7993c01648e7c61d191b018e84d38fe0825c8fcb2720cd0f37caf7ba14404aa1", size = 4255155 }, + { url = "https://files.pythonhosted.org/packages/da/80/0762f77f53605d10c9477be39bb47722cc8e383bbbc2531471ce0e396c07/torchvision-0.26.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5d63dd43162691258b1b3529b9041bac7d54caa37eae0925f997108268cbf7c4", size = 1860809 }, + { url = "https://files.pythonhosted.org/packages/e6/81/0b3e58d1478c660a5af4268713486b2df7203f35abd9195fea87348a5178/torchvision-0.26.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a39c7a26538c41fda453f9a9692b5ff9b35a5437db1d94f3027f6f509c160eac", size = 7727494 }, + { url = "https://files.pythonhosted.org/packages/b6/dc/d9ab5d29115aa05e12e30f1397a3eeae1d88a511241dc3bce48dc4342675/torchvision-0.26.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:b7e6213620bbf97742e5f79832f9e9d769e6cf0f744c5b53dad80b76db633691", size = 7521747 }, + { url = "https://files.pythonhosted.org/packages/a9/1b/f1bc86a918c5f6feab1eeff11982e2060f4704332e96185463d27855bdf5/torchvision-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:4280c35ec8cba1fcc8294fb87e136924708726864c379e4c54494797d86bc474", size = 4319880 }, + { url = "https://files.pythonhosted.org/packages/66/28/b4ad0a723ed95b003454caffcc41894b34bd8379df340848cae2c33871de/torchvision-0.26.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:358fc4726d0c08615b6d83b3149854f11efb2a564ed1acb6fce882e151412d23", size = 1951973 }, + { url = "https://files.pythonhosted.org/packages/71/e2/7a89096e6cf2f3336353b5338ba925e0addf9d8601920340e6bdf47e8eb3/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:3daf9cc149cf3cdcbd4df9c59dae69ffca86c6823250442c3bbfd63fc2e26c61", size = 7728679 }, + { url = "https://files.pythonhosted.org/packages/69/1d/4e1eebc17d18ce080a11dcf3df3f8f717f0efdfa00983f06e8ba79259f61/torchvision-0.26.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:82c3965eca27e86a316e31e4c3e5a16d353e0bcbe0ef8efa2e66502c54493c4b", size = 7609138 }, + { url = "https://files.pythonhosted.org/packages/f3/a4/f1155e943ae5b32400d7000adc81c79bb0392b16ceb33bcf13e02e48cced/torchvision-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ebc043cc5a4f0bf22e7680806dbba37ffb19e70f6953bbb44ed1a90aeb5c9bea", size = 4248202 }, + { url = "https://files.pythonhosted.org/packages/7f/c8/9bffa9c7f7bdf95b2a0a2dc535c290b9f1cc580c3fb3033ab1246ffffdeb/torchvision-0.26.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:eb61804eb9dbe88c5a2a6c4da8dec1d80d2d0a6f18c999c524e32266cb1ebcd3", size = 1860813 }, + { url = "https://files.pythonhosted.org/packages/7b/ac/48f28ffd227991f2e14f4392dde7e8dc14352bb9428c1ef4a4bbf5f7ed85/torchvision-0.26.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:9a904f2131cbfadab4df828088a9f66291ad33f49ff853872aed1f86848ef776", size = 7727777 }, + { url = "https://files.pythonhosted.org/packages/a4/21/a2266f7f1b0e58e624ff15fd6f01041f59182c49551ece0db9a183071329/torchvision-0.26.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:0f3e572efe62ad645017ea847e0b5e4f2f638d4e39f05bc011d1eb9ac68d4806", size = 7522174 }, + { url = "https://files.pythonhosted.org/packages/fc/ba/1666f90bc0bdd77aaa11dcc42bb9f621a9c3668819c32430452e3d404730/torchvision-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:114bec0c0e98aa4ba446f63e2fe7a2cbca37b39ac933987ee4804f65de121800", size = 4348469 }, + { url = "https://files.pythonhosted.org/packages/45/8f/1f0402ac55c2ae15651ff831957d083fe70b2d12282e72612a30ba601512/torchvision-0.26.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:b7d3e295624a28b3b1769228ce1345d94cf4d390dd31136766f76f2d20f718da", size = 1860826 }, + { url = "https://files.pythonhosted.org/packages/d2/6a/18a582fe3c5ee26f49b5c9fb21ad8016b4d1c06d10178894a58653946fda/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:7058c5878262937e876f20c25867b33724586aa4499e2853b2d52b99a5e51953", size = 7729089 }, + { url = "https://files.pythonhosted.org/packages/c5/9b/f7e119b59499edc00c55c03adc9ec3bd96144d9b81c46852c431f9c64a9a/torchvision-0.26.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8008474855623c6ba52876589dc52df0aa66e518c25eca841445348e5f79844c", size = 7522704 }, + { url = "https://files.pythonhosted.org/packages/d0/6a/09f3844c10643f6c0de5d95abc863420cfaf194c88c7dffd0ac523e2015f/torchvision-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e9d0e022c19a78552fb055d0414d47fecb4a649309b9968573daea160ba6869c", size = 4454275 }, ] [[package]] name = "tornado" version = "6.5.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006 } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" }, - { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" }, - { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" }, - { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" }, - { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" }, - { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" }, - { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" }, - { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" }, - { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983 }, + { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246 }, + { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229 }, + { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192 }, + { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039 }, + { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445 }, + { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582 }, + { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990 }, + { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016 }, ] [[package]] @@ -8642,9 +8625,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598 } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374 }, ] [[package]] @@ -8660,9 +8643,9 @@ dependencies = [ { name = "lxml" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/25/e3ebeefdebfdfae8c4a4396f5a6ea51fc6fa0831d63ce338e5090a8003dc/trafilatura-2.0.0.tar.gz", hash = "sha256:ceb7094a6ecc97e72fea73c7dba36714c5c5b577b6470e4520dca893706d6247", size = 253404, upload-time = "2024-12-03T15:23:24.16Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/25/e3ebeefdebfdfae8c4a4396f5a6ea51fc6fa0831d63ce338e5090a8003dc/trafilatura-2.0.0.tar.gz", hash = "sha256:ceb7094a6ecc97e72fea73c7dba36714c5c5b577b6470e4520dca893706d6247", size = 253404 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/b6/097367f180b6383a3581ca1b86fcae284e52075fa941d1232df35293363c/trafilatura-2.0.0-py3-none-any.whl", hash = "sha256:77eb5d1e993747f6f20938e1de2d840020719735690c840b9a1024803a4cd51d", size = 132557, upload-time = "2024-12-03T15:23:21.41Z" }, + { url = "https://files.pythonhosted.org/packages/8a/b6/097367f180b6383a3581ca1b86fcae284e52075fa941d1232df35293363c/trafilatura-2.0.0-py3-none-any.whl", hash = "sha256:77eb5d1e993747f6f20938e1de2d840020719735690c840b9a1024803a4cd51d", size = 132557 }, ] [[package]] @@ -8681,69 +8664,69 @@ dependencies = [ { name = "tokenizers" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/35/67252acc1b929dc88b6602e8c4a982e64f31e733b804c14bc24b47da35e6/transformers-4.57.6.tar.gz", hash = "sha256:55e44126ece9dc0a291521b7e5492b572e6ef2766338a610b9ab5afbb70689d3", size = 10134912, upload-time = "2026-01-16T10:38:39.284Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/35/67252acc1b929dc88b6602e8c4a982e64f31e733b804c14bc24b47da35e6/transformers-4.57.6.tar.gz", hash = "sha256:55e44126ece9dc0a291521b7e5492b572e6ef2766338a610b9ab5afbb70689d3", size = 10134912 } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/b8/e484ef633af3887baeeb4b6ad12743363af7cce68ae51e938e00aaa0529d/transformers-4.57.6-py3-none-any.whl", hash = "sha256:4c9e9de11333ddfe5114bc872c9f370509198acf0b87a832a0ab9458e2bd0550", size = 11993498, upload-time = "2026-01-16T10:38:31.289Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/e484ef633af3887baeeb4b6ad12743363af7cce68ae51e938e00aaa0529d/transformers-4.57.6-py3-none-any.whl", hash = "sha256:4c9e9de11333ddfe5114bc872c9f370509198acf0b87a832a0ab9458e2bd0550", size = 11993498 }, ] [[package]] name = "tree-sitter" version = "0.25.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/7c/0350cfc47faadc0d3cf7d8237a4e34032b3014ddf4a12ded9933e1648b55/tree-sitter-0.25.2.tar.gz", hash = "sha256:fe43c158555da46723b28b52e058ad444195afd1db3ca7720c59a254544e9c20", size = 177961, upload-time = "2025-09-25T17:37:59.751Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/7c/0350cfc47faadc0d3cf7d8237a4e34032b3014ddf4a12ded9933e1648b55/tree-sitter-0.25.2.tar.gz", hash = "sha256:fe43c158555da46723b28b52e058ad444195afd1db3ca7720c59a254544e9c20", size = 177961 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/9e/20c2a00a862f1c2897a436b17edb774e831b22218083b459d0d081c9db33/tree_sitter-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ddabfff809ffc983fc9963455ba1cecc90295803e06e140a4c83e94c1fa3d960", size = 146941, upload-time = "2025-09-25T17:37:34.813Z" }, - { url = "https://files.pythonhosted.org/packages/ef/04/8512e2062e652a1016e840ce36ba1cc33258b0dcc4e500d8089b4054afec/tree_sitter-0.25.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c0c0ab5f94938a23fe81928a21cc0fac44143133ccc4eb7eeb1b92f84748331c", size = 137699, upload-time = "2025-09-25T17:37:36.349Z" }, - { url = "https://files.pythonhosted.org/packages/47/8a/d48c0414db19307b0fb3bb10d76a3a0cbe275bb293f145ee7fba2abd668e/tree_sitter-0.25.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd12d80d91d4114ca097626eb82714618dcdfacd6a5e0955216c6485c350ef99", size = 607125, upload-time = "2025-09-25T17:37:37.725Z" }, - { url = "https://files.pythonhosted.org/packages/39/d1/b95f545e9fc5001b8a78636ef942a4e4e536580caa6a99e73dd0a02e87aa/tree_sitter-0.25.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b43a9e4c89d4d0839de27cd4d6902d33396de700e9ff4c5ab7631f277a85ead9", size = 635418, upload-time = "2025-09-25T17:37:38.922Z" }, - { url = "https://files.pythonhosted.org/packages/de/4d/b734bde3fb6f3513a010fa91f1f2875442cdc0382d6a949005cd84563d8f/tree_sitter-0.25.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbb1706407c0e451c4f8cc016fec27d72d4b211fdd3173320b1ada7a6c74c3ac", size = 631250, upload-time = "2025-09-25T17:37:40.039Z" }, - { url = "https://files.pythonhosted.org/packages/46/f2/5f654994f36d10c64d50a192239599fcae46677491c8dd53e7579c35a3e3/tree_sitter-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:6d0302550bbe4620a5dc7649517c4409d74ef18558276ce758419cf09e578897", size = 127156, upload-time = "2025-09-25T17:37:41.132Z" }, - { url = "https://files.pythonhosted.org/packages/67/23/148c468d410efcf0a9535272d81c258d840c27b34781d625f1f627e2e27d/tree_sitter-0.25.2-cp312-cp312-win_arm64.whl", hash = "sha256:0c8b6682cac77e37cfe5cf7ec388844957f48b7bd8d6321d0ca2d852994e10d5", size = 113984, upload-time = "2025-09-25T17:37:42.074Z" }, - { url = "https://files.pythonhosted.org/packages/8c/67/67492014ce32729b63d7ef318a19f9cfedd855d677de5773476caf771e96/tree_sitter-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0628671f0de69bb279558ef6b640bcfc97864fe0026d840f872728a86cd6b6cd", size = 146926, upload-time = "2025-09-25T17:37:43.041Z" }, - { url = "https://files.pythonhosted.org/packages/4e/9c/a278b15e6b263e86c5e301c82a60923fa7c59d44f78d7a110a89a413e640/tree_sitter-0.25.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5ddcd3e291a749b62521f71fc953f66f5fd9743973fd6dd962b092773569601", size = 137712, upload-time = "2025-09-25T17:37:44.039Z" }, - { url = "https://files.pythonhosted.org/packages/54/9a/423bba15d2bf6473ba67846ba5244b988cd97a4b1ea2b146822162256794/tree_sitter-0.25.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd88fbb0f6c3a0f28f0a68d72df88e9755cf5215bae146f5a1bdc8362b772053", size = 607873, upload-time = "2025-09-25T17:37:45.477Z" }, - { url = "https://files.pythonhosted.org/packages/ed/4c/b430d2cb43f8badfb3a3fa9d6cd7c8247698187b5674008c9d67b2a90c8e/tree_sitter-0.25.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b878e296e63661c8e124177cc3084b041ba3f5936b43076d57c487822426f614", size = 636313, upload-time = "2025-09-25T17:37:46.68Z" }, - { url = "https://files.pythonhosted.org/packages/9d/27/5f97098dbba807331d666a0997662e82d066e84b17d92efab575d283822f/tree_sitter-0.25.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d77605e0d353ba3fe5627e5490f0fbfe44141bafa4478d88ef7954a61a848dae", size = 631370, upload-time = "2025-09-25T17:37:47.993Z" }, - { url = "https://files.pythonhosted.org/packages/d4/3c/87caaed663fabc35e18dc704cd0e9800a0ee2f22bd18b9cbe7c10799895d/tree_sitter-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:463c032bd02052d934daa5f45d183e0521ceb783c2548501cf034b0beba92c9b", size = 127157, upload-time = "2025-09-25T17:37:48.967Z" }, - { url = "https://files.pythonhosted.org/packages/d5/23/f8467b408b7988aff4ea40946a4bd1a2c1a73d17156a9d039bbaff1e2ceb/tree_sitter-0.25.2-cp313-cp313-win_arm64.whl", hash = "sha256:b3f63a1796886249bd22c559a5944d64d05d43f2be72961624278eff0dcc5cb8", size = 113975, upload-time = "2025-09-25T17:37:49.922Z" }, - { url = "https://files.pythonhosted.org/packages/07/e3/d9526ba71dfbbe4eba5e51d89432b4b333a49a1e70712aa5590cd22fc74f/tree_sitter-0.25.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:65d3c931013ea798b502782acab986bbf47ba2c452610ab0776cf4a8ef150fc0", size = 146776, upload-time = "2025-09-25T17:37:50.898Z" }, - { url = "https://files.pythonhosted.org/packages/42/97/4bd4ad97f85a23011dd8a535534bb1035c4e0bac1234d58f438e15cff51f/tree_sitter-0.25.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bda059af9d621918efb813b22fb06b3fe00c3e94079c6143fcb2c565eb44cb87", size = 137732, upload-time = "2025-09-25T17:37:51.877Z" }, - { url = "https://files.pythonhosted.org/packages/b6/19/1e968aa0b1b567988ed522f836498a6a9529a74aab15f09dd9ac1e41f505/tree_sitter-0.25.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eac4e8e4c7060c75f395feec46421eb61212cb73998dbe004b7384724f3682ab", size = 609456, upload-time = "2025-09-25T17:37:52.925Z" }, - { url = "https://files.pythonhosted.org/packages/48/b6/cf08f4f20f4c9094006ef8828555484e842fc468827ad6e56011ab668dbd/tree_sitter-0.25.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:260586381b23be33b6191a07cea3d44ecbd6c01aa4c6b027a0439145fcbc3358", size = 636772, upload-time = "2025-09-25T17:37:54.647Z" }, - { url = "https://files.pythonhosted.org/packages/57/e2/d42d55bf56360987c32bc7b16adb06744e425670b823fb8a5786a1cea991/tree_sitter-0.25.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d2ee1acbacebe50ba0f85fff1bc05e65d877958f00880f49f9b2af38dce1af0", size = 631522, upload-time = "2025-09-25T17:37:55.833Z" }, - { url = "https://files.pythonhosted.org/packages/03/87/af9604ebe275a9345d88c3ace0cf2a1341aa3f8ef49dd9fc11662132df8a/tree_sitter-0.25.2-cp314-cp314-win_amd64.whl", hash = "sha256:4973b718fcadfb04e59e746abfbb0288694159c6aeecd2add59320c03368c721", size = 130864, upload-time = "2025-09-25T17:37:57.453Z" }, - { url = "https://files.pythonhosted.org/packages/a6/6e/e64621037357acb83d912276ffd30a859ef117f9c680f2e3cb955f47c680/tree_sitter-0.25.2-cp314-cp314-win_arm64.whl", hash = "sha256:b8d4429954a3beb3e844e2872610d2a4800ba4eb42bb1990c6a4b1949b18459f", size = 117470, upload-time = "2025-09-25T17:37:58.431Z" }, + { url = "https://files.pythonhosted.org/packages/3c/9e/20c2a00a862f1c2897a436b17edb774e831b22218083b459d0d081c9db33/tree_sitter-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ddabfff809ffc983fc9963455ba1cecc90295803e06e140a4c83e94c1fa3d960", size = 146941 }, + { url = "https://files.pythonhosted.org/packages/ef/04/8512e2062e652a1016e840ce36ba1cc33258b0dcc4e500d8089b4054afec/tree_sitter-0.25.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c0c0ab5f94938a23fe81928a21cc0fac44143133ccc4eb7eeb1b92f84748331c", size = 137699 }, + { url = "https://files.pythonhosted.org/packages/47/8a/d48c0414db19307b0fb3bb10d76a3a0cbe275bb293f145ee7fba2abd668e/tree_sitter-0.25.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd12d80d91d4114ca097626eb82714618dcdfacd6a5e0955216c6485c350ef99", size = 607125 }, + { url = "https://files.pythonhosted.org/packages/39/d1/b95f545e9fc5001b8a78636ef942a4e4e536580caa6a99e73dd0a02e87aa/tree_sitter-0.25.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b43a9e4c89d4d0839de27cd4d6902d33396de700e9ff4c5ab7631f277a85ead9", size = 635418 }, + { url = "https://files.pythonhosted.org/packages/de/4d/b734bde3fb6f3513a010fa91f1f2875442cdc0382d6a949005cd84563d8f/tree_sitter-0.25.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbb1706407c0e451c4f8cc016fec27d72d4b211fdd3173320b1ada7a6c74c3ac", size = 631250 }, + { url = "https://files.pythonhosted.org/packages/46/f2/5f654994f36d10c64d50a192239599fcae46677491c8dd53e7579c35a3e3/tree_sitter-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:6d0302550bbe4620a5dc7649517c4409d74ef18558276ce758419cf09e578897", size = 127156 }, + { url = "https://files.pythonhosted.org/packages/67/23/148c468d410efcf0a9535272d81c258d840c27b34781d625f1f627e2e27d/tree_sitter-0.25.2-cp312-cp312-win_arm64.whl", hash = "sha256:0c8b6682cac77e37cfe5cf7ec388844957f48b7bd8d6321d0ca2d852994e10d5", size = 113984 }, + { url = "https://files.pythonhosted.org/packages/8c/67/67492014ce32729b63d7ef318a19f9cfedd855d677de5773476caf771e96/tree_sitter-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0628671f0de69bb279558ef6b640bcfc97864fe0026d840f872728a86cd6b6cd", size = 146926 }, + { url = "https://files.pythonhosted.org/packages/4e/9c/a278b15e6b263e86c5e301c82a60923fa7c59d44f78d7a110a89a413e640/tree_sitter-0.25.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5ddcd3e291a749b62521f71fc953f66f5fd9743973fd6dd962b092773569601", size = 137712 }, + { url = "https://files.pythonhosted.org/packages/54/9a/423bba15d2bf6473ba67846ba5244b988cd97a4b1ea2b146822162256794/tree_sitter-0.25.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd88fbb0f6c3a0f28f0a68d72df88e9755cf5215bae146f5a1bdc8362b772053", size = 607873 }, + { url = "https://files.pythonhosted.org/packages/ed/4c/b430d2cb43f8badfb3a3fa9d6cd7c8247698187b5674008c9d67b2a90c8e/tree_sitter-0.25.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b878e296e63661c8e124177cc3084b041ba3f5936b43076d57c487822426f614", size = 636313 }, + { url = "https://files.pythonhosted.org/packages/9d/27/5f97098dbba807331d666a0997662e82d066e84b17d92efab575d283822f/tree_sitter-0.25.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d77605e0d353ba3fe5627e5490f0fbfe44141bafa4478d88ef7954a61a848dae", size = 631370 }, + { url = "https://files.pythonhosted.org/packages/d4/3c/87caaed663fabc35e18dc704cd0e9800a0ee2f22bd18b9cbe7c10799895d/tree_sitter-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:463c032bd02052d934daa5f45d183e0521ceb783c2548501cf034b0beba92c9b", size = 127157 }, + { url = "https://files.pythonhosted.org/packages/d5/23/f8467b408b7988aff4ea40946a4bd1a2c1a73d17156a9d039bbaff1e2ceb/tree_sitter-0.25.2-cp313-cp313-win_arm64.whl", hash = "sha256:b3f63a1796886249bd22c559a5944d64d05d43f2be72961624278eff0dcc5cb8", size = 113975 }, + { url = "https://files.pythonhosted.org/packages/07/e3/d9526ba71dfbbe4eba5e51d89432b4b333a49a1e70712aa5590cd22fc74f/tree_sitter-0.25.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:65d3c931013ea798b502782acab986bbf47ba2c452610ab0776cf4a8ef150fc0", size = 146776 }, + { url = "https://files.pythonhosted.org/packages/42/97/4bd4ad97f85a23011dd8a535534bb1035c4e0bac1234d58f438e15cff51f/tree_sitter-0.25.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bda059af9d621918efb813b22fb06b3fe00c3e94079c6143fcb2c565eb44cb87", size = 137732 }, + { url = "https://files.pythonhosted.org/packages/b6/19/1e968aa0b1b567988ed522f836498a6a9529a74aab15f09dd9ac1e41f505/tree_sitter-0.25.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eac4e8e4c7060c75f395feec46421eb61212cb73998dbe004b7384724f3682ab", size = 609456 }, + { url = "https://files.pythonhosted.org/packages/48/b6/cf08f4f20f4c9094006ef8828555484e842fc468827ad6e56011ab668dbd/tree_sitter-0.25.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:260586381b23be33b6191a07cea3d44ecbd6c01aa4c6b027a0439145fcbc3358", size = 636772 }, + { url = "https://files.pythonhosted.org/packages/57/e2/d42d55bf56360987c32bc7b16adb06744e425670b823fb8a5786a1cea991/tree_sitter-0.25.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d2ee1acbacebe50ba0f85fff1bc05e65d877958f00880f49f9b2af38dce1af0", size = 631522 }, + { url = "https://files.pythonhosted.org/packages/03/87/af9604ebe275a9345d88c3ace0cf2a1341aa3f8ef49dd9fc11662132df8a/tree_sitter-0.25.2-cp314-cp314-win_amd64.whl", hash = "sha256:4973b718fcadfb04e59e746abfbb0288694159c6aeecd2add59320c03368c721", size = 130864 }, + { url = "https://files.pythonhosted.org/packages/a6/6e/e64621037357acb83d912276ffd30a859ef117f9c680f2e3cb955f47c680/tree_sitter-0.25.2-cp314-cp314-win_arm64.whl", hash = "sha256:b8d4429954a3beb3e844e2872610d2a4800ba4eb42bb1990c6a4b1949b18459f", size = 117470 }, ] [[package]] name = "tree-sitter-c" version = "0.24.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/f5/ba8cd08d717277551ade8537d3aa2a94b907c6c6e0fbcf4e4d8b1c747fa3/tree_sitter_c-0.24.1.tar.gz", hash = "sha256:7d2d0cda0b8dda428c81440c1e94367f9f13548eedca3f49768bde66b1422ad6", size = 228014, upload-time = "2025-05-24T17:32:58.384Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/f5/ba8cd08d717277551ade8537d3aa2a94b907c6c6e0fbcf4e4d8b1c747fa3/tree_sitter_c-0.24.1.tar.gz", hash = "sha256:7d2d0cda0b8dda428c81440c1e94367f9f13548eedca3f49768bde66b1422ad6", size = 228014 } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/c7/c817be36306e457c2d36cc324789046390d9d8c555c38772429ffdb7d361/tree_sitter_c-0.24.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9c06ac26a1efdcc8b26a8a6970fbc6997c4071857359e5837d4c42892d45fe1e", size = 80940, upload-time = "2025-05-24T17:32:49.967Z" }, - { url = "https://files.pythonhosted.org/packages/7a/42/283909467290b24fdbc29bb32ee20e409a19a55002b43175d66d091ca1a4/tree_sitter_c-0.24.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:942bcd7cbecd810dcf7ca6f8f834391ebf0771a89479646d891ba4ca2fdfdc88", size = 86304, upload-time = "2025-05-24T17:32:51.271Z" }, - { url = "https://files.pythonhosted.org/packages/94/53/fb4f61d4e5f15ec3da85774a4df8e58d3b5b73036cf167f0203b4dd9d158/tree_sitter_c-0.24.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a74cfd7a11ca5a961fafd4d751892ee65acae667d2818968a6f079397d8d28c", size = 109996, upload-time = "2025-05-24T17:32:52.119Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e8/fc541d34ee81c386c5453c2596c1763e8e9cd7cb0725f39d7dfa2276afa4/tree_sitter_c-0.24.1-cp310-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6a807705a3978911dc7ee26a7ad36dcfacb6adfc13c190d496660ec9bd66707", size = 98137, upload-time = "2025-05-24T17:32:53.361Z" }, - { url = "https://files.pythonhosted.org/packages/32/c6/d0563319cae0d5b5780a92e2806074b24afea2a07aa4c10599b899bda3ec/tree_sitter_c-0.24.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:789781afcb710df34144f7e2a20cd80e325114b9119e3956c6bd1dd2d365df98", size = 94148, upload-time = "2025-05-24T17:32:54.855Z" }, - { url = "https://files.pythonhosted.org/packages/50/5a/6361df7f3fa2310c53a0d26b4702a261c332da16fa9d801e381e3a86e25f/tree_sitter_c-0.24.1-cp310-abi3-win_amd64.whl", hash = "sha256:290bff0f9c79c966496ebae45042f77543e6e4aea725f40587a8611d566231a8", size = 84703, upload-time = "2025-05-24T17:32:56.084Z" }, - { url = "https://files.pythonhosted.org/packages/22/6a/210a302e8025ac492cbaea58d3720d66b7d8034c5d747ac5e4d2d235aa25/tree_sitter_c-0.24.1-cp310-abi3-win_arm64.whl", hash = "sha256:d46bbda06f838c2dcb91daf767813671fd366b49ad84ff37db702129267b46e1", size = 82715, upload-time = "2025-05-24T17:32:57.248Z" }, + { url = "https://files.pythonhosted.org/packages/15/c7/c817be36306e457c2d36cc324789046390d9d8c555c38772429ffdb7d361/tree_sitter_c-0.24.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9c06ac26a1efdcc8b26a8a6970fbc6997c4071857359e5837d4c42892d45fe1e", size = 80940 }, + { url = "https://files.pythonhosted.org/packages/7a/42/283909467290b24fdbc29bb32ee20e409a19a55002b43175d66d091ca1a4/tree_sitter_c-0.24.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:942bcd7cbecd810dcf7ca6f8f834391ebf0771a89479646d891ba4ca2fdfdc88", size = 86304 }, + { url = "https://files.pythonhosted.org/packages/94/53/fb4f61d4e5f15ec3da85774a4df8e58d3b5b73036cf167f0203b4dd9d158/tree_sitter_c-0.24.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a74cfd7a11ca5a961fafd4d751892ee65acae667d2818968a6f079397d8d28c", size = 109996 }, + { url = "https://files.pythonhosted.org/packages/5e/e8/fc541d34ee81c386c5453c2596c1763e8e9cd7cb0725f39d7dfa2276afa4/tree_sitter_c-0.24.1-cp310-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6a807705a3978911dc7ee26a7ad36dcfacb6adfc13c190d496660ec9bd66707", size = 98137 }, + { url = "https://files.pythonhosted.org/packages/32/c6/d0563319cae0d5b5780a92e2806074b24afea2a07aa4c10599b899bda3ec/tree_sitter_c-0.24.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:789781afcb710df34144f7e2a20cd80e325114b9119e3956c6bd1dd2d365df98", size = 94148 }, + { url = "https://files.pythonhosted.org/packages/50/5a/6361df7f3fa2310c53a0d26b4702a261c332da16fa9d801e381e3a86e25f/tree_sitter_c-0.24.1-cp310-abi3-win_amd64.whl", hash = "sha256:290bff0f9c79c966496ebae45042f77543e6e4aea725f40587a8611d566231a8", size = 84703 }, + { url = "https://files.pythonhosted.org/packages/22/6a/210a302e8025ac492cbaea58d3720d66b7d8034c5d747ac5e4d2d235aa25/tree_sitter_c-0.24.1-cp310-abi3-win_arm64.whl", hash = "sha256:d46bbda06f838c2dcb91daf767813671fd366b49ad84ff37db702129267b46e1", size = 82715 }, ] [[package]] name = "tree-sitter-javascript" version = "0.25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/59/e0/e63103c72a9d3dfd89a31e02e660263ad84b7438e5f44ee82e443e65bbde/tree_sitter_javascript-0.25.0.tar.gz", hash = "sha256:329b5414874f0588a98f1c291f1b28138286617aa907746ffe55adfdcf963f38", size = 132338, upload-time = "2025-09-01T07:13:44.792Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/e0/e63103c72a9d3dfd89a31e02e660263ad84b7438e5f44ee82e443e65bbde/tree_sitter_javascript-0.25.0.tar.gz", hash = "sha256:329b5414874f0588a98f1c291f1b28138286617aa907746ffe55adfdcf963f38", size = 132338 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/df/5106ac250cd03661ebc3cc75da6b3d9f6800a3606393a0122eca58038104/tree_sitter_javascript-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b70f887fb269d6e58c349d683f59fa647140c410cfe2bee44a883b20ec92e3dc", size = 64052, upload-time = "2025-09-01T07:13:36.865Z" }, - { url = "https://files.pythonhosted.org/packages/b1/8f/6b4b2bc90d8ab3955856ce852cc9d1e82c81d7ab9646385f0e75ffd5b5d3/tree_sitter_javascript-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:8264a996b8845cfce06965152a013b5d9cbb7d199bc3503e12b5682e62bb1de1", size = 66440, upload-time = "2025-09-01T07:13:37.962Z" }, - { url = "https://files.pythonhosted.org/packages/5f/c4/7da74ecdcd8a398f88bd003a87c65403b5fe0e958cdd43fbd5fd4a398fcf/tree_sitter_javascript-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9dc04ba91fc8583344e57c1f1ed5b2c97ecaaf47480011b92fbeab8dda96db75", size = 99728, upload-time = "2025-09-01T07:13:38.755Z" }, - { url = "https://files.pythonhosted.org/packages/96/c8/97da3af4796495e46421e9344738addb3602fa6426ea695be3fcbadbee37/tree_sitter_javascript-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:199d09985190852e0912da2b8d26c932159be314bc04952cf917ed0e4c633e6b", size = 106072, upload-time = "2025-09-01T07:13:39.798Z" }, - { url = "https://files.pythonhosted.org/packages/13/be/c964e8130be08cc9bd6627d845f0e4460945b158429d39510953bbcb8fcc/tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dfcf789064c58dc13c0a4edb550acacfc6f0f280577f1e7a00de3e89fc7f8ddc", size = 104388, upload-time = "2025-09-01T07:13:40.866Z" }, - { url = "https://files.pythonhosted.org/packages/ee/89/9b773dee0f8961d1bb8d7baf0a204ab587618df19897c1ef260916f318ec/tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b852d3aee8a36186dbcc32c798b11b4869f9b5041743b63b65c2ef793db7a54", size = 98377, upload-time = "2025-09-01T07:13:41.838Z" }, - { url = "https://files.pythonhosted.org/packages/3b/dc/d90cb1790f8cec9b4878d278ad9faf7c8f893189ce0f855304fd704fc274/tree_sitter_javascript-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:e5ed840f5bd4a3f0272e441d19429b26eedc257abe5574c8546da6b556865e3c", size = 62975, upload-time = "2025-09-01T07:13:42.828Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1f/f9eba1038b7d4394410f3c0a6ec2122b590cd7acb03f196e52fa57ebbe72/tree_sitter_javascript-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:622a69d677aa7f6ee2931d8c77c981a33f0ebb6d275aa9d43d3397c879a9bb0b", size = 61668, upload-time = "2025-09-01T07:13:43.803Z" }, + { url = "https://files.pythonhosted.org/packages/2c/df/5106ac250cd03661ebc3cc75da6b3d9f6800a3606393a0122eca58038104/tree_sitter_javascript-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b70f887fb269d6e58c349d683f59fa647140c410cfe2bee44a883b20ec92e3dc", size = 64052 }, + { url = "https://files.pythonhosted.org/packages/b1/8f/6b4b2bc90d8ab3955856ce852cc9d1e82c81d7ab9646385f0e75ffd5b5d3/tree_sitter_javascript-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:8264a996b8845cfce06965152a013b5d9cbb7d199bc3503e12b5682e62bb1de1", size = 66440 }, + { url = "https://files.pythonhosted.org/packages/5f/c4/7da74ecdcd8a398f88bd003a87c65403b5fe0e958cdd43fbd5fd4a398fcf/tree_sitter_javascript-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9dc04ba91fc8583344e57c1f1ed5b2c97ecaaf47480011b92fbeab8dda96db75", size = 99728 }, + { url = "https://files.pythonhosted.org/packages/96/c8/97da3af4796495e46421e9344738addb3602fa6426ea695be3fcbadbee37/tree_sitter_javascript-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:199d09985190852e0912da2b8d26c932159be314bc04952cf917ed0e4c633e6b", size = 106072 }, + { url = "https://files.pythonhosted.org/packages/13/be/c964e8130be08cc9bd6627d845f0e4460945b158429d39510953bbcb8fcc/tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dfcf789064c58dc13c0a4edb550acacfc6f0f280577f1e7a00de3e89fc7f8ddc", size = 104388 }, + { url = "https://files.pythonhosted.org/packages/ee/89/9b773dee0f8961d1bb8d7baf0a204ab587618df19897c1ef260916f318ec/tree_sitter_javascript-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b852d3aee8a36186dbcc32c798b11b4869f9b5041743b63b65c2ef793db7a54", size = 98377 }, + { url = "https://files.pythonhosted.org/packages/3b/dc/d90cb1790f8cec9b4878d278ad9faf7c8f893189ce0f855304fd704fc274/tree_sitter_javascript-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:e5ed840f5bd4a3f0272e441d19429b26eedc257abe5574c8546da6b556865e3c", size = 62975 }, + { url = "https://files.pythonhosted.org/packages/2e/1f/f9eba1038b7d4394410f3c0a6ec2122b590cd7acb03f196e52fa57ebbe72/tree_sitter_javascript-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:622a69d677aa7f6ee2931d8c77c981a33f0ebb6d275aa9d43d3397c879a9bb0b", size = 61668 }, ] [[package]] @@ -8754,41 +8737,41 @@ dependencies = [ { name = "tree-sitter" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/80/ec/230bda9b98fbc7a37e91e634633bb51a95aa25c3c6b92bb1a41f741c3dd3/tree_sitter_language_pack-1.3.3-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:3efb7068e61342b731b23216447b536f2fe57f7abc7a4f291e5c22760d964350", size = 2191180, upload-time = "2026-03-27T06:55:51.528Z" }, - { url = "https://files.pythonhosted.org/packages/e5/61/531cc590cf6aa35efdc63741fc2bf17b97a6b280a08695df65b168dc3f9a/tree_sitter_language_pack-1.3.3-cp310-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:a6921210705fd12fb67823c428c800e7903e835c5108eef06df3a09832e8a23e", size = 2369160, upload-time = "2026-03-27T06:55:53.529Z" }, - { url = "https://files.pythonhosted.org/packages/03/42/8e768d21ae85bc6982e2693e9016d75a6e06f1f98a6f4dec5bcb07120dd7/tree_sitter_language_pack-1.3.3-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d699b26a251769628045f298a184e86c005cc25c8a67646c0562adc929044e95", size = 2506996, upload-time = "2026-03-27T06:55:55.538Z" }, - { url = "https://files.pythonhosted.org/packages/6e/64/4fdf78ddd2d59f3e66332d3d6a0f3460e6425045f7150ed02d37f73ef019/tree_sitter_language_pack-1.3.3-cp310-abi3-win_amd64.whl", hash = "sha256:8a0735f898fc9443c90ea824c88b9fc19588ea10567ae97c768e3f9b14b64e16", size = 2300882, upload-time = "2026-03-27T06:55:57.047Z" }, + { url = "https://files.pythonhosted.org/packages/80/ec/230bda9b98fbc7a37e91e634633bb51a95aa25c3c6b92bb1a41f741c3dd3/tree_sitter_language_pack-1.3.3-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:3efb7068e61342b731b23216447b536f2fe57f7abc7a4f291e5c22760d964350", size = 2191180 }, + { url = "https://files.pythonhosted.org/packages/e5/61/531cc590cf6aa35efdc63741fc2bf17b97a6b280a08695df65b168dc3f9a/tree_sitter_language_pack-1.3.3-cp310-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:a6921210705fd12fb67823c428c800e7903e835c5108eef06df3a09832e8a23e", size = 2369160 }, + { url = "https://files.pythonhosted.org/packages/03/42/8e768d21ae85bc6982e2693e9016d75a6e06f1f98a6f4dec5bcb07120dd7/tree_sitter_language_pack-1.3.3-cp310-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d699b26a251769628045f298a184e86c005cc25c8a67646c0562adc929044e95", size = 2506996 }, + { url = "https://files.pythonhosted.org/packages/6e/64/4fdf78ddd2d59f3e66332d3d6a0f3460e6425045f7150ed02d37f73ef019/tree_sitter_language_pack-1.3.3-cp310-abi3-win_amd64.whl", hash = "sha256:8a0735f898fc9443c90ea824c88b9fc19588ea10567ae97c768e3f9b14b64e16", size = 2300882 }, ] [[package]] name = "tree-sitter-python" version = "0.25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/8b/c992ff0e768cb6768d5c96234579bf8842b3a633db641455d86dd30d5dac/tree_sitter_python-0.25.0.tar.gz", hash = "sha256:b13e090f725f5b9c86aa455a268553c65cadf325471ad5b65cd29cac8a1a68ac", size = 159845, upload-time = "2025-09-11T06:47:58.159Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/8b/c992ff0e768cb6768d5c96234579bf8842b3a633db641455d86dd30d5dac/tree_sitter_python-0.25.0.tar.gz", hash = "sha256:b13e090f725f5b9c86aa455a268553c65cadf325471ad5b65cd29cac8a1a68ac", size = 159845 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/64/a4e503c78a4eb3ac46d8e72a29c1b1237fa85238d8e972b063e0751f5a94/tree_sitter_python-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:14a79a47ddef72f987d5a2c122d148a812169d7484ff5c75a3db9609d419f361", size = 73790, upload-time = "2025-09-11T06:47:47.652Z" }, - { url = "https://files.pythonhosted.org/packages/e6/1d/60d8c2a0cc63d6ec4ba4e99ce61b802d2e39ef9db799bdf2a8f932a6cd4b/tree_sitter_python-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:480c21dbd995b7fe44813e741d71fed10ba695e7caab627fb034e3828469d762", size = 76691, upload-time = "2025-09-11T06:47:49.038Z" }, - { url = "https://files.pythonhosted.org/packages/aa/cb/d9b0b67d037922d60cbe0359e0c86457c2da721bc714381a63e2c8e35eba/tree_sitter_python-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86f118e5eecad616ecdb81d171a36dde9bef5a0b21ed71ea9c3e390813c3baf5", size = 108133, upload-time = "2025-09-11T06:47:50.499Z" }, - { url = "https://files.pythonhosted.org/packages/40/bd/bf4787f57e6b2860f3f1c8c62f045b39fb32d6bac4b53d7a9e66de968440/tree_sitter_python-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be71650ca2b93b6e9649e5d65c6811aad87a7614c8c1003246b303f6b150f61b", size = 110603, upload-time = "2025-09-11T06:47:51.985Z" }, - { url = "https://files.pythonhosted.org/packages/5d/25/feff09f5c2f32484fbce15db8b49455c7572346ce61a699a41972dea7318/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6d5b5799628cc0f24691ab2a172a8e676f668fe90dc60468bee14084a35c16d", size = 108998, upload-time = "2025-09-11T06:47:53.046Z" }, - { url = "https://files.pythonhosted.org/packages/75/69/4946da3d6c0df316ccb938316ce007fb565d08f89d02d854f2d308f0309f/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:71959832fc5d9642e52c11f2f7d79ae520b461e63334927e93ca46cd61cd9683", size = 107268, upload-time = "2025-09-11T06:47:54.388Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a2/996fc2dfa1076dc460d3e2f3c75974ea4b8f02f6bc925383aaae519920e8/tree_sitter_python-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:9bcde33f18792de54ee579b00e1b4fe186b7926825444766f849bf7181793a76", size = 76073, upload-time = "2025-09-11T06:47:55.773Z" }, - { url = "https://files.pythonhosted.org/packages/07/19/4b5569d9b1ebebb5907d11554a96ef3fa09364a30fcfabeff587495b512f/tree_sitter_python-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:0fbf6a3774ad7e89ee891851204c2e2c47e12b63a5edbe2e9156997731c128bb", size = 74169, upload-time = "2025-09-11T06:47:56.747Z" }, + { url = "https://files.pythonhosted.org/packages/cf/64/a4e503c78a4eb3ac46d8e72a29c1b1237fa85238d8e972b063e0751f5a94/tree_sitter_python-0.25.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:14a79a47ddef72f987d5a2c122d148a812169d7484ff5c75a3db9609d419f361", size = 73790 }, + { url = "https://files.pythonhosted.org/packages/e6/1d/60d8c2a0cc63d6ec4ba4e99ce61b802d2e39ef9db799bdf2a8f932a6cd4b/tree_sitter_python-0.25.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:480c21dbd995b7fe44813e741d71fed10ba695e7caab627fb034e3828469d762", size = 76691 }, + { url = "https://files.pythonhosted.org/packages/aa/cb/d9b0b67d037922d60cbe0359e0c86457c2da721bc714381a63e2c8e35eba/tree_sitter_python-0.25.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86f118e5eecad616ecdb81d171a36dde9bef5a0b21ed71ea9c3e390813c3baf5", size = 108133 }, + { url = "https://files.pythonhosted.org/packages/40/bd/bf4787f57e6b2860f3f1c8c62f045b39fb32d6bac4b53d7a9e66de968440/tree_sitter_python-0.25.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be71650ca2b93b6e9649e5d65c6811aad87a7614c8c1003246b303f6b150f61b", size = 110603 }, + { url = "https://files.pythonhosted.org/packages/5d/25/feff09f5c2f32484fbce15db8b49455c7572346ce61a699a41972dea7318/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6d5b5799628cc0f24691ab2a172a8e676f668fe90dc60468bee14084a35c16d", size = 108998 }, + { url = "https://files.pythonhosted.org/packages/75/69/4946da3d6c0df316ccb938316ce007fb565d08f89d02d854f2d308f0309f/tree_sitter_python-0.25.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:71959832fc5d9642e52c11f2f7d79ae520b461e63334927e93ca46cd61cd9683", size = 107268 }, + { url = "https://files.pythonhosted.org/packages/ed/a2/996fc2dfa1076dc460d3e2f3c75974ea4b8f02f6bc925383aaae519920e8/tree_sitter_python-0.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:9bcde33f18792de54ee579b00e1b4fe186b7926825444766f849bf7181793a76", size = 76073 }, + { url = "https://files.pythonhosted.org/packages/07/19/4b5569d9b1ebebb5907d11554a96ef3fa09364a30fcfabeff587495b512f/tree_sitter_python-0.25.0-cp310-abi3-win_arm64.whl", hash = "sha256:0fbf6a3774ad7e89ee891851204c2e2c47e12b63a5edbe2e9156997731c128bb", size = 74169 }, ] [[package]] name = "tree-sitter-typescript" version = "0.23.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/fc/bb52958f7e399250aee093751e9373a6311cadbe76b6e0d109b853757f35/tree_sitter_typescript-0.23.2.tar.gz", hash = "sha256:7b167b5827c882261cb7a50dfa0fb567975f9b315e87ed87ad0a0a3aedb3834d", size = 773053, upload-time = "2024-11-11T02:36:11.396Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/fc/bb52958f7e399250aee093751e9373a6311cadbe76b6e0d109b853757f35/tree_sitter_typescript-0.23.2.tar.gz", hash = "sha256:7b167b5827c882261cb7a50dfa0fb567975f9b315e87ed87ad0a0a3aedb3834d", size = 773053 } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/95/4c00680866280e008e81dd621fd4d3f54aa3dad1b76b857a19da1b2cc426/tree_sitter_typescript-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3cd752d70d8e5371fdac6a9a4df9d8924b63b6998d268586f7d374c9fba2a478", size = 286677, upload-time = "2024-11-11T02:35:58.839Z" }, - { url = "https://files.pythonhosted.org/packages/8f/2f/1f36fda564518d84593f2740d5905ac127d590baf5c5753cef2a88a89c15/tree_sitter_typescript-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c7cc1b0ff5d91bac863b0e38b1578d5505e718156c9db577c8baea2557f66de8", size = 302008, upload-time = "2024-11-11T02:36:00.733Z" }, - { url = "https://files.pythonhosted.org/packages/96/2d/975c2dad292aa9994f982eb0b69cc6fda0223e4b6c4ea714550477d8ec3a/tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b1eed5b0b3a8134e86126b00b743d667ec27c63fc9de1b7bb23168803879e31", size = 351987, upload-time = "2024-11-11T02:36:02.669Z" }, - { url = "https://files.pythonhosted.org/packages/49/d1/a71c36da6e2b8a4ed5e2970819b86ef13ba77ac40d9e333cb17df6a2c5db/tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e96d36b85bcacdeb8ff5c2618d75593ef12ebaf1b4eace3477e2bdb2abb1752c", size = 344960, upload-time = "2024-11-11T02:36:04.443Z" }, - { url = "https://files.pythonhosted.org/packages/7f/cb/f57b149d7beed1a85b8266d0c60ebe4c46e79c9ba56bc17b898e17daf88e/tree_sitter_typescript-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8d4f0f9bcb61ad7b7509d49a1565ff2cc363863644a234e1e0fe10960e55aea0", size = 340245, upload-time = "2024-11-11T02:36:06.473Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ab/dd84f0e2337296a5f09749f7b5483215d75c8fa9e33738522e5ed81f7254/tree_sitter_typescript-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:3f730b66396bc3e11811e4465c41ee45d9e9edd6de355a58bbbc49fa770da8f9", size = 278015, upload-time = "2024-11-11T02:36:07.631Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e4/81f9a935789233cf412a0ed5fe04c883841d2c8fb0b7e075958a35c65032/tree_sitter_typescript-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:05db58f70b95ef0ea126db5560f3775692f609589ed6f8dd0af84b7f19f1cbb7", size = 274052, upload-time = "2024-11-11T02:36:09.514Z" }, + { url = "https://files.pythonhosted.org/packages/28/95/4c00680866280e008e81dd621fd4d3f54aa3dad1b76b857a19da1b2cc426/tree_sitter_typescript-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3cd752d70d8e5371fdac6a9a4df9d8924b63b6998d268586f7d374c9fba2a478", size = 286677 }, + { url = "https://files.pythonhosted.org/packages/8f/2f/1f36fda564518d84593f2740d5905ac127d590baf5c5753cef2a88a89c15/tree_sitter_typescript-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c7cc1b0ff5d91bac863b0e38b1578d5505e718156c9db577c8baea2557f66de8", size = 302008 }, + { url = "https://files.pythonhosted.org/packages/96/2d/975c2dad292aa9994f982eb0b69cc6fda0223e4b6c4ea714550477d8ec3a/tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b1eed5b0b3a8134e86126b00b743d667ec27c63fc9de1b7bb23168803879e31", size = 351987 }, + { url = "https://files.pythonhosted.org/packages/49/d1/a71c36da6e2b8a4ed5e2970819b86ef13ba77ac40d9e333cb17df6a2c5db/tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e96d36b85bcacdeb8ff5c2618d75593ef12ebaf1b4eace3477e2bdb2abb1752c", size = 344960 }, + { url = "https://files.pythonhosted.org/packages/7f/cb/f57b149d7beed1a85b8266d0c60ebe4c46e79c9ba56bc17b898e17daf88e/tree_sitter_typescript-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8d4f0f9bcb61ad7b7509d49a1565ff2cc363863644a234e1e0fe10960e55aea0", size = 340245 }, + { url = "https://files.pythonhosted.org/packages/8b/ab/dd84f0e2337296a5f09749f7b5483215d75c8fa9e33738522e5ed81f7254/tree_sitter_typescript-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:3f730b66396bc3e11811e4465c41ee45d9e9edd6de355a58bbbc49fa770da8f9", size = 278015 }, + { url = "https://files.pythonhosted.org/packages/9f/e4/81f9a935789233cf412a0ed5fe04c883841d2c8fb0b7e075958a35c65032/tree_sitter_typescript-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:05db58f70b95ef0ea126db5560f3775692f609589ed6f8dd0af84b7f19f1cbb7", size = 274052 }, ] [[package]] @@ -8796,16 +8779,16 @@ name = "triton" version = "3.6.0" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243, upload-time = "2026-01-20T16:16:07.857Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, - { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521, upload-time = "2026-01-20T16:16:13.321Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087, upload-time = "2026-01-20T16:16:18.989Z" }, - { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, - { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577, upload-time = "2026-01-20T16:16:25.426Z" }, - { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, - { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804, upload-time = "2026-01-20T16:16:31.528Z" }, - { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243 }, + { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850 }, + { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521 }, + { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450 }, + { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087 }, + { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296 }, + { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577 }, + { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063 }, + { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804 }, + { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994 }, ] [[package]] @@ -8823,9 +8806,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/7d/9081fd2c0146196546954c895b467986681d0e6fdcc42810a0780ee459a8/turbopuffer-1.19.0.tar.gz", hash = "sha256:6b7ba70dae2769dd21892b4535ed63fe1e4482a650f4d0f5239d1a8f2c101660", size = 326319, upload-time = "2026-03-18T23:26:57.44Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/7d/9081fd2c0146196546954c895b467986681d0e6fdcc42810a0780ee459a8/turbopuffer-1.19.0.tar.gz", hash = "sha256:6b7ba70dae2769dd21892b4535ed63fe1e4482a650f4d0f5239d1a8f2c101660", size = 326319 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/8b/509e5fc117eaeb7387709a8cedf471fbb943a8c0672608e2eddf317f4b02/turbopuffer-1.19.0-py3-none-any.whl", hash = "sha256:b0ad5eac6075ff2fe0d1a85bf5eb3013b4cbffc7f696b67121584ad33467a726", size = 117105, upload-time = "2026-03-18T23:26:58.726Z" }, + { url = "https://files.pythonhosted.org/packages/e2/8b/509e5fc117eaeb7387709a8cedf471fbb943a8c0672608e2eddf317f4b02/turbopuffer-1.19.0-py3-none-any.whl", hash = "sha256:b0ad5eac6075ff2fe0d1a85bf5eb3013b4cbffc7f696b67121584ad33467a726", size = 117105 }, ] [[package]] @@ -8843,9 +8826,9 @@ dependencies = [ { name = "rich" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/a8/949edebe3a82774c1ec34f637f5dd82d1cf22c25e963b7d63771083bbee5/twine-6.2.0.tar.gz", hash = "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf", size = 172262, upload-time = "2025-09-04T15:43:17.255Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/a8/949edebe3a82774c1ec34f637f5dd82d1cf22c25e963b7d63771083bbee5/twine-6.2.0.tar.gz", hash = "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf", size = 172262 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl", hash = "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", size = 42727, upload-time = "2025-09-04T15:43:15.994Z" }, + { url = "https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl", hash = "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", size = 42727 }, ] [[package]] @@ -8858,9 +8841,9 @@ dependencies = [ { name = "rich" }, { name = "shellingham" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f2/1e/a27cc02a0cd715118c71fa2aef2c687fdefc3c28d90fd0dd789c5118154c/typer-0.21.2.tar.gz", hash = "sha256:1abd95a3b675e17ff61b0838ac637fe9478d446d62ad17fa4bb81ea57cc54028", size = 120426, upload-time = "2026-02-10T19:33:46.182Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/1e/a27cc02a0cd715118c71fa2aef2c687fdefc3c28d90fd0dd789c5118154c/typer-0.21.2.tar.gz", hash = "sha256:1abd95a3b675e17ff61b0838ac637fe9478d446d62ad17fa4bb81ea57cc54028", size = 120426 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/cc/d59f893fbdfb5f58770c05febfc4086a46875f1084453621c35605cec946/typer-0.21.2-py3-none-any.whl", hash = "sha256:c3d8de54d00347ef90b82131ca946274f017cffb46683ae3883c360fa958f55c", size = 56728, upload-time = "2026-02-10T19:33:48.01Z" }, + { url = "https://files.pythonhosted.org/packages/b8/cc/d59f893fbdfb5f58770c05febfc4086a46875f1084453621c35605cec946/typer-0.21.2-py3-none-any.whl", hash = "sha256:c3d8de54d00347ef90b82131ca946274f017cffb46683ae3883c360fa958f55c", size = 56728 }, ] [[package]] @@ -8870,18 +8853,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/5f/2e3dbae6e21be6ae026563bad96cbf76602d73aa85ea09f13419ddbdabb4/types_requests-2.33.0.20260327.tar.gz", hash = "sha256:f4f74f0b44f059e3db420ff17bd1966e3587cdd34062fe38a23cda97868f8dd8", size = 23804, upload-time = "2026-03-27T04:23:38.737Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/5f/2e3dbae6e21be6ae026563bad96cbf76602d73aa85ea09f13419ddbdabb4/types_requests-2.33.0.20260327.tar.gz", hash = "sha256:f4f74f0b44f059e3db420ff17bd1966e3587cdd34062fe38a23cda97868f8dd8", size = 23804 } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/55/951e733616c92cb96b57554746d2f65f4464d080cc2cc093605f897aba89/types_requests-2.33.0.20260327-py3-none-any.whl", hash = "sha256:fde0712be6d7c9a4d490042d6323115baf872d9a71a22900809d0432de15776e", size = 20737, upload-time = "2026-03-27T04:23:37.813Z" }, + { url = "https://files.pythonhosted.org/packages/8c/55/951e733616c92cb96b57554746d2f65f4464d080cc2cc093605f897aba89/types_requests-2.33.0.20260327-py3-none-any.whl", hash = "sha256:fde0712be6d7c9a4d490042d6323115baf872d9a71a22900809d0432de15776e", size = 20737 }, ] [[package]] name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, ] [[package]] @@ -8892,9 +8875,9 @@ dependencies = [ { name = "mypy-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825, upload-time = "2023-05-24T20:25:47.612Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825 } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" }, + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 }, ] [[package]] @@ -8904,42 +8887,42 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949 } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611 }, ] [[package]] name = "typst" version = "0.14.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0e/17/011059074fe6c51ed775991d5066c73443f17d49b3d4ab9c1a969dcdb5cb/typst-0.14.8.tar.gz", hash = "sha256:8ffb8d5896aa6a20a7b88ae3fa1dfcf062fdd09b5b6a0a164f92f78ad1a2d8cd", size = 62369, upload-time = "2026-02-08T02:31:21.753Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/17/011059074fe6c51ed775991d5066c73443f17d49b3d4ab9c1a969dcdb5cb/typst-0.14.8.tar.gz", hash = "sha256:8ffb8d5896aa6a20a7b88ae3fa1dfcf062fdd09b5b6a0a164f92f78ad1a2d8cd", size = 62369 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/67/af5551e95261fc425f6dbf241ec08bf1172fd10ef239787ff6e009bb2f08/typst-0.14.8-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:4697b9de12d7b1bc85209960e1ef7e2c4947cffd7d6ef68201aea03597cf38bd", size = 22935370, upload-time = "2026-02-08T02:30:30.418Z" }, - { url = "https://files.pythonhosted.org/packages/6f/93/cbb32c7e830a806105ee0f6d9b6c780f2736a9c75d8121602e7842a316d2/typst-0.14.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ecb523ff7e3eb68667ad693ff4c460ac58aedfbeb6514054efce2718e7563f", size = 22624078, upload-time = "2026-02-08T02:30:33.762Z" }, - { url = "https://files.pythonhosted.org/packages/77/38/070c068442a8be93125366b27e5cf1a6b1dd62c85dab62bd6d4355643d29/typst-0.14.8-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9db137ca037bd12c0ebbbbfa1190fffaa75a2043d04adacb273cc98f0265a32", size = 26894087, upload-time = "2026-02-08T02:30:36.876Z" }, - { url = "https://files.pythonhosted.org/packages/ee/32/8754413c4cdf631c51e16690775dcfd28e783c1ccc0efc71d92ef73e0db3/typst-0.14.8-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d8c4ac751c3480b0fcfc7fce273025bb7392654db5a3aa65904f8678192c54f8", size = 26489748, upload-time = "2026-02-08T02:30:40.169Z" }, - { url = "https://files.pythonhosted.org/packages/27/2b/3b1256033c7b971d0c79af41fadff552c1df7a9f9774a540f1a2ede97937/typst-0.14.8-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37da60ec4afcd82b55664612aab10cac11a8ebc075686057705261de9e901523", size = 28023293, upload-time = "2026-02-08T02:30:43.222Z" }, - { url = "https://files.pythonhosted.org/packages/61/1b/8769c89998299525e4b04fddce1b15977d18051695c65760203b55f7ed47/typst-0.14.8-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1cfbc313ba3b883da8c45233506766a503da307057a5d8d39e360023733c463", size = 27109055, upload-time = "2026-02-08T02:30:46.214Z" }, - { url = "https://files.pythonhosted.org/packages/51/97/b1f43e29051401289b6ef37398eb83d78584f52e0b213f8675b9b10b0c0b/typst-0.14.8-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b509e7a599dd07e36e18495f0258511de527f5e0dc145622025d204c84db5246", size = 26017464, upload-time = "2026-02-08T02:30:49.271Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ca/44732fc1e486be822ba65ee9a02f0bc5f28d1cb08284c9dd1286d975f9ce/typst-0.14.8-cp314-cp314t-win_amd64.whl", hash = "sha256:10710c58dbc8820a954970ba5d0af5611c7c57f8ddacfebb1a85ddb6449f01eb", size = 21471708, upload-time = "2026-02-08T02:30:54.185Z" }, - { url = "https://files.pythonhosted.org/packages/5b/cb/e49219a75d39ce866ae5d64e0a1d8d712b394ed3a1e7de3a8f4a35cde78e/typst-0.14.8-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f47fe029f6ebe907f981ce0cb5208eab27eaf7342e319e6c798ac1dbae976f58", size = 22936285, upload-time = "2026-02-08T02:30:57.338Z" }, - { url = "https://files.pythonhosted.org/packages/f8/6b/d36f312c32b70303abd88d0abe6ffb50f8f7fcc0b457c914c78d791ed934/typst-0.14.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:aba11243463f6994ca1140b8515e70be1a98fd3025ae3211b84103499b0c5a5a", size = 22632767, upload-time = "2026-02-08T02:31:00.454Z" }, - { url = "https://files.pythonhosted.org/packages/6f/b4/87d2d24078b94645ba8788c8b4a5bbab6a3c779370141c31a02e2003ee0f/typst-0.14.8-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544fcd9ce55b140115d7442b3661c45897778650c307e2eb0749efed29bbfcea", size = 26907232, upload-time = "2026-02-08T02:31:03.528Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e8/3efdebcf37639daa4799e7a4c833a280f14685f7e6058fed576c6fb2e722/typst-0.14.8-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a296f85bf0d27043b031d1d2d74a34802e4876a8936f70784fbe99021b0dad4d", size = 26501791, upload-time = "2026-02-08T02:31:06.898Z" }, - { url = "https://files.pythonhosted.org/packages/7e/28/094d4b9f0ff4ee81f88eee2df00dbcfbd961070df981973bc385a1544ff8/typst-0.14.8-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e3891a2e5551017c9030dd6de31587a29b97c18464df6bcff05f30f7cdab677", size = 28028881, upload-time = "2026-02-08T02:31:10.437Z" }, - { url = "https://files.pythonhosted.org/packages/11/a1/15cd399dfc5ce0ea9e05d5bbc274c95f8ecababc04b4210bae8d583fe454/typst-0.14.8-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a19cf938607c73fd8c5245a7cb32c94af413080a3d747fcf7e16df88713c686", size = 27128399, upload-time = "2026-02-08T02:31:13.597Z" }, - { url = "https://files.pythonhosted.org/packages/4c/6f/ff1c58dac9245d4c355bfced006090b14a2f17497e9cf79a84d9d720663a/typst-0.14.8-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12766a83e390377008722a8c80afdd9195a297261fd3c9d1f3720f9aecd2b19", size = 26026753, upload-time = "2026-02-08T02:31:16.591Z" }, - { url = "https://files.pythonhosted.org/packages/a9/42/db15d775c09f0da92191ea1b50cee056e46b599bb5524e2d8ff51f973765/typst-0.14.8-cp38-abi3-win_amd64.whl", hash = "sha256:66eb2ebfe13275cf2a63ed7ff261eb5af3da5293077a5d6ca16e27a96d0d2f5e", size = 21475900, upload-time = "2026-02-08T02:31:19.484Z" }, + { url = "https://files.pythonhosted.org/packages/d2/67/af5551e95261fc425f6dbf241ec08bf1172fd10ef239787ff6e009bb2f08/typst-0.14.8-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:4697b9de12d7b1bc85209960e1ef7e2c4947cffd7d6ef68201aea03597cf38bd", size = 22935370 }, + { url = "https://files.pythonhosted.org/packages/6f/93/cbb32c7e830a806105ee0f6d9b6c780f2736a9c75d8121602e7842a316d2/typst-0.14.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ecb523ff7e3eb68667ad693ff4c460ac58aedfbeb6514054efce2718e7563f", size = 22624078 }, + { url = "https://files.pythonhosted.org/packages/77/38/070c068442a8be93125366b27e5cf1a6b1dd62c85dab62bd6d4355643d29/typst-0.14.8-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9db137ca037bd12c0ebbbbfa1190fffaa75a2043d04adacb273cc98f0265a32", size = 26894087 }, + { url = "https://files.pythonhosted.org/packages/ee/32/8754413c4cdf631c51e16690775dcfd28e783c1ccc0efc71d92ef73e0db3/typst-0.14.8-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d8c4ac751c3480b0fcfc7fce273025bb7392654db5a3aa65904f8678192c54f8", size = 26489748 }, + { url = "https://files.pythonhosted.org/packages/27/2b/3b1256033c7b971d0c79af41fadff552c1df7a9f9774a540f1a2ede97937/typst-0.14.8-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37da60ec4afcd82b55664612aab10cac11a8ebc075686057705261de9e901523", size = 28023293 }, + { url = "https://files.pythonhosted.org/packages/61/1b/8769c89998299525e4b04fddce1b15977d18051695c65760203b55f7ed47/typst-0.14.8-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1cfbc313ba3b883da8c45233506766a503da307057a5d8d39e360023733c463", size = 27109055 }, + { url = "https://files.pythonhosted.org/packages/51/97/b1f43e29051401289b6ef37398eb83d78584f52e0b213f8675b9b10b0c0b/typst-0.14.8-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b509e7a599dd07e36e18495f0258511de527f5e0dc145622025d204c84db5246", size = 26017464 }, + { url = "https://files.pythonhosted.org/packages/5b/ca/44732fc1e486be822ba65ee9a02f0bc5f28d1cb08284c9dd1286d975f9ce/typst-0.14.8-cp314-cp314t-win_amd64.whl", hash = "sha256:10710c58dbc8820a954970ba5d0af5611c7c57f8ddacfebb1a85ddb6449f01eb", size = 21471708 }, + { url = "https://files.pythonhosted.org/packages/5b/cb/e49219a75d39ce866ae5d64e0a1d8d712b394ed3a1e7de3a8f4a35cde78e/typst-0.14.8-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f47fe029f6ebe907f981ce0cb5208eab27eaf7342e319e6c798ac1dbae976f58", size = 22936285 }, + { url = "https://files.pythonhosted.org/packages/f8/6b/d36f312c32b70303abd88d0abe6ffb50f8f7fcc0b457c914c78d791ed934/typst-0.14.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:aba11243463f6994ca1140b8515e70be1a98fd3025ae3211b84103499b0c5a5a", size = 22632767 }, + { url = "https://files.pythonhosted.org/packages/6f/b4/87d2d24078b94645ba8788c8b4a5bbab6a3c779370141c31a02e2003ee0f/typst-0.14.8-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544fcd9ce55b140115d7442b3661c45897778650c307e2eb0749efed29bbfcea", size = 26907232 }, + { url = "https://files.pythonhosted.org/packages/bc/e8/3efdebcf37639daa4799e7a4c833a280f14685f7e6058fed576c6fb2e722/typst-0.14.8-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a296f85bf0d27043b031d1d2d74a34802e4876a8936f70784fbe99021b0dad4d", size = 26501791 }, + { url = "https://files.pythonhosted.org/packages/7e/28/094d4b9f0ff4ee81f88eee2df00dbcfbd961070df981973bc385a1544ff8/typst-0.14.8-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e3891a2e5551017c9030dd6de31587a29b97c18464df6bcff05f30f7cdab677", size = 28028881 }, + { url = "https://files.pythonhosted.org/packages/11/a1/15cd399dfc5ce0ea9e05d5bbc274c95f8ecababc04b4210bae8d583fe454/typst-0.14.8-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a19cf938607c73fd8c5245a7cb32c94af413080a3d747fcf7e16df88713c686", size = 27128399 }, + { url = "https://files.pythonhosted.org/packages/4c/6f/ff1c58dac9245d4c355bfced006090b14a2f17497e9cf79a84d9d720663a/typst-0.14.8-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12766a83e390377008722a8c80afdd9195a297261fd3c9d1f3720f9aecd2b19", size = 26026753 }, + { url = "https://files.pythonhosted.org/packages/a9/42/db15d775c09f0da92191ea1b50cee056e46b599bb5524e2d8ff51f973765/typst-0.14.8-cp38-abi3-win_amd64.whl", hash = "sha256:66eb2ebfe13275cf2a63ed7ff261eb5af3da5293077a5d6ca16e27a96d0d2f5e", size = 21475900 }, ] [[package]] name = "tzdata" version = "2025.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521 }, ] [[package]] @@ -8949,9 +8932,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026 }, ] [[package]] @@ -8983,9 +8966,9 @@ dependencies = [ { name = "unstructured-client" }, { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/e6/fbef61517d130af1def3b81681e253a5679f19de2f04e439afbbf1f021e0/unstructured-0.21.5.tar.gz", hash = "sha256:3e220d0c2b9c8ec12c99767162b95ab0acfca75e979b82c66c15ca15caa60139", size = 1501811, upload-time = "2026-02-24T15:29:27.84Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/e6/fbef61517d130af1def3b81681e253a5679f19de2f04e439afbbf1f021e0/unstructured-0.21.5.tar.gz", hash = "sha256:3e220d0c2b9c8ec12c99767162b95ab0acfca75e979b82c66c15ca15caa60139", size = 1501811 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/b6/7e6dd60bde81d5a4d4ddf426f566a5d1b4c30490053caed69e47f55c676f/unstructured-0.21.5-py3-none-any.whl", hash = "sha256:d88a277c368462b69a8843b9cb22476f3cc4d0a58455536520359387224b3366", size = 1554925, upload-time = "2026-02-24T15:29:26.009Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b6/7e6dd60bde81d5a4d4ddf426f566a5d1b4c30490053caed69e47f55c676f/unstructured-0.21.5-py3-none-any.whl", hash = "sha256:d88a277c368462b69a8843b9cb22476f3cc4d0a58455536520359387224b3366", size = 1554925 }, ] [package.optional-dependencies] @@ -9023,9 +9006,9 @@ dependencies = [ { name = "pypdfium2" }, { name = "requests-toolbelt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/ca/73904d53e486af2f1d9d8baaf43d2a74b3d67e5f533834f5d51056471339/unstructured_client-0.42.12.tar.gz", hash = "sha256:50eb6717d8c6513b14b309fce8d6551354e433da982b7a9161a889d8e6a11166", size = 94714, upload-time = "2026-03-25T20:24:21.528Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/ca/73904d53e486af2f1d9d8baaf43d2a74b3d67e5f533834f5d51056471339/unstructured_client-0.42.12.tar.gz", hash = "sha256:50eb6717d8c6513b14b309fce8d6551354e433da982b7a9161a889d8e6a11166", size = 94714 } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/80/fbf02ec3c566a3e383a5649385096834a2a981832f1432c3a8797b29185a/unstructured_client-0.42.12-py3-none-any.whl", hash = "sha256:fe6f217066a0c308ba7213185524506dbfc3bb9d35df0ab79549291e9728a012", size = 220154, upload-time = "2026-03-25T20:24:20.288Z" }, + { url = "https://files.pythonhosted.org/packages/21/80/fbf02ec3c566a3e383a5649385096834a2a981832f1432c3a8797b29185a/unstructured_client-0.42.12-py3-none-any.whl", hash = "sha256:fe6f217066a0c308ba7213185524506dbfc3bb9d35df0ab79549291e9728a012", size = 220154 }, ] [[package]] @@ -9050,9 +9033,9 @@ dependencies = [ { name = "torch", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, { name = "transformers", marker = "python_full_version < '3.13' or sys_platform != 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/10/8f3bccfa9f1e0101a402ae1f529e07876541c6b18004747f0e793ed41f9e/unstructured_inference-1.2.0.tar.gz", hash = "sha256:19ca28512f3649c70a759cf2a4e98663e942a1b83c1acdb9506b0445f4862f23", size = 45732, upload-time = "2026-01-30T20:57:58.019Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/10/8f3bccfa9f1e0101a402ae1f529e07876541c6b18004747f0e793ed41f9e/unstructured_inference-1.2.0.tar.gz", hash = "sha256:19ca28512f3649c70a759cf2a4e98663e942a1b83c1acdb9506b0445f4862f23", size = 45732 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/3b/349cd091b590a6f1dbfebcb5fee0ea7b0b6ef6520df58794c9582567a24f/unstructured_inference-1.2.0-py3-none-any.whl", hash = "sha256:60a1635aa8e97a9e7daed1a129836f51c26588e0d2062c9cc6a5a17e6d40cb6a", size = 49443, upload-time = "2026-01-30T20:57:56.617Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3b/349cd091b590a6f1dbfebcb5fee0ea7b0b6ef6520df58794c9582567a24f/unstructured_inference-1.2.0-py3-none-any.whl", hash = "sha256:60a1635aa8e97a9e7daed1a129836f51c26588e0d2062c9cc6a5a17e6d40cb6a", size = 49443 }, ] [[package]] @@ -9063,49 +9046,49 @@ dependencies = [ { name = "packaging" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/b1/4b3a976b76549f22c3f5493a622603617cbe08804402978e1dac9c387997/unstructured.pytesseract-0.3.15.tar.gz", hash = "sha256:4b81bc76cfff4e2ef37b04863f0e48bd66184c0b39c3b2b4e017483bca1a7394", size = 15703, upload-time = "2025-03-05T00:59:17.516Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/b1/4b3a976b76549f22c3f5493a622603617cbe08804402978e1dac9c387997/unstructured.pytesseract-0.3.15.tar.gz", hash = "sha256:4b81bc76cfff4e2ef37b04863f0e48bd66184c0b39c3b2b4e017483bca1a7394", size = 15703 } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/6d/adb955ecf60811a3735d508974bbb5358e7745b635dc001329267529c6f2/unstructured.pytesseract-0.3.15-py3-none-any.whl", hash = "sha256:a3f505c5efb7ff9f10379051a7dd6aa624b3be6b0f023ed6767cc80d0b1613d1", size = 14992, upload-time = "2025-03-05T00:59:15.962Z" }, + { url = "https://files.pythonhosted.org/packages/10/6d/adb955ecf60811a3735d508974bbb5358e7745b635dc001329267529c6f2/unstructured.pytesseract-0.3.15-py3-none-any.whl", hash = "sha256:a3f505c5efb7ff9f10379051a7dd6aa624b3be6b0f023ed6767cc80d0b1613d1", size = 14992 }, ] [[package]] name = "uritemplate" version = "4.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267, upload-time = "2025-06-02T15:12:06.318Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" }, + { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488 }, ] [[package]] name = "urllib3" version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556 } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584 }, ] [[package]] name = "uuid-utils" version = "0.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/d1/38a573f0c631c062cf42fa1f5d021d4dd3c31fb23e4376e4b56b0c9fbbed/uuid_utils-0.14.1.tar.gz", hash = "sha256:9bfc95f64af80ccf129c604fb6b8ca66c6f256451e32bc4570f760e4309c9b69", size = 22195, upload-time = "2026-02-20T22:50:38.833Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/d1/38a573f0c631c062cf42fa1f5d021d4dd3c31fb23e4376e4b56b0c9fbbed/uuid_utils-0.14.1.tar.gz", hash = "sha256:9bfc95f64af80ccf129c604fb6b8ca66c6f256451e32bc4570f760e4309c9b69", size = 22195 } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/b7/add4363039a34506a58457d96d4aa2126061df3a143eb4d042aedd6a2e76/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:93a3b5dc798a54a1feb693f2d1cb4cf08258c32ff05ae4929b5f0a2ca624a4f0", size = 604679, upload-time = "2026-02-20T22:50:27.469Z" }, - { url = "https://files.pythonhosted.org/packages/dd/84/d1d0bef50d9e66d31b2019997c741b42274d53dde2e001b7a83e9511c339/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ccd65a4b8e83af23eae5e56d88034b2fe7264f465d3e830845f10d1591b81741", size = 309346, upload-time = "2026-02-20T22:50:31.857Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ed/b6d6fd52a6636d7c3eddf97d68da50910bf17cd5ac221992506fb56cf12e/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b56b0cacd81583834820588378e432b0696186683b813058b707aedc1e16c4b1", size = 344714, upload-time = "2026-02-20T22:50:42.642Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a7/a19a1719fb626fe0b31882db36056d44fe904dc0cf15b06fdf56b2679cf7/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb3cf14de789097320a3c56bfdfdd51b1225d11d67298afbedee7e84e3837c96", size = 350914, upload-time = "2026-02-20T22:50:36.487Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fc/f6690e667fdc3bb1a73f57951f97497771c56fe23e3d302d7404be394d4f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e0854a90d67f4b0cc6e54773deb8be618f4c9bad98d3326f081423b5d14fae", size = 482609, upload-time = "2026-02-20T22:50:37.511Z" }, - { url = "https://files.pythonhosted.org/packages/54/6e/dcd3fa031320921a12ec7b4672dea3bd1dd90ddffa363a91831ba834d559/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6743ba194de3910b5feb1a62590cd2587e33a73ab6af8a01b642ceb5055862", size = 345699, upload-time = "2026-02-20T22:50:46.87Z" }, - { url = "https://files.pythonhosted.org/packages/04/28/e5220204b58b44ac0047226a9d016a113fde039280cc8732d9e6da43b39f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:043fb58fde6cf1620a6c066382f04f87a8e74feb0f95a585e4ed46f5d44af57b", size = 372205, upload-time = "2026-02-20T22:50:28.438Z" }, - { url = "https://files.pythonhosted.org/packages/c7/d9/3d2eb98af94b8dfffc82b6a33b4dfc87b0a5de2c68a28f6dde0db1f8681b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c915d53f22945e55fe0d3d3b0b87fd965a57f5fd15666fd92d6593a73b1dd297", size = 521836, upload-time = "2026-02-20T22:50:23.057Z" }, - { url = "https://files.pythonhosted.org/packages/a8/15/0eb106cc6fe182f7577bc0ab6e2f0a40be247f35c5e297dbf7bbc460bd02/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0972488e3f9b449e83f006ead5a0e0a33ad4a13e4462e865b7c286ab7d7566a3", size = 625260, upload-time = "2026-02-20T22:50:25.949Z" }, - { url = "https://files.pythonhosted.org/packages/3c/17/f539507091334b109e7496830af2f093d9fc8082411eafd3ece58af1f8ba/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1c238812ae0c8ffe77d8d447a32c6dfd058ea4631246b08b5a71df586ff08531", size = 587824, upload-time = "2026-02-20T22:50:35.225Z" }, - { url = "https://files.pythonhosted.org/packages/2e/c2/d37a7b2e41f153519367d4db01f0526e0d4b06f1a4a87f1c5dfca5d70a8b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:bec8f8ef627af86abf8298e7ec50926627e29b34fa907fcfbedb45aaa72bca43", size = 551407, upload-time = "2026-02-20T22:50:44.915Z" }, - { url = "https://files.pythonhosted.org/packages/65/36/2d24b2cbe78547c6532da33fb8613debd3126eccc33a6374ab788f5e46e9/uuid_utils-0.14.1-cp39-abi3-win32.whl", hash = "sha256:b54d6aa6252d96bac1fdbc80d26ba71bad9f220b2724d692ad2f2310c22ef523", size = 183476, upload-time = "2026-02-20T22:50:32.745Z" }, - { url = "https://files.pythonhosted.org/packages/83/92/2d7e90df8b1a69ec4cff33243ce02b7a62f926ef9e2f0eca5a026889cd73/uuid_utils-0.14.1-cp39-abi3-win_amd64.whl", hash = "sha256:fc27638c2ce267a0ce3e06828aff786f91367f093c80625ee21dad0208e0f5ba", size = 187147, upload-time = "2026-02-20T22:50:45.807Z" }, - { url = "https://files.pythonhosted.org/packages/d9/26/529f4beee17e5248e37e0bc17a2761d34c0fa3b1e5729c88adb2065bae6e/uuid_utils-0.14.1-cp39-abi3-win_arm64.whl", hash = "sha256:b04cb49b42afbc4ff8dbc60cf054930afc479d6f4dd7f1ec3bbe5dbfdde06b7a", size = 188132, upload-time = "2026-02-20T22:50:41.718Z" }, + { url = "https://files.pythonhosted.org/packages/43/b7/add4363039a34506a58457d96d4aa2126061df3a143eb4d042aedd6a2e76/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:93a3b5dc798a54a1feb693f2d1cb4cf08258c32ff05ae4929b5f0a2ca624a4f0", size = 604679 }, + { url = "https://files.pythonhosted.org/packages/dd/84/d1d0bef50d9e66d31b2019997c741b42274d53dde2e001b7a83e9511c339/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ccd65a4b8e83af23eae5e56d88034b2fe7264f465d3e830845f10d1591b81741", size = 309346 }, + { url = "https://files.pythonhosted.org/packages/ef/ed/b6d6fd52a6636d7c3eddf97d68da50910bf17cd5ac221992506fb56cf12e/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b56b0cacd81583834820588378e432b0696186683b813058b707aedc1e16c4b1", size = 344714 }, + { url = "https://files.pythonhosted.org/packages/a8/a7/a19a1719fb626fe0b31882db36056d44fe904dc0cf15b06fdf56b2679cf7/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb3cf14de789097320a3c56bfdfdd51b1225d11d67298afbedee7e84e3837c96", size = 350914 }, + { url = "https://files.pythonhosted.org/packages/1d/fc/f6690e667fdc3bb1a73f57951f97497771c56fe23e3d302d7404be394d4f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e0854a90d67f4b0cc6e54773deb8be618f4c9bad98d3326f081423b5d14fae", size = 482609 }, + { url = "https://files.pythonhosted.org/packages/54/6e/dcd3fa031320921a12ec7b4672dea3bd1dd90ddffa363a91831ba834d559/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6743ba194de3910b5feb1a62590cd2587e33a73ab6af8a01b642ceb5055862", size = 345699 }, + { url = "https://files.pythonhosted.org/packages/04/28/e5220204b58b44ac0047226a9d016a113fde039280cc8732d9e6da43b39f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:043fb58fde6cf1620a6c066382f04f87a8e74feb0f95a585e4ed46f5d44af57b", size = 372205 }, + { url = "https://files.pythonhosted.org/packages/c7/d9/3d2eb98af94b8dfffc82b6a33b4dfc87b0a5de2c68a28f6dde0db1f8681b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c915d53f22945e55fe0d3d3b0b87fd965a57f5fd15666fd92d6593a73b1dd297", size = 521836 }, + { url = "https://files.pythonhosted.org/packages/a8/15/0eb106cc6fe182f7577bc0ab6e2f0a40be247f35c5e297dbf7bbc460bd02/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0972488e3f9b449e83f006ead5a0e0a33ad4a13e4462e865b7c286ab7d7566a3", size = 625260 }, + { url = "https://files.pythonhosted.org/packages/3c/17/f539507091334b109e7496830af2f093d9fc8082411eafd3ece58af1f8ba/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1c238812ae0c8ffe77d8d447a32c6dfd058ea4631246b08b5a71df586ff08531", size = 587824 }, + { url = "https://files.pythonhosted.org/packages/2e/c2/d37a7b2e41f153519367d4db01f0526e0d4b06f1a4a87f1c5dfca5d70a8b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:bec8f8ef627af86abf8298e7ec50926627e29b34fa907fcfbedb45aaa72bca43", size = 551407 }, + { url = "https://files.pythonhosted.org/packages/65/36/2d24b2cbe78547c6532da33fb8613debd3126eccc33a6374ab788f5e46e9/uuid_utils-0.14.1-cp39-abi3-win32.whl", hash = "sha256:b54d6aa6252d96bac1fdbc80d26ba71bad9f220b2724d692ad2f2310c22ef523", size = 183476 }, + { url = "https://files.pythonhosted.org/packages/83/92/2d7e90df8b1a69ec4cff33243ce02b7a62f926ef9e2f0eca5a026889cd73/uuid_utils-0.14.1-cp39-abi3-win_amd64.whl", hash = "sha256:fc27638c2ce267a0ce3e06828aff786f91367f093c80625ee21dad0208e0f5ba", size = 187147 }, + { url = "https://files.pythonhosted.org/packages/d9/26/529f4beee17e5248e37e0bc17a2761d34c0fa3b1e5729c88adb2065bae6e/uuid_utils-0.14.1-cp39-abi3-win_arm64.whl", hash = "sha256:b04cb49b42afbc4ff8dbc60cf054930afc479d6f4dd7f1ec3bbe5dbfdde06b7a", size = 188132 }, ] [[package]] @@ -9116,9 +9099,9 @@ dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e3/ad/4a96c425be6fb67e0621e62d86c402b4a17ab2be7f7c055d9bd2f638b9e2/uvicorn-0.42.0.tar.gz", hash = "sha256:9b1f190ce15a2dd22e7758651d9b6d12df09a13d51ba5bf4fc33c383a48e1775", size = 85393, upload-time = "2026-03-16T06:19:50.077Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/ad/4a96c425be6fb67e0621e62d86c402b4a17ab2be7f7c055d9bd2f638b9e2/uvicorn-0.42.0.tar.gz", hash = "sha256:9b1f190ce15a2dd22e7758651d9b6d12df09a13d51ba5bf4fc33c383a48e1775", size = 85393 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/89/f8827ccff89c1586027a105e5630ff6139a64da2515e24dafe860bd9ae4d/uvicorn-0.42.0-py3-none-any.whl", hash = "sha256:96c30f5c7abe6f74ae8900a70e92b85ad6613b745d4879eb9b16ccad15645359", size = 68830, upload-time = "2026-03-16T06:19:48.325Z" }, + { url = "https://files.pythonhosted.org/packages/0a/89/f8827ccff89c1586027a105e5630ff6139a64da2515e24dafe860bd9ae4d/uvicorn-0.42.0-py3-none-any.whl", hash = "sha256:96c30f5c7abe6f74ae8900a70e92b85ad6613b745d4879eb9b16ccad15645359", size = 68830 }, ] [package.optional-dependencies] @@ -9136,41 +9119,41 @@ standard = [ name = "uvloop" version = "0.22.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, - { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, - { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, - { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, - { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, - { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, - { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, - { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, - { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, - { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, - { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, - { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, - { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, - { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, - { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, - { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, - { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, - { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, - { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, - { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, - { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936 }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769 }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413 }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307 }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970 }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343 }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611 }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811 }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562 }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890 }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472 }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051 }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067 }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423 }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437 }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101 }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158 }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360 }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790 }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783 }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548 }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065 }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384 }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730 }, ] [[package]] name = "validators" version = "0.35.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/66/a435d9ae49850b2f071f7ebd8119dd4e84872b01630d6736761e6e7fd847/validators-0.35.0.tar.gz", hash = "sha256:992d6c48a4e77c81f1b4daba10d16c3a9bb0dbb79b3a19ea847ff0928e70497a", size = 73399, upload-time = "2025-05-01T05:42:06.7Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/66/a435d9ae49850b2f071f7ebd8119dd4e84872b01630d6736761e6e7fd847/validators-0.35.0.tar.gz", hash = "sha256:992d6c48a4e77c81f1b4daba10d16c3a9bb0dbb79b3a19ea847ff0928e70497a", size = 73399 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/6e/3e955517e22cbdd565f2f8b2e73d52528b14b8bcfdb04f62466b071de847/validators-0.35.0-py3-none-any.whl", hash = "sha256:e8c947097eae7892cb3d26868d637f79f47b4a0554bc6b80065dfe5aac3705dd", size = 44712, upload-time = "2025-05-01T05:42:04.203Z" }, + { url = "https://files.pythonhosted.org/packages/fa/6e/3e955517e22cbdd565f2f8b2e73d52528b14b8bcfdb04f62466b071de847/validators-0.35.0-py3-none-any.whl", hash = "sha256:e8c947097eae7892cb3d26868d637f79f47b4a0554bc6b80065dfe5aac3705dd", size = 44712 }, ] [[package]] @@ -9184,15 +9167,15 @@ dependencies = [ { name = "psycopg2-binary" }, { name = "sqlalchemy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/87/9fb55aff1e18278c2a0d93ba48432e060086702e258e7e13068a31376548/vecs-0.4.5.tar.gz", hash = "sha256:7cd3ab65cf88f5869d49f70ae7385e844c4915700da1f2299c938afa56148cb6", size = 22036, upload-time = "2024-12-13T20:53:50.983Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/87/9fb55aff1e18278c2a0d93ba48432e060086702e258e7e13068a31376548/vecs-0.4.5.tar.gz", hash = "sha256:7cd3ab65cf88f5869d49f70ae7385e844c4915700da1f2299c938afa56148cb6", size = 22036 } [[package]] name = "vine" version = "5.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980, upload-time = "2023-11-05T08:46:53.857Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980 } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636, upload-time = "2023-11-05T08:46:51.205Z" }, + { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636 }, ] [[package]] @@ -9211,9 +9194,9 @@ dependencies = [ { name = "tenacity" }, { name = "tokenizers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/16/1b46b3cd401e1717a68197c1fe336d7bb4e0a1833f8105e1738f5b1add05/voyageai-0.3.7.tar.gz", hash = "sha256:826cd97f97223f42b5babc5c459c9c80f3a8215ce5c0e007b0b276550f790d24", size = 26485, upload-time = "2025-12-16T18:43:05.26Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/16/1b46b3cd401e1717a68197c1fe336d7bb4e0a1833f8105e1738f5b1add05/voyageai-0.3.7.tar.gz", hash = "sha256:826cd97f97223f42b5babc5c459c9c80f3a8215ce5c0e007b0b276550f790d24", size = 26485 } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/64/89f6325666d6836979f94ac88b96fefc7527e02e61abc81359843585e088/voyageai-0.3.7-py3-none-any.whl", hash = "sha256:909f6c033001e5a3b3caf970525bf3614a1bfef9003cf3c3b68207dfdb53e86d", size = 34691, upload-time = "2025-12-16T18:43:04.073Z" }, + { url = "https://files.pythonhosted.org/packages/60/64/89f6325666d6836979f94ac88b96fefc7527e02e61abc81359843585e088/voyageai-0.3.7-py3-none-any.whl", hash = "sha256:909f6c033001e5a3b3caf970525bf3614a1bfef9003cf3c3b68207dfdb53e86d", size = 34691 }, ] [[package]] @@ -9223,9 +9206,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/f9/054e6e2f1071e963b5e746b48d1e3727470b2a490834d18ad92364929db3/wasabi-1.1.3.tar.gz", hash = "sha256:4bb3008f003809db0c3e28b4daf20906ea871a2bb43f9914197d540f4f2e0878", size = 30391, upload-time = "2024-05-31T16:56:18.99Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/f9/054e6e2f1071e963b5e746b48d1e3727470b2a490834d18ad92364929db3/wasabi-1.1.3.tar.gz", hash = "sha256:4bb3008f003809db0c3e28b4daf20906ea871a2bb43f9914197d540f4f2e0878", size = 30391 } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/7c/34330a89da55610daa5f245ddce5aab81244321101614751e7537f125133/wasabi-1.1.3-py3-none-any.whl", hash = "sha256:f76e16e8f7e79f8c4c8be49b4024ac725713ab10cd7f19350ad18a8e3f71728c", size = 27880, upload-time = "2024-05-31T16:56:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/06/7c/34330a89da55610daa5f245ddce5aab81244321101614751e7537f125133/wasabi-1.1.3-py3-none-any.whl", hash = "sha256:f76e16e8f7e79f8c4c8be49b4024ac725713ab10cd7f19350ad18a8e3f71728c", size = 27880 }, ] [[package]] @@ -9235,67 +9218,67 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440 } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, - { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, - { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, - { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, - { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, - { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, - { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, - { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, - { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, - { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, - { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, - { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, - { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, - { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, - { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, - { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, - { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, - { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, - { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, - { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, - { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, - { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, - { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, - { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, - { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, - { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, - { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, - { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, - { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, - { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, - { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, - { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, - { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, - { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, - { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, - { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, - { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, - { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, - { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, - { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, - { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, - { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, - { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, - { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, - { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, - { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, - { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, - { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, - { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, - { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745 }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769 }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374 }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485 }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813 }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816 }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186 }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812 }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196 }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657 }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042 }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410 }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209 }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321 }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783 }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279 }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405 }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976 }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506 }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936 }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147 }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007 }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280 }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056 }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162 }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909 }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389 }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964 }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114 }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264 }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877 }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176 }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577 }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425 }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826 }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208 }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315 }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869 }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919 }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845 }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027 }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615 }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836 }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099 }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626 }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519 }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078 }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664 }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154 }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510 }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408 }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968 }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096 }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040 }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847 }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072 }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104 }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112 }, ] [[package]] @@ -9305,18 +9288,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bracex" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421, upload-time = "2025-06-22T19:14:02.49Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421 } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854, upload-time = "2025-06-22T19:14:00.978Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854 }, ] [[package]] name = "wcwidth" version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684 } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189 }, ] [[package]] @@ -9334,9 +9317,9 @@ dependencies = [ { name = "typer" }, { name = "wasabi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/e5/e272bb9a045105a1fdf4b798d8086f5932a178f4d738f17a74f5c9e0ae9a/weasel-1.0.0.tar.gz", hash = "sha256:7b129b44c90cc543b760532974ca1e4eb30dad2aa2026f57bdce66354ae610fc", size = 38682, upload-time = "2026-03-20T08:10:25.266Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/e5/e272bb9a045105a1fdf4b798d8086f5932a178f4d738f17a74f5c9e0ae9a/weasel-1.0.0.tar.gz", hash = "sha256:7b129b44c90cc543b760532974ca1e4eb30dad2aa2026f57bdce66354ae610fc", size = 38682 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/07/57ebf7a6798b016c064bd0ca81b4c6a99daa4dc377b898bc7b41eb6b5af0/weasel-1.0.0-py3-none-any.whl", hash = "sha256:89518acee027f49d743126c3502d35e6dd14f5768be5c37c9af47c171b6005cc", size = 50713, upload-time = "2026-03-20T08:10:23.637Z" }, + { url = "https://files.pythonhosted.org/packages/0a/07/57ebf7a6798b016c064bd0ca81b4c6a99daa4dc377b898bc7b41eb6b5af0/weasel-1.0.0-py3-none-any.whl", hash = "sha256:89518acee027f49d743126c3502d35e6dd14f5768be5c37c9af47c171b6005cc", size = 50713 }, ] [[package]] @@ -9352,217 +9335,217 @@ dependencies = [ { name = "pydantic" }, { name = "validators" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/1c/82b560254f612f95b644849d86e092da6407f17965d61e22b583b30b72cf/weaviate_client-4.20.4.tar.gz", hash = "sha256:08703234b59e4e03739f39e740e9e88cb50cd0aa147d9408b88ea6ce995c37b6", size = 809529, upload-time = "2026-03-10T15:08:13.845Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/1c/82b560254f612f95b644849d86e092da6407f17965d61e22b583b30b72cf/weaviate_client-4.20.4.tar.gz", hash = "sha256:08703234b59e4e03739f39e740e9e88cb50cd0aa147d9408b88ea6ce995c37b6", size = 809529 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/d7/9461c3e7d8c44080d2307078e33dc7fefefa3171c8f930f2b83a5cbf67f2/weaviate_client-4.20.4-py3-none-any.whl", hash = "sha256:7af3a213bebcb30dcf456b0db8b6225d8926106b835d7b883276de9dc1c301fe", size = 619517, upload-time = "2026-03-10T15:08:12.047Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d7/9461c3e7d8c44080d2307078e33dc7fefefa3171c8f930f2b83a5cbf67f2/weaviate_client-4.20.4-py3-none-any.whl", hash = "sha256:7af3a213bebcb30dcf456b0db8b6225d8926106b835d7b883276de9dc1c301fe", size = 619517 }, ] [[package]] name = "webencodings" version = "0.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, ] [[package]] name = "websocket-client" version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576 } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616 }, ] [[package]] name = "websockets" version = "15.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, - { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, - { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, - { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, - { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, - { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, - { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, - { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, - { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, - { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, - { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, - { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, - { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, - { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, - { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, - { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437 }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096 }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332 }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152 }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096 }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523 }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790 }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165 }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160 }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395 }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841 }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440 }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098 }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329 }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111 }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054 }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496 }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829 }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217 }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195 }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393 }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837 }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, ] [[package]] name = "win32-setctime" version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, + { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083 }, ] [[package]] name = "wrapt" version = "1.17.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, - { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, - { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, - { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, - { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, - { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, - { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, - { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, - { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, - { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, - { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, - { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, - { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, - { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, - { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, - { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, - { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, - { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, - { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, - { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, - { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, - { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, - { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, - { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, - { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, - { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, - { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, - { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, - { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, - { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, - { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, - { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, + { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998 }, + { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020 }, + { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098 }, + { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036 }, + { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156 }, + { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102 }, + { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732 }, + { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705 }, + { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877 }, + { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885 }, + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003 }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025 }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108 }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072 }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214 }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105 }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766 }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711 }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885 }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896 }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132 }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091 }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172 }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163 }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963 }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945 }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857 }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178 }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310 }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266 }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544 }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283 }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366 }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571 }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094 }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659 }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946 }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717 }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334 }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471 }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591 }, ] [[package]] name = "xlrd" version = "2.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/07/5a/377161c2d3538d1990d7af382c79f3b2372e880b65de21b01b1a2b78691e/xlrd-2.0.2.tar.gz", hash = "sha256:08b5e25de58f21ce71dc7db3b3b8106c1fa776f3024c54e45b45b374e89234c9", size = 100167, upload-time = "2025-06-14T08:46:39.039Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/5a/377161c2d3538d1990d7af382c79f3b2372e880b65de21b01b1a2b78691e/xlrd-2.0.2.tar.gz", hash = "sha256:08b5e25de58f21ce71dc7db3b3b8106c1fa776f3024c54e45b45b374e89234c9", size = 100167 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/62/c8d562e7766786ba6587d09c5a8ba9f718ed3fa8af7f4553e8f91c36f302/xlrd-2.0.2-py2.py3-none-any.whl", hash = "sha256:ea762c3d29f4cca48d82df517b6d89fbce4db3107f9d78713e48cd321d5c9aa9", size = 96555, upload-time = "2025-06-14T08:46:37.766Z" }, + { url = "https://files.pythonhosted.org/packages/1a/62/c8d562e7766786ba6587d09c5a8ba9f718ed3fa8af7f4553e8f91c36f302/xlrd-2.0.2-py2.py3-none-any.whl", hash = "sha256:ea762c3d29f4cca48d82df517b6d89fbce4db3107f9d78713e48cd321d5c9aa9", size = 96555 }, ] [[package]] name = "xlsxwriter" version = "3.2.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/2c/c06ef49dc36e7954e55b802a8b231770d286a9758b3d936bd1e04ce5ba88/xlsxwriter-3.2.9.tar.gz", hash = "sha256:254b1c37a368c444eac6e2f867405cc9e461b0ed97a3233b2ac1e574efb4140c", size = 215940, upload-time = "2025-09-16T00:16:21.63Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/2c/c06ef49dc36e7954e55b802a8b231770d286a9758b3d936bd1e04ce5ba88/xlsxwriter-3.2.9.tar.gz", hash = "sha256:254b1c37a368c444eac6e2f867405cc9e461b0ed97a3233b2ac1e574efb4140c", size = 215940 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/0c/3662f4a66880196a590b202f0db82d919dd2f89e99a27fadef91c4a33d41/xlsxwriter-3.2.9-py3-none-any.whl", hash = "sha256:9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3", size = 175315, upload-time = "2025-09-16T00:16:20.108Z" }, + { url = "https://files.pythonhosted.org/packages/3a/0c/3662f4a66880196a590b202f0db82d919dd2f89e99a27fadef91c4a33d41/xlsxwriter-3.2.9-py3-none-any.whl", hash = "sha256:9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3", size = 175315 }, ] [[package]] name = "xxhash" version = "3.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744, upload-time = "2025-10-02T14:34:34.622Z" }, - { url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816, upload-time = "2025-10-02T14:34:36.043Z" }, - { url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035, upload-time = "2025-10-02T14:34:37.354Z" }, - { url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914, upload-time = "2025-10-02T14:34:38.6Z" }, - { url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163, upload-time = "2025-10-02T14:34:39.872Z" }, - { url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411, upload-time = "2025-10-02T14:34:41.569Z" }, - { url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883, upload-time = "2025-10-02T14:34:43.249Z" }, - { url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392, upload-time = "2025-10-02T14:34:45.042Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898, upload-time = "2025-10-02T14:34:46.302Z" }, - { url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655, upload-time = "2025-10-02T14:34:47.571Z" }, - { url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001, upload-time = "2025-10-02T14:34:49.273Z" }, - { url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431, upload-time = "2025-10-02T14:34:50.798Z" }, - { url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617, upload-time = "2025-10-02T14:34:51.954Z" }, - { url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534, upload-time = "2025-10-02T14:34:53.276Z" }, - { url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" }, - { url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738, upload-time = "2025-10-02T14:34:55.839Z" }, - { url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821, upload-time = "2025-10-02T14:34:57.219Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127, upload-time = "2025-10-02T14:34:59.21Z" }, - { url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975, upload-time = "2025-10-02T14:35:00.816Z" }, - { url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241, upload-time = "2025-10-02T14:35:02.207Z" }, - { url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471, upload-time = "2025-10-02T14:35:03.61Z" }, - { url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936, upload-time = "2025-10-02T14:35:05.013Z" }, - { url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440, upload-time = "2025-10-02T14:35:06.239Z" }, - { url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990, upload-time = "2025-10-02T14:35:07.735Z" }, - { url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689, upload-time = "2025-10-02T14:35:09.438Z" }, - { url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068, upload-time = "2025-10-02T14:35:11.162Z" }, - { url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495, upload-time = "2025-10-02T14:35:12.971Z" }, - { url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620, upload-time = "2025-10-02T14:35:14.129Z" }, - { url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542, upload-time = "2025-10-02T14:35:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880, upload-time = "2025-10-02T14:35:16.315Z" }, - { url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956, upload-time = "2025-10-02T14:35:17.413Z" }, - { url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072, upload-time = "2025-10-02T14:35:18.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409, upload-time = "2025-10-02T14:35:20.31Z" }, - { url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736, upload-time = "2025-10-02T14:35:21.616Z" }, - { url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833, upload-time = "2025-10-02T14:35:23.32Z" }, - { url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348, upload-time = "2025-10-02T14:35:25.111Z" }, - { url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070, upload-time = "2025-10-02T14:35:26.586Z" }, - { url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907, upload-time = "2025-10-02T14:35:28.087Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839, upload-time = "2025-10-02T14:35:29.857Z" }, - { url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304, upload-time = "2025-10-02T14:35:31.222Z" }, - { url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930, upload-time = "2025-10-02T14:35:32.517Z" }, - { url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787, upload-time = "2025-10-02T14:35:33.827Z" }, - { url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916, upload-time = "2025-10-02T14:35:35.107Z" }, - { url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799, upload-time = "2025-10-02T14:35:36.165Z" }, - { url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044, upload-time = "2025-10-02T14:35:37.195Z" }, - { url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754, upload-time = "2025-10-02T14:35:38.245Z" }, - { url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846, upload-time = "2025-10-02T14:35:39.6Z" }, - { url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343, upload-time = "2025-10-02T14:35:40.69Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074, upload-time = "2025-10-02T14:35:42.29Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388, upload-time = "2025-10-02T14:35:43.929Z" }, - { url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614, upload-time = "2025-10-02T14:35:45.216Z" }, - { url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024, upload-time = "2025-10-02T14:35:46.959Z" }, - { url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541, upload-time = "2025-10-02T14:35:48.301Z" }, - { url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305, upload-time = "2025-10-02T14:35:49.584Z" }, - { url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848, upload-time = "2025-10-02T14:35:50.877Z" }, - { url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142, upload-time = "2025-10-02T14:35:52.15Z" }, - { url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547, upload-time = "2025-10-02T14:35:53.547Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214, upload-time = "2025-10-02T14:35:54.746Z" }, - { url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290, upload-time = "2025-10-02T14:35:55.791Z" }, - { url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795, upload-time = "2025-10-02T14:35:57.162Z" }, - { url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955, upload-time = "2025-10-02T14:35:58.267Z" }, - { url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072, upload-time = "2025-10-02T14:35:59.382Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579, upload-time = "2025-10-02T14:36:00.838Z" }, - { url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854, upload-time = "2025-10-02T14:36:02.207Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965, upload-time = "2025-10-02T14:36:03.507Z" }, - { url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484, upload-time = "2025-10-02T14:36:04.828Z" }, - { url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162, upload-time = "2025-10-02T14:36:06.182Z" }, - { url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007, upload-time = "2025-10-02T14:36:07.733Z" }, - { url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956, upload-time = "2025-10-02T14:36:09.106Z" }, - { url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401, upload-time = "2025-10-02T14:36:10.585Z" }, - { url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083, upload-time = "2025-10-02T14:36:12.276Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913, upload-time = "2025-10-02T14:36:14.025Z" }, - { url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586, upload-time = "2025-10-02T14:36:15.603Z" }, - { url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526, upload-time = "2025-10-02T14:36:16.708Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898, upload-time = "2025-10-02T14:36:17.843Z" }, + { url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744 }, + { url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816 }, + { url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035 }, + { url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914 }, + { url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163 }, + { url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411 }, + { url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883 }, + { url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392 }, + { url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898 }, + { url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655 }, + { url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001 }, + { url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431 }, + { url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617 }, + { url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534 }, + { url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876 }, + { url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738 }, + { url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821 }, + { url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127 }, + { url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975 }, + { url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241 }, + { url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471 }, + { url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936 }, + { url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440 }, + { url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990 }, + { url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689 }, + { url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068 }, + { url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495 }, + { url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620 }, + { url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542 }, + { url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880 }, + { url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956 }, + { url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072 }, + { url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409 }, + { url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736 }, + { url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833 }, + { url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348 }, + { url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070 }, + { url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907 }, + { url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839 }, + { url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304 }, + { url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930 }, + { url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787 }, + { url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916 }, + { url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799 }, + { url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044 }, + { url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754 }, + { url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846 }, + { url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343 }, + { url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074 }, + { url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388 }, + { url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614 }, + { url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024 }, + { url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541 }, + { url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305 }, + { url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848 }, + { url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142 }, + { url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547 }, + { url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214 }, + { url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290 }, + { url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795 }, + { url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955 }, + { url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072 }, + { url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579 }, + { url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854 }, + { url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965 }, + { url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484 }, + { url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162 }, + { url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007 }, + { url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956 }, + { url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401 }, + { url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083 }, + { url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913 }, + { url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586 }, + { url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526 }, + { url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898 }, ] [[package]] @@ -9574,99 +9557,99 @@ dependencies = [ { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737, upload-time = "2026-03-01T22:05:12.897Z" }, - { url = "https://files.pythonhosted.org/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069", size = 87029, upload-time = "2026-03-01T22:05:14.376Z" }, - { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310, upload-time = "2026-03-01T22:05:15.71Z" }, - { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587, upload-time = "2026-03-01T22:05:17.384Z" }, - { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528, upload-time = "2026-03-01T22:05:18.804Z" }, - { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339, upload-time = "2026-03-01T22:05:20.235Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061, upload-time = "2026-03-01T22:05:22.268Z" }, - { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132, upload-time = "2026-03-01T22:05:23.638Z" }, - { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289, upload-time = "2026-03-01T22:05:25.749Z" }, - { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950, upload-time = "2026-03-01T22:05:27.318Z" }, - { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960, upload-time = "2026-03-01T22:05:28.738Z" }, - { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703, upload-time = "2026-03-01T22:05:30.438Z" }, - { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325, upload-time = "2026-03-01T22:05:31.835Z" }, - { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067, upload-time = "2026-03-01T22:05:33.358Z" }, - { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285, upload-time = "2026-03-01T22:05:35.4Z" }, - { url = "https://files.pythonhosted.org/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d", size = 82359, upload-time = "2026-03-01T22:05:36.811Z" }, - { url = "https://files.pythonhosted.org/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e", size = 87674, upload-time = "2026-03-01T22:05:38.171Z" }, - { url = "https://files.pythonhosted.org/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9", size = 81879, upload-time = "2026-03-01T22:05:40.006Z" }, - { url = "https://files.pythonhosted.org/packages/9a/4b/a0a6e5d0ee8a2f3a373ddef8a4097d74ac901ac363eea1440464ccbe0898/yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e", size = 123796, upload-time = "2026-03-01T22:05:41.412Z" }, - { url = "https://files.pythonhosted.org/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5", size = 86547, upload-time = "2026-03-01T22:05:42.841Z" }, - { url = "https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b", size = 85854, upload-time = "2026-03-01T22:05:44.85Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035", size = 98351, upload-time = "2026-03-01T22:05:46.836Z" }, - { url = "https://files.pythonhosted.org/packages/86/fc/4118c5671ea948208bdb1492d8b76bdf1453d3e73df051f939f563e7dcc5/yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5", size = 92711, upload-time = "2026-03-01T22:05:48.316Z" }, - { url = "https://files.pythonhosted.org/packages/56/11/1ed91d42bd9e73c13dc9e7eb0dd92298d75e7ac4dd7f046ad0c472e231cd/yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735", size = 106014, upload-time = "2026-03-01T22:05:50.028Z" }, - { url = "https://files.pythonhosted.org/packages/ce/c9/74e44e056a23fbc33aca71779ef450ca648a5bc472bdad7a82339918f818/yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401", size = 105557, upload-time = "2026-03-01T22:05:51.416Z" }, - { url = "https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4", size = 101559, upload-time = "2026-03-01T22:05:52.872Z" }, - { url = "https://files.pythonhosted.org/packages/72/59/c5b8d94b14e3d3c2a9c20cb100119fd534ab5a14b93673ab4cc4a4141ea5/yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f", size = 100502, upload-time = "2026-03-01T22:05:54.954Z" }, - { url = "https://files.pythonhosted.org/packages/77/4f/96976cb54cbfc5c9fd73ed4c51804f92f209481d1fb190981c0f8a07a1d7/yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a", size = 98027, upload-time = "2026-03-01T22:05:56.409Z" }, - { url = "https://files.pythonhosted.org/packages/63/6e/904c4f476471afdbad6b7e5b70362fb5810e35cd7466529a97322b6f5556/yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2", size = 95369, upload-time = "2026-03-01T22:05:58.141Z" }, - { url = "https://files.pythonhosted.org/packages/9d/40/acfcdb3b5f9d68ef499e39e04d25e141fe90661f9d54114556cf83be8353/yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f", size = 105565, upload-time = "2026-03-01T22:06:00.286Z" }, - { url = "https://files.pythonhosted.org/packages/5e/c6/31e28f3a6ba2869c43d124f37ea5260cac9c9281df803c354b31f4dd1f3c/yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b", size = 99813, upload-time = "2026-03-01T22:06:01.712Z" }, - { url = "https://files.pythonhosted.org/packages/08/1f/6f65f59e72d54aa467119b63fc0b0b1762eff0232db1f4720cd89e2f4a17/yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a", size = 105632, upload-time = "2026-03-01T22:06:03.188Z" }, - { url = "https://files.pythonhosted.org/packages/a3/c4/18b178a69935f9e7a338127d5b77d868fdc0f0e49becd286d51b3a18c61d/yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543", size = 101895, upload-time = "2026-03-01T22:06:04.651Z" }, - { url = "https://files.pythonhosted.org/packages/8f/54/f5b870b5505663911dba950a8e4776a0dbd51c9c54c0ae88e823e4b874a0/yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957", size = 82356, upload-time = "2026-03-01T22:06:06.04Z" }, - { url = "https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3", size = 87515, upload-time = "2026-03-01T22:06:08.107Z" }, - { url = "https://files.pythonhosted.org/packages/00/fd/7e1c66efad35e1649114fa13f17485f62881ad58edeeb7f49f8c5e748bf9/yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3", size = 81785, upload-time = "2026-03-01T22:06:10.181Z" }, - { url = "https://files.pythonhosted.org/packages/9c/fc/119dd07004f17ea43bb91e3ece6587759edd7519d6b086d16bfbd3319982/yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa", size = 130719, upload-time = "2026-03-01T22:06:11.708Z" }, - { url = "https://files.pythonhosted.org/packages/e6/0d/9f2348502fbb3af409e8f47730282cd6bc80dec6630c1e06374d882d6eb2/yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120", size = 89690, upload-time = "2026-03-01T22:06:13.429Z" }, - { url = "https://files.pythonhosted.org/packages/50/93/e88f3c80971b42cfc83f50a51b9d165a1dbf154b97005f2994a79f212a07/yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59", size = 89851, upload-time = "2026-03-01T22:06:15.53Z" }, - { url = "https://files.pythonhosted.org/packages/1c/07/61c9dd8ba8f86473263b4036f70fb594c09e99c0d9737a799dfd8bc85651/yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512", size = 95874, upload-time = "2026-03-01T22:06:17.553Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e9/f9ff8ceefba599eac6abddcfb0b3bee9b9e636e96dbf54342a8577252379/yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4", size = 88710, upload-time = "2026-03-01T22:06:19.004Z" }, - { url = "https://files.pythonhosted.org/packages/eb/78/0231bfcc5d4c8eec220bc2f9ef82cb4566192ea867a7c5b4148f44f6cbcd/yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1", size = 101033, upload-time = "2026-03-01T22:06:21.203Z" }, - { url = "https://files.pythonhosted.org/packages/cd/9b/30ea5239a61786f18fd25797151a17fbb3be176977187a48d541b5447dd4/yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea", size = 100817, upload-time = "2026-03-01T22:06:22.738Z" }, - { url = "https://files.pythonhosted.org/packages/62/e2/a4980481071791bc83bce2b7a1a1f7adcabfa366007518b4b845e92eeee3/yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9", size = 97482, upload-time = "2026-03-01T22:06:24.21Z" }, - { url = "https://files.pythonhosted.org/packages/e5/1e/304a00cf5f6100414c4b5a01fc7ff9ee724b62158a08df2f8170dfc72a2d/yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123", size = 95949, upload-time = "2026-03-01T22:06:25.697Z" }, - { url = "https://files.pythonhosted.org/packages/68/03/093f4055ed4cae649ac53bca3d180bd37102e9e11d048588e9ab0c0108d0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24", size = 95839, upload-time = "2026-03-01T22:06:27.309Z" }, - { url = "https://files.pythonhosted.org/packages/b9/28/4c75ebb108f322aa8f917ae10a8ffa4f07cae10a8a627b64e578617df6a0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de", size = 90696, upload-time = "2026-03-01T22:06:29.048Z" }, - { url = "https://files.pythonhosted.org/packages/23/9c/42c2e2dd91c1a570402f51bdf066bfdb1241c2240ba001967bad778e77b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b", size = 100865, upload-time = "2026-03-01T22:06:30.525Z" }, - { url = "https://files.pythonhosted.org/packages/74/05/1bcd60a8a0a914d462c305137246b6f9d167628d73568505fce3f1cb2e65/yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6", size = 96234, upload-time = "2026-03-01T22:06:32.692Z" }, - { url = "https://files.pythonhosted.org/packages/90/b2/f52381aac396d6778ce516b7bc149c79e65bfc068b5de2857ab69eeea3b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6", size = 100295, upload-time = "2026-03-01T22:06:34.268Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e8/638bae5bbf1113a659b2435d8895474598afe38b4a837103764f603aba56/yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5", size = 97784, upload-time = "2026-03-01T22:06:35.864Z" }, - { url = "https://files.pythonhosted.org/packages/80/25/a3892b46182c586c202629fc2159aa13975d3741d52ebd7347fd501d48d5/yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595", size = 88313, upload-time = "2026-03-01T22:06:37.39Z" }, - { url = "https://files.pythonhosted.org/packages/43/68/8c5b36aa5178900b37387937bc2c2fe0e9505537f713495472dcf6f6fccc/yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090", size = 94932, upload-time = "2026-03-01T22:06:39.579Z" }, - { url = "https://files.pythonhosted.org/packages/c6/cc/d79ba8292f51f81f4dc533a8ccfb9fc6992cabf0998ed3245de7589dc07c/yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144", size = 84786, upload-time = "2026-03-01T22:06:41.988Z" }, - { url = "https://files.pythonhosted.org/packages/90/98/b85a038d65d1b92c3903ab89444f48d3cee490a883477b716d7a24b1a78c/yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912", size = 124455, upload-time = "2026-03-01T22:06:43.615Z" }, - { url = "https://files.pythonhosted.org/packages/39/54/bc2b45559f86543d163b6e294417a107bb87557609007c007ad889afec18/yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474", size = 86752, upload-time = "2026-03-01T22:06:45.425Z" }, - { url = "https://files.pythonhosted.org/packages/24/f9/e8242b68362bffe6fb536c8db5076861466fc780f0f1b479fc4ffbebb128/yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719", size = 86291, upload-time = "2026-03-01T22:06:46.974Z" }, - { url = "https://files.pythonhosted.org/packages/ea/d8/d1cb2378c81dd729e98c716582b1ccb08357e8488e4c24714658cc6630e8/yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319", size = 99026, upload-time = "2026-03-01T22:06:48.459Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ff/7196790538f31debe3341283b5b0707e7feb947620fc5e8236ef28d44f72/yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434", size = 92355, upload-time = "2026-03-01T22:06:50.306Z" }, - { url = "https://files.pythonhosted.org/packages/c1/56/25d58c3eddde825890a5fe6aa1866228377354a3c39262235234ab5f616b/yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723", size = 106417, upload-time = "2026-03-01T22:06:52.1Z" }, - { url = "https://files.pythonhosted.org/packages/51/8a/882c0e7bc8277eb895b31bce0138f51a1ba551fc2e1ec6753ffc1e7c1377/yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039", size = 106422, upload-time = "2026-03-01T22:06:54.424Z" }, - { url = "https://files.pythonhosted.org/packages/42/2b/fef67d616931055bf3d6764885990a3ac647d68734a2d6a9e1d13de437a2/yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52", size = 101915, upload-time = "2026-03-01T22:06:55.895Z" }, - { url = "https://files.pythonhosted.org/packages/18/6a/530e16aebce27c5937920f3431c628a29a4b6b430fab3fd1c117b26ff3f6/yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c", size = 100690, upload-time = "2026-03-01T22:06:58.21Z" }, - { url = "https://files.pythonhosted.org/packages/88/08/93749219179a45e27b036e03260fda05190b911de8e18225c294ac95bbc9/yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae", size = 98750, upload-time = "2026-03-01T22:06:59.794Z" }, - { url = "https://files.pythonhosted.org/packages/d9/cf/ea424a004969f5d81a362110a6ac1496d79efdc6d50c2c4b2e3ea0fc2519/yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e", size = 94685, upload-time = "2026-03-01T22:07:01.375Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b7/14341481fe568e2b0408bcf1484c652accafe06a0ade9387b5d3fd9df446/yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85", size = 106009, upload-time = "2026-03-01T22:07:03.151Z" }, - { url = "https://files.pythonhosted.org/packages/0a/e6/5c744a9b54f4e8007ad35bce96fbc9218338e84812d36f3390cea616881a/yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd", size = 100033, upload-time = "2026-03-01T22:07:04.701Z" }, - { url = "https://files.pythonhosted.org/packages/0c/23/e3bfc188d0b400f025bc49d99793d02c9abe15752138dcc27e4eaf0c4a9e/yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6", size = 106483, upload-time = "2026-03-01T22:07:06.231Z" }, - { url = "https://files.pythonhosted.org/packages/72/42/f0505f949a90b3f8b7a363d6cbdf398f6e6c58946d85c6d3a3bc70595b26/yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe", size = 102175, upload-time = "2026-03-01T22:07:08.4Z" }, - { url = "https://files.pythonhosted.org/packages/aa/65/b39290f1d892a9dd671d1c722014ca062a9c35d60885d57e5375db0404b5/yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169", size = 83871, upload-time = "2026-03-01T22:07:09.968Z" }, - { url = "https://files.pythonhosted.org/packages/a9/5b/9b92f54c784c26e2a422e55a8d2607ab15b7ea3349e28359282f84f01d43/yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70", size = 89093, upload-time = "2026-03-01T22:07:11.501Z" }, - { url = "https://files.pythonhosted.org/packages/e0/7d/8a84dc9381fd4412d5e7ff04926f9865f6372b4c2fd91e10092e65d29eb8/yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e", size = 83384, upload-time = "2026-03-01T22:07:13.069Z" }, - { url = "https://files.pythonhosted.org/packages/dd/8d/d2fad34b1c08aa161b74394183daa7d800141aaaee207317e82c790b418d/yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679", size = 131019, upload-time = "2026-03-01T22:07:14.903Z" }, - { url = "https://files.pythonhosted.org/packages/19/ff/33009a39d3ccf4b94d7d7880dfe17fb5816c5a4fe0096d9b56abceea9ac7/yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412", size = 89894, upload-time = "2026-03-01T22:07:17.372Z" }, - { url = "https://files.pythonhosted.org/packages/0c/f1/dab7ac5e7306fb79c0190766a3c00b4cb8d09a1f390ded68c85a5934faf5/yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4", size = 89979, upload-time = "2026-03-01T22:07:19.361Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b1/08e95f3caee1fad6e65017b9f26c1d79877b502622d60e517de01e72f95d/yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c", size = 95943, upload-time = "2026-03-01T22:07:21.266Z" }, - { url = "https://files.pythonhosted.org/packages/c0/cc/6409f9018864a6aa186c61175b977131f373f1988e198e031236916e87e4/yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4", size = 88786, upload-time = "2026-03-01T22:07:23.129Z" }, - { url = "https://files.pythonhosted.org/packages/76/40/cc22d1d7714b717fde2006fad2ced5efe5580606cb059ae42117542122f3/yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94", size = 101307, upload-time = "2026-03-01T22:07:24.689Z" }, - { url = "https://files.pythonhosted.org/packages/8f/0d/476c38e85ddb4c6ec6b20b815bdd779aa386a013f3d8b85516feee55c8dc/yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28", size = 100904, upload-time = "2026-03-01T22:07:26.287Z" }, - { url = "https://files.pythonhosted.org/packages/72/32/0abe4a76d59adf2081dcb0397168553ece4616ada1c54d1c49d8936c74f8/yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6", size = 97728, upload-time = "2026-03-01T22:07:27.906Z" }, - { url = "https://files.pythonhosted.org/packages/b7/35/7b30f4810fba112f60f5a43237545867504e15b1c7647a785fbaf588fac2/yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277", size = 95964, upload-time = "2026-03-01T22:07:30.198Z" }, - { url = "https://files.pythonhosted.org/packages/2d/86/ed7a73ab85ef00e8bb70b0cb5421d8a2a625b81a333941a469a6f4022828/yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4", size = 95882, upload-time = "2026-03-01T22:07:32.132Z" }, - { url = "https://files.pythonhosted.org/packages/19/90/d56967f61a29d8498efb7afb651e0b2b422a1e9b47b0ab5f4e40a19b699b/yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a", size = 90797, upload-time = "2026-03-01T22:07:34.404Z" }, - { url = "https://files.pythonhosted.org/packages/72/00/8b8f76909259f56647adb1011d7ed8b321bcf97e464515c65016a47ecdf0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb", size = 101023, upload-time = "2026-03-01T22:07:35.953Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e2/cab11b126fb7d440281b7df8e9ddbe4851e70a4dde47a202b6642586b8d9/yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41", size = 96227, upload-time = "2026-03-01T22:07:37.594Z" }, - { url = "https://files.pythonhosted.org/packages/c2/9b/2c893e16bfc50e6b2edf76c1a9eb6cb0c744346197e74c65e99ad8d634d0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2", size = 100302, upload-time = "2026-03-01T22:07:39.334Z" }, - { url = "https://files.pythonhosted.org/packages/28/ec/5498c4e3a6d5f1003beb23405671c2eb9cdbf3067d1c80f15eeafe301010/yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4", size = 98202, upload-time = "2026-03-01T22:07:41.717Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c3/cd737e2d45e70717907f83e146f6949f20cc23cd4bf7b2688727763aa458/yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4", size = 90558, upload-time = "2026-03-01T22:07:43.433Z" }, - { url = "https://files.pythonhosted.org/packages/e1/19/3774d162f6732d1cfb0b47b4140a942a35ca82bb19b6db1f80e9e7bdc8f8/yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2", size = 97610, upload-time = "2026-03-01T22:07:45.773Z" }, - { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041, upload-time = "2026-03-01T22:07:49.026Z" }, - { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, + { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737 }, + { url = "https://files.pythonhosted.org/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069", size = 87029 }, + { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310 }, + { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587 }, + { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528 }, + { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339 }, + { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061 }, + { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132 }, + { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289 }, + { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950 }, + { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960 }, + { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703 }, + { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325 }, + { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067 }, + { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285 }, + { url = "https://files.pythonhosted.org/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d", size = 82359 }, + { url = "https://files.pythonhosted.org/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e", size = 87674 }, + { url = "https://files.pythonhosted.org/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9", size = 81879 }, + { url = "https://files.pythonhosted.org/packages/9a/4b/a0a6e5d0ee8a2f3a373ddef8a4097d74ac901ac363eea1440464ccbe0898/yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e", size = 123796 }, + { url = "https://files.pythonhosted.org/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5", size = 86547 }, + { url = "https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b", size = 85854 }, + { url = "https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035", size = 98351 }, + { url = "https://files.pythonhosted.org/packages/86/fc/4118c5671ea948208bdb1492d8b76bdf1453d3e73df051f939f563e7dcc5/yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5", size = 92711 }, + { url = "https://files.pythonhosted.org/packages/56/11/1ed91d42bd9e73c13dc9e7eb0dd92298d75e7ac4dd7f046ad0c472e231cd/yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735", size = 106014 }, + { url = "https://files.pythonhosted.org/packages/ce/c9/74e44e056a23fbc33aca71779ef450ca648a5bc472bdad7a82339918f818/yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401", size = 105557 }, + { url = "https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4", size = 101559 }, + { url = "https://files.pythonhosted.org/packages/72/59/c5b8d94b14e3d3c2a9c20cb100119fd534ab5a14b93673ab4cc4a4141ea5/yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f", size = 100502 }, + { url = "https://files.pythonhosted.org/packages/77/4f/96976cb54cbfc5c9fd73ed4c51804f92f209481d1fb190981c0f8a07a1d7/yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a", size = 98027 }, + { url = "https://files.pythonhosted.org/packages/63/6e/904c4f476471afdbad6b7e5b70362fb5810e35cd7466529a97322b6f5556/yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2", size = 95369 }, + { url = "https://files.pythonhosted.org/packages/9d/40/acfcdb3b5f9d68ef499e39e04d25e141fe90661f9d54114556cf83be8353/yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f", size = 105565 }, + { url = "https://files.pythonhosted.org/packages/5e/c6/31e28f3a6ba2869c43d124f37ea5260cac9c9281df803c354b31f4dd1f3c/yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b", size = 99813 }, + { url = "https://files.pythonhosted.org/packages/08/1f/6f65f59e72d54aa467119b63fc0b0b1762eff0232db1f4720cd89e2f4a17/yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a", size = 105632 }, + { url = "https://files.pythonhosted.org/packages/a3/c4/18b178a69935f9e7a338127d5b77d868fdc0f0e49becd286d51b3a18c61d/yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543", size = 101895 }, + { url = "https://files.pythonhosted.org/packages/8f/54/f5b870b5505663911dba950a8e4776a0dbd51c9c54c0ae88e823e4b874a0/yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957", size = 82356 }, + { url = "https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3", size = 87515 }, + { url = "https://files.pythonhosted.org/packages/00/fd/7e1c66efad35e1649114fa13f17485f62881ad58edeeb7f49f8c5e748bf9/yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3", size = 81785 }, + { url = "https://files.pythonhosted.org/packages/9c/fc/119dd07004f17ea43bb91e3ece6587759edd7519d6b086d16bfbd3319982/yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa", size = 130719 }, + { url = "https://files.pythonhosted.org/packages/e6/0d/9f2348502fbb3af409e8f47730282cd6bc80dec6630c1e06374d882d6eb2/yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120", size = 89690 }, + { url = "https://files.pythonhosted.org/packages/50/93/e88f3c80971b42cfc83f50a51b9d165a1dbf154b97005f2994a79f212a07/yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59", size = 89851 }, + { url = "https://files.pythonhosted.org/packages/1c/07/61c9dd8ba8f86473263b4036f70fb594c09e99c0d9737a799dfd8bc85651/yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512", size = 95874 }, + { url = "https://files.pythonhosted.org/packages/9e/e9/f9ff8ceefba599eac6abddcfb0b3bee9b9e636e96dbf54342a8577252379/yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4", size = 88710 }, + { url = "https://files.pythonhosted.org/packages/eb/78/0231bfcc5d4c8eec220bc2f9ef82cb4566192ea867a7c5b4148f44f6cbcd/yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1", size = 101033 }, + { url = "https://files.pythonhosted.org/packages/cd/9b/30ea5239a61786f18fd25797151a17fbb3be176977187a48d541b5447dd4/yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea", size = 100817 }, + { url = "https://files.pythonhosted.org/packages/62/e2/a4980481071791bc83bce2b7a1a1f7adcabfa366007518b4b845e92eeee3/yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9", size = 97482 }, + { url = "https://files.pythonhosted.org/packages/e5/1e/304a00cf5f6100414c4b5a01fc7ff9ee724b62158a08df2f8170dfc72a2d/yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123", size = 95949 }, + { url = "https://files.pythonhosted.org/packages/68/03/093f4055ed4cae649ac53bca3d180bd37102e9e11d048588e9ab0c0108d0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24", size = 95839 }, + { url = "https://files.pythonhosted.org/packages/b9/28/4c75ebb108f322aa8f917ae10a8ffa4f07cae10a8a627b64e578617df6a0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de", size = 90696 }, + { url = "https://files.pythonhosted.org/packages/23/9c/42c2e2dd91c1a570402f51bdf066bfdb1241c2240ba001967bad778e77b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b", size = 100865 }, + { url = "https://files.pythonhosted.org/packages/74/05/1bcd60a8a0a914d462c305137246b6f9d167628d73568505fce3f1cb2e65/yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6", size = 96234 }, + { url = "https://files.pythonhosted.org/packages/90/b2/f52381aac396d6778ce516b7bc149c79e65bfc068b5de2857ab69eeea3b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6", size = 100295 }, + { url = "https://files.pythonhosted.org/packages/e5/e8/638bae5bbf1113a659b2435d8895474598afe38b4a837103764f603aba56/yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5", size = 97784 }, + { url = "https://files.pythonhosted.org/packages/80/25/a3892b46182c586c202629fc2159aa13975d3741d52ebd7347fd501d48d5/yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595", size = 88313 }, + { url = "https://files.pythonhosted.org/packages/43/68/8c5b36aa5178900b37387937bc2c2fe0e9505537f713495472dcf6f6fccc/yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090", size = 94932 }, + { url = "https://files.pythonhosted.org/packages/c6/cc/d79ba8292f51f81f4dc533a8ccfb9fc6992cabf0998ed3245de7589dc07c/yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144", size = 84786 }, + { url = "https://files.pythonhosted.org/packages/90/98/b85a038d65d1b92c3903ab89444f48d3cee490a883477b716d7a24b1a78c/yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912", size = 124455 }, + { url = "https://files.pythonhosted.org/packages/39/54/bc2b45559f86543d163b6e294417a107bb87557609007c007ad889afec18/yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474", size = 86752 }, + { url = "https://files.pythonhosted.org/packages/24/f9/e8242b68362bffe6fb536c8db5076861466fc780f0f1b479fc4ffbebb128/yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719", size = 86291 }, + { url = "https://files.pythonhosted.org/packages/ea/d8/d1cb2378c81dd729e98c716582b1ccb08357e8488e4c24714658cc6630e8/yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319", size = 99026 }, + { url = "https://files.pythonhosted.org/packages/0a/ff/7196790538f31debe3341283b5b0707e7feb947620fc5e8236ef28d44f72/yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434", size = 92355 }, + { url = "https://files.pythonhosted.org/packages/c1/56/25d58c3eddde825890a5fe6aa1866228377354a3c39262235234ab5f616b/yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723", size = 106417 }, + { url = "https://files.pythonhosted.org/packages/51/8a/882c0e7bc8277eb895b31bce0138f51a1ba551fc2e1ec6753ffc1e7c1377/yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039", size = 106422 }, + { url = "https://files.pythonhosted.org/packages/42/2b/fef67d616931055bf3d6764885990a3ac647d68734a2d6a9e1d13de437a2/yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52", size = 101915 }, + { url = "https://files.pythonhosted.org/packages/18/6a/530e16aebce27c5937920f3431c628a29a4b6b430fab3fd1c117b26ff3f6/yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c", size = 100690 }, + { url = "https://files.pythonhosted.org/packages/88/08/93749219179a45e27b036e03260fda05190b911de8e18225c294ac95bbc9/yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae", size = 98750 }, + { url = "https://files.pythonhosted.org/packages/d9/cf/ea424a004969f5d81a362110a6ac1496d79efdc6d50c2c4b2e3ea0fc2519/yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e", size = 94685 }, + { url = "https://files.pythonhosted.org/packages/e2/b7/14341481fe568e2b0408bcf1484c652accafe06a0ade9387b5d3fd9df446/yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85", size = 106009 }, + { url = "https://files.pythonhosted.org/packages/0a/e6/5c744a9b54f4e8007ad35bce96fbc9218338e84812d36f3390cea616881a/yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd", size = 100033 }, + { url = "https://files.pythonhosted.org/packages/0c/23/e3bfc188d0b400f025bc49d99793d02c9abe15752138dcc27e4eaf0c4a9e/yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6", size = 106483 }, + { url = "https://files.pythonhosted.org/packages/72/42/f0505f949a90b3f8b7a363d6cbdf398f6e6c58946d85c6d3a3bc70595b26/yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe", size = 102175 }, + { url = "https://files.pythonhosted.org/packages/aa/65/b39290f1d892a9dd671d1c722014ca062a9c35d60885d57e5375db0404b5/yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169", size = 83871 }, + { url = "https://files.pythonhosted.org/packages/a9/5b/9b92f54c784c26e2a422e55a8d2607ab15b7ea3349e28359282f84f01d43/yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70", size = 89093 }, + { url = "https://files.pythonhosted.org/packages/e0/7d/8a84dc9381fd4412d5e7ff04926f9865f6372b4c2fd91e10092e65d29eb8/yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e", size = 83384 }, + { url = "https://files.pythonhosted.org/packages/dd/8d/d2fad34b1c08aa161b74394183daa7d800141aaaee207317e82c790b418d/yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679", size = 131019 }, + { url = "https://files.pythonhosted.org/packages/19/ff/33009a39d3ccf4b94d7d7880dfe17fb5816c5a4fe0096d9b56abceea9ac7/yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412", size = 89894 }, + { url = "https://files.pythonhosted.org/packages/0c/f1/dab7ac5e7306fb79c0190766a3c00b4cb8d09a1f390ded68c85a5934faf5/yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4", size = 89979 }, + { url = "https://files.pythonhosted.org/packages/aa/b1/08e95f3caee1fad6e65017b9f26c1d79877b502622d60e517de01e72f95d/yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c", size = 95943 }, + { url = "https://files.pythonhosted.org/packages/c0/cc/6409f9018864a6aa186c61175b977131f373f1988e198e031236916e87e4/yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4", size = 88786 }, + { url = "https://files.pythonhosted.org/packages/76/40/cc22d1d7714b717fde2006fad2ced5efe5580606cb059ae42117542122f3/yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94", size = 101307 }, + { url = "https://files.pythonhosted.org/packages/8f/0d/476c38e85ddb4c6ec6b20b815bdd779aa386a013f3d8b85516feee55c8dc/yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28", size = 100904 }, + { url = "https://files.pythonhosted.org/packages/72/32/0abe4a76d59adf2081dcb0397168553ece4616ada1c54d1c49d8936c74f8/yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6", size = 97728 }, + { url = "https://files.pythonhosted.org/packages/b7/35/7b30f4810fba112f60f5a43237545867504e15b1c7647a785fbaf588fac2/yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277", size = 95964 }, + { url = "https://files.pythonhosted.org/packages/2d/86/ed7a73ab85ef00e8bb70b0cb5421d8a2a625b81a333941a469a6f4022828/yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4", size = 95882 }, + { url = "https://files.pythonhosted.org/packages/19/90/d56967f61a29d8498efb7afb651e0b2b422a1e9b47b0ab5f4e40a19b699b/yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a", size = 90797 }, + { url = "https://files.pythonhosted.org/packages/72/00/8b8f76909259f56647adb1011d7ed8b321bcf97e464515c65016a47ecdf0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb", size = 101023 }, + { url = "https://files.pythonhosted.org/packages/ac/e2/cab11b126fb7d440281b7df8e9ddbe4851e70a4dde47a202b6642586b8d9/yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41", size = 96227 }, + { url = "https://files.pythonhosted.org/packages/c2/9b/2c893e16bfc50e6b2edf76c1a9eb6cb0c744346197e74c65e99ad8d634d0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2", size = 100302 }, + { url = "https://files.pythonhosted.org/packages/28/ec/5498c4e3a6d5f1003beb23405671c2eb9cdbf3067d1c80f15eeafe301010/yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4", size = 98202 }, + { url = "https://files.pythonhosted.org/packages/fe/c3/cd737e2d45e70717907f83e146f6949f20cc23cd4bf7b2688727763aa458/yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4", size = 90558 }, + { url = "https://files.pythonhosted.org/packages/e1/19/3774d162f6732d1cfb0b47b4140a942a35ca82bb19b6db1f80e9e7bdc8f8/yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2", size = 97610 }, + { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041 }, + { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288 }, ] [[package]] @@ -9677,73 +9660,73 @@ dependencies = [ { name = "defusedxml" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/43/4104185a2eaa839daa693b30e15c37e7e58795e8e09ec414f22b3db54bec/youtube_transcript_api-1.2.4.tar.gz", hash = "sha256:b72d0e96a335df599d67cee51d49e143cff4f45b84bcafc202ff51291603ddcd", size = 469839, upload-time = "2026-01-29T09:09:17.088Z" } +sdist = { url = "https://files.pythonhosted.org/packages/60/43/4104185a2eaa839daa693b30e15c37e7e58795e8e09ec414f22b3db54bec/youtube_transcript_api-1.2.4.tar.gz", hash = "sha256:b72d0e96a335df599d67cee51d49e143cff4f45b84bcafc202ff51291603ddcd", size = 469839 } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/95/129ea37efd6cd6ed00f62baae6543345c677810b8a3bf0026756e1d3cf3c/youtube_transcript_api-1.2.4-py3-none-any.whl", hash = "sha256:03878759356da5caf5edac77431780b91448fb3d8c21d4496015bdc8a7bc43ff", size = 485227, upload-time = "2026-01-29T09:09:15.427Z" }, + { url = "https://files.pythonhosted.org/packages/be/95/129ea37efd6cd6ed00f62baae6543345c677810b8a3bf0026756e1d3cf3c/youtube_transcript_api-1.2.4-py3-none-any.whl", hash = "sha256:03878759356da5caf5edac77431780b91448fb3d8c21d4496015bdc8a7bc43ff", size = 485227 }, ] [[package]] name = "zipp" version = "3.23.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276 }, ] [[package]] name = "zstandard" version = "0.25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513 } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, - { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, - { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, - { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, - { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, - { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, - { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, - { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, - { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, - { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, - { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, - { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, - { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, - { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, - { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, - { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, - { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" }, - { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" }, - { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" }, - { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" }, - { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" }, - { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" }, - { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" }, - { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" }, - { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" }, - { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" }, - { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" }, - { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" }, - { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" }, - { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" }, - { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" }, - { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" }, - { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" }, - { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" }, - { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" }, - { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" }, - { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" }, - { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" }, - { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" }, - { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" }, + { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738 }, + { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436 }, + { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019 }, + { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012 }, + { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148 }, + { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652 }, + { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993 }, + { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806 }, + { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659 }, + { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933 }, + { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008 }, + { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517 }, + { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292 }, + { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237 }, + { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922 }, + { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276 }, + { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679 }, + { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735 }, + { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440 }, + { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070 }, + { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001 }, + { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120 }, + { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230 }, + { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173 }, + { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736 }, + { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368 }, + { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022 }, + { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889 }, + { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952 }, + { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054 }, + { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113 }, + { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936 }, + { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232 }, + { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671 }, + { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887 }, + { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658 }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849 }, + { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095 }, + { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751 }, + { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818 }, + { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402 }, + { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108 }, + { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248 }, + { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330 }, + { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123 }, + { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591 }, + { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513 }, + { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118 }, + { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940 }, ] diff --git a/surfsense_browser_extension/package.json b/surfsense_browser_extension/package.json index 2f17899a8..13cd31b80 100644 --- a/surfsense_browser_extension/package.json +++ b/surfsense_browser_extension/package.json @@ -1,7 +1,7 @@ { "name": "surfsense_browser_extension", "displayName": "Surfsense Browser Extension", - "version": "0.0.25", + "version": "0.0.26", "description": "Extension to collect Browsing History for SurfSense.", "author": "https://github.com/MODSetter", "engines": { diff --git a/surfsense_desktop/package.json b/surfsense_desktop/package.json index 4ee6ea3c4..1f0e6dafc 100644 --- a/surfsense_desktop/package.json +++ b/surfsense_desktop/package.json @@ -1,7 +1,7 @@ { "name": "surfsense-desktop", "productName": "SurfSense", - "version": "0.0.25", + "version": "0.0.26", "description": "SurfSense Desktop App", "main": "dist/main.js", "scripts": { diff --git a/surfsense_web/changelog/content/2026-05-31.mdx b/surfsense_web/changelog/content/2026-05-31.mdx new file mode 100644 index 000000000..2e79effc6 --- /dev/null +++ b/surfsense_web/changelog/content/2026-05-31.mdx @@ -0,0 +1,76 @@ +--- +title: "SurfSense v0.0.26 - AI Automations: Build, Schedule & Event-Trigger AI Agents From Chat" +description: "SurfSense v0.0.26 introduces open source AI automations across your connectors: describe a workflow in plain English and SurfSense builds it, run AI agents on a schedule, or trigger them when a document lands in a folder, working across Notion, Slack, Google Drive, Gmail, GitHub, Linear, Jira and Confluence. Plus connector @-mentions in chat and a faster anonymous chat experience." +date: "2026-05-31" +tags: ["Automations", "AI Agents", "Workflow Automation", "Scheduled Workflows", "Event Triggers", "Connectors", "Notion", "Slack", "Open Source"] +version: "0.0.26" +--- + +## What's New in v0.0.26 + +v0.0.26 is our biggest workflow release yet: **AI automations** land in SurfSense. If you've been looking for **open source AI automation**, a **self-hosted AI agent** that runs on a schedule, or **document workflow automation** that reacts the instant a file shows up, this release is for you. Describe what you want in plain English, let SurfSense draft the automation, approve it, and your agent runs on its own: on a **cron schedule** or triggered by real events in your knowledge base. Best of all, automations work **across your connectors**, so one workflow can search and act on **Notion**, **Slack**, **Google Drive**, **Gmail**, **GitHub**, **Linear**, **Jira** and **Confluence**. This release also brings connector **@-mentions** into chat, a faster anonymous chat experience, and a redesigned homepage. + +### AI Automations + +Turn one-off prompts into repeatable, hands-off **AI agent workflows**. + +- **Build Automations From Chat**: Describe an automation in plain English and SurfSense drafts it for you. Review the generated workflow in a human-in-the-loop approval card, tweak it, and save. No config files, no code. +- **Scheduled AI Workflows**: Run an agent on a **cron schedule** for daily briefs, weekly digests, and recurring reports. SurfSense computes the next run time and fires it automatically in the background. +- **Event-Triggered Automations**: Kick off an agent the moment a document enters a folder. SurfSense's new in-process event bus watches your knowledge base and launches the right automation as soon as the event happens. +- **Agent Task Action**: Every automation runs a full multi-agent chat turn, so your scheduled and event-driven runs have the same reasoning, search, and tool access as a live chat, with an auto-approve loop so they finish unattended. +- **Automations Across Your Connectors**: Because each automation runs a full agent turn, your scheduled and event-triggered workflows can search and act across all 25+ connected sources, including **Notion**, **Slack**, **Google Drive**, **Gmail**, **GitHub**, **Linear**, **Jira**, **Confluence**, and your **Obsidian** vault. +- **Automations Dashboard**: A dedicated list view shows every automation with its status, plus one-click pause, resume, and delete. Find it in the sidebar under Inbox. +- **Detail & Run History**: Open any automation to inspect its definition, manage triggers inline, and browse a complete run history with inputs, outputs, and clear error reporting. +- **Power-User JSON Mode**: Create or edit an automation directly as raw JSON with a unified JSON viewer and editor for full control over the workflow definition. +- **Role-Based Access**: Automations are governed by SurfSense's RBAC system, with a dedicated permissions family so teams control exactly who can create, run, and manage them. + +### Connector Mentions in Chat + +- **@-Mention Your Connectors**: Mention connectors directly in the composer to scope a question to a specific source like **Notion**, **Slack**, **Google Drive**, **Gmail**, **GitHub**, **Linear**, **Jira**, or **Confluence**, alongside existing document mentions. +- **Recent Mentions**: SurfSense now remembers your recent mentions so the sources you use most are always one keystroke away. +- **Smoother Mention Picker**: A refreshed mention picker with loading skeletons, clearer connector definitions, and a better inline editing experience. + +### Faster, Friendlier Chat + +- **Anonymous Chat, Reworked**: The free, no-login chat experience has been rebuilt for a faster first impression and a cleaner anonymous-to-account path. +- **Better Long-Running Turns**: Improved task management and timeout handling in multi-agent chat keep complex, tool-heavy conversations reliable. +- **Leaner Toolset**: Retired the legacy in-product docs search tool to keep agent reasoning focused and fast. + +### Homepage & Marketing + +- **Redesigned Use-Case Showcase**: The homepage now groups demos into clear categories (Desktop App, Deliverable Studio, Search & Chat, Connectors & Integrations, and Automations) so visitors immediately see what SurfSense can do. +- **Desktop App, Front and Center**: The desktop experience is highlighted as a set of native extras on top of everything SurfSense already does, not a separate product. + + + + Bug Fixes + +
    +
  • Bulk-moving documents now uses ORM objects so folder events fire correctly and trigger automations
  • +
  • Automation enum columns now persist Postgres enum values instead of names
  • +
  • Automation agent tasks use an in-memory checkpointer to avoid Celery pool timeouts
  • +
  • The API client now handles 204 No Content responses without errors
  • +
  • The model role manager now stays in sync when preferences are updated
  • +
  • The JSON editor now coerces numeric strings to numbers on edit
  • +
+
+
+ + Under the Hood + +
    +
  • New in-process domain event bus with an event catalog and a document.entered_folder event
  • +
  • SQLAlchemy session hooks publish folder events automatically, registered at app startup
  • +
  • Cron schedule triggers backed by croniter and a Celery beat tick task
  • +
  • Sandboxed template engine with an allowlisted filter and test set for safe workflow templating
  • +
  • Automations reorganized into a vertical-slice architecture (actions and triggers grouped by domain)
  • +
  • Extensive new test coverage locking automation schemas, dispatch, runtime, triggers, and templating
  • +
  • Model eligibility checks when creating automations, so only valid models are selectable
  • +
+
+
+
+ +v0.0.26 turns SurfSense from a place you ask questions into a place that does the work for you. Whether you want **scheduled AI workflows**, **event-driven document automation**, or a **self-hosted, open source AI agent** you fully control, this release lets you build it from a single sentence. + +SurfSense connects all your knowledge sources in one place. diff --git a/surfsense_web/package.json b/surfsense_web/package.json index 6ac32160b..cc55d0d5d 100644 --- a/surfsense_web/package.json +++ b/surfsense_web/package.json @@ -1,6 +1,6 @@ { "name": "surfsense_web", - "version": "0.0.25", + "version": "0.0.26", "private": true, "packageManager": "pnpm@10.26.0", "description": "SurfSense Frontend", From 792d8520a619a0e5ddcbaa7a2a9f5dd2aa252ad2 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 18:25:14 -0700 Subject: [PATCH 193/198] feat(hero-section): enhance use case descriptions and add examples - Updated the hero section to include detailed examples for various use cases, improving user guidance. - Modified existing use case descriptions for clarity and relevance. - Introduced a new component to display example prompts, enhancing the overall user experience. --- .../components/homepage/hero-section.tsx | 80 +++++++++++++++++-- 1 file changed, 75 insertions(+), 5 deletions(-) diff --git a/surfsense_web/components/homepage/hero-section.tsx b/surfsense_web/components/homepage/hero-section.tsx index 8a831d492..0641d4e4e 100644 --- a/surfsense_web/components/homepage/hero-section.tsx +++ b/surfsense_web/components/homepage/hero-section.tsx @@ -1,5 +1,13 @@ "use client"; -import { ChevronDown, Clock, Download, Monitor, Sparkles } from "lucide-react"; +import { + ChevronDown, + Clock, + CornerDownLeft, + Download, + Lightbulb, + Monitor, + Sparkles, +} from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import Link from "next/link"; import React, { memo, useCallback, useEffect, useRef, useState } from "react"; @@ -67,6 +75,7 @@ type HeroUseCase = { description: string; src: string | null; comingSoon?: boolean; + examples?: string[]; }; type HeroCategory = { @@ -143,9 +152,16 @@ const CATEGORIES: HeroCategory[] = [ { id: "resume", title: "AI Resume Builder", - description: "Draft and format an ATS-ready resume as a polished PDF.", + description: "Tailor your existing resume to any job description and beat the ATS.", src: null, comingSoon: true, + examples: [ + "Tailor my resume to this job description so it gets past ATS and lands an interview.", + "Optimize my resume for ATS by matching the keywords in this job posting.", + "Rewrite my resume bullet points to highlight the skills this role is asking for.", + "Compare my resume against this job description and list the gaps to fix.", + "Write a matching cover letter from my resume and this job description.", + ], }, ], }, @@ -159,6 +175,13 @@ const CATEGORIES: HeroCategory[] = [ description: "Run an agent on a schedule: daily briefs, weekly digests, recurring reports.", src: null, comingSoon: true, + examples: [ + "Email me a daily brief of new documents in my knowledge base every morning.", + "Generate a weekly status report from my Slack and Gmail every Friday.", + "Run a monthly competitor analysis report and save it to my workspace.", + "Summarize my GitHub and Linear activity into a daily standup update.", + "Create a recurring weekly research report on the topics I track.", + ], }, { id: "event", @@ -167,6 +190,13 @@ const CATEGORIES: HeroCategory[] = [ "Fire an agent the moment a document lands in a folder, then post the result to your tools.", src: null, comingSoon: true, + examples: [ + "When a PDF lands in my Research folder, generate a cited AI summary.", + "When new meeting notes are added, turn them into meeting minutes with action items.", + "When an invoice is uploaded, extract the vendor, total, and due date into a table.", + "When a contract enters my Legal folder, flag key terms and renewal dates.", + "When a resume is added to Candidates, screen it against the job description.", + ], }, { id: "chat-built", @@ -174,6 +204,13 @@ const CATEGORIES: HeroCategory[] = [ description: "Describe an automation in plain English and SurfSense builds it for you.", src: null, comingSoon: true, + examples: [ + "Build an AI agent that emails me a summary of new Notion pages each morning.", + "Create a no-code automation that posts a weekly research digest to Slack.", + "Set up an AI note taker that turns new meeting notes into minutes.", + "Make a workflow that extracts action items from meeting notes and assigns owners.", + "Automate a daily email brief from my Gmail and Google Drive.", + ], }, ], }, @@ -230,6 +267,13 @@ const CATEGORIES: HeroCategory[] = [ description: "Let the agent post results back to Notion, Slack, Linear and Drive.", src: null, comingSoon: true, + examples: [ + "Post this research summary to my Notion workspace.", + "Send these meeting action items to our team Slack channel.", + "Create a Jira ticket from this bug report.", + "Open a Linear issue from this feature request.", + "Save this generated report to Google Drive as a doc.", + ], }, ], }, @@ -435,6 +479,28 @@ const UseCasePlaceholder = ({ title }: { title: string }) => ( ); +const UseCaseExamples = ({ examples }: { examples: string[] }) => ( +
+
+
+
    + {examples.map((example) => ( +
  • +
    +
    +
  • + ))} +
+
+); + const DesktopBadge = () => ( @@ -469,9 +535,13 @@ const UseCasePane = memo(function UseCasePane({ ) : (
-
- -
+ {useCase.examples && useCase.examples.length > 0 ? ( + + ) : ( +
+ +
+ )}
); From 414ca6787234c27d055d03cbf9cf80615f883956 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 18:32:28 -0700 Subject: [PATCH 194/198] feat(docs): update README files with enhanced prompts - Revised the AI Resume Builder description to emphasize tailoring resumes for job descriptions and ATS compliance. - Added example prompts across multiple languages to guide users in utilizing the feature effectively. - Enhanced the automations section with new examples for various use cases, improving user engagement and understanding. --- README.es.md | 48 ++++++++++++++++++++++++++++++++++++++++++------ README.hi.md | 48 ++++++++++++++++++++++++++++++++++++++++++------ README.md | 48 ++++++++++++++++++++++++++++++++++++++++++------ README.pt-BR.md | 48 ++++++++++++++++++++++++++++++++++++++++++------ README.zh-CN.md | 48 ++++++++++++++++++++++++++++++++++++++++++------ 5 files changed, 210 insertions(+), 30 deletions(-) diff --git a/README.es.md b/README.es.md index 75f499ab0..e7b61dd07 100644 --- a/README.es.md +++ b/README.es.md @@ -112,7 +112,14 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

AI Image Generator

- - AI Resume Builder: redacta y da formato a un currículum listo para ATS en un PDF pulido. (próximamente) + - AI Resume Builder: adapta tu currículum existente a cualquier descripción de empleo y supera el ATS. + Prueba indicaciones como estas: + + - "Adapta mi currículum a esta descripción de empleo para superar el ATS y conseguir una entrevista." + - "Optimiza mi currículum para ATS haciendo coincidir las palabras clave de esta oferta." + - "Reescribe los puntos de mi currículum para resaltar las habilidades que pide este puesto." + - "Compara mi currículum con esta descripción de empleo y enumera las carencias a corregir." + - "Escribe una carta de presentación a juego con mi currículum y esta descripción de empleo." **Búsqueda y Chat** @@ -142,15 +149,44 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

Chat With Uploaded Files

- - Connector Write-Back: deja que el agente publique los resultados de vuelta en Notion, Slack, Linear y Drive. (próximamente) + - Connector Write-Back: deja que el agente publique los resultados de vuelta en Notion, Slack, Linear y Drive. + Prueba indicaciones como estas: - - Obsidian & Knowledge Base Sync: mantén tu bóveda de Obsidian y tu base de conocimiento personal sincronizadas. (próximamente) + - "Publica este resumen de investigación en mi espacio de Notion." + - "Envía estos elementos de acción de la reunión a nuestro canal de Slack." + - "Crea un ticket de Jira a partir de este informe de error." + - "Abre una incidencia en Linear a partir de esta solicitud de función." + - "Guarda este informe generado en Google Drive como un documento." + - Obsidian & Knowledge Base Sync: mantén tu bóveda de Obsidian y tu base de conocimiento personal sincronizadas. **Automatizaciones** - - Scheduled AI Workflows: ejecuta un agente según una programación: resúmenes diarios, boletines semanales, informes recurrentes. (próximamente) - - Event-Triggered Automations: lanza un agente en el momento en que un documento llega a una carpeta y publica el resultado en tus herramientas. (próximamente) - - Chat-Built Automations: describe una automatización en lenguaje sencillo y SurfSense la crea por ti. (próximamente) + - Scheduled AI Workflows: ejecuta un agente según una programación: resúmenes diarios, boletines semanales, informes recurrentes. + Prueba indicaciones como estas: + + - "Envíame cada mañana un resumen diario de los nuevos documentos en mi base de conocimiento." + - "Genera un informe de estado semanal a partir de mi Slack y Gmail cada viernes." + - "Ejecuta un informe mensual de análisis de la competencia y guárdalo en mi espacio de trabajo." + - "Resume mi actividad de GitHub y Linear en una actualización diaria de standup." + - "Crea un informe de investigación semanal recurrente sobre los temas que sigo." + + - Event-Triggered Automations: lanza un agente en el momento en que un documento llega a una carpeta y publica el resultado en tus herramientas. + Prueba indicaciones como estas: + + - "Cuando llegue un PDF a mi carpeta de Investigación, genera un resumen de IA con citas." + - "Cuando se añadan nuevas notas de reunión, conviértelas en actas con elementos de acción." + - "Cuando se suba una factura, extrae el proveedor, el total y la fecha de vencimiento en una tabla." + - "Cuando entre un contrato en mi carpeta Legal, señala los términos clave y las fechas de renovación." + - "Cuando se añada un currículum a Candidatos, evalúalo frente a la descripción del empleo." + + - Chat-Built Automations: describe una automatización en lenguaje sencillo y SurfSense la crea por ti. + Prueba indicaciones como estas: + + - "Crea un agente de IA que me envíe cada mañana un resumen de las nuevas páginas de Notion." + - "Crea una automatización sin código que publique un resumen de investigación semanal en Slack." + - "Configura un tomador de notas con IA que convierta las nuevas notas de reunión en actas." + - "Crea un flujo que extraiga los elementos de acción de las notas de reunión y asigne responsables." + - "Automatiza un resumen diario por correo a partir de mi Gmail y Google Drive." ### Auto-Hospedado diff --git a/README.hi.md b/README.hi.md index c03e8a21c..99bb1c25c 100644 --- a/README.hi.md +++ b/README.hi.md @@ -112,7 +112,14 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

AI Image Generator

- - AI Resume Builder: ATS-तैयार रिज़्यूमे को एक बेहतरीन PDF के रूप में बनाएं और फ़ॉर्मेट करें। (जल्द आ रहा है) + - AI Resume Builder: अपने मौजूदा रिज़्यूमे को किसी भी जॉब डिस्क्रिप्शन के अनुसार ढालें और ATS को पार करें। + इस तरह के प्रॉम्प्ट आज़माएं: + + - "मेरे रिज़्यूमे को इस जॉब डिस्क्रिप्शन के अनुसार ढालें ताकि वह ATS पार करे और इंटरव्यू दिलाए।" + - "इस जॉब पोस्टिंग के कीवर्ड्स से मिलान करके मेरे रिज़्यूमे को ATS के लिए ऑप्टिमाइज़ करें।" + - "इस भूमिका के लिए ज़रूरी स्किल्स को उभारने के लिए मेरे रिज़्यूमे के बुलेट पॉइंट फिर से लिखें।" + - "मेरे रिज़्यूमे की तुलना इस जॉब डिस्क्रिप्शन से करें और सुधारने योग्य कमियों की सूची दें।" + - "मेरे रिज़्यूमे और इस जॉब डिस्क्रिप्शन से मेल खाता एक कवर लेटर लिखें।" **सर्च और चैट** @@ -142,15 +149,44 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

Chat With Uploaded Files

- - Connector Write-Back: एजेंट को परिणाम वापस Notion, Slack, Linear और Drive में पोस्ट करने दें। (जल्द आ रहा है) + - Connector Write-Back: एजेंट को परिणाम वापस Notion, Slack, Linear और Drive में पोस्ट करने दें। + इस तरह के प्रॉम्प्ट आज़माएं: - - Obsidian & Knowledge Base Sync: अपने Obsidian vault और व्यक्तिगत नॉलेज बेस को सिंक रखें। (जल्द आ रहा है) + - "इस रिसर्च सारांश को मेरे Notion वर्कस्पेस में पोस्ट करें।" + - "इन मीटिंग एक्शन आइटम्स को हमारे टीम Slack चैनल पर भेजें।" + - "इस बग रिपोर्ट से एक Jira टिकट बनाएं।" + - "इस फ़ीचर अनुरोध से Linear में एक इश्यू खोलें।" + - "इस जनरेट की गई रिपोर्ट को Google Drive में एक डॉक के रूप में सेव करें।" + - Obsidian & Knowledge Base Sync: अपने Obsidian vault और व्यक्तिगत नॉलेज बेस को सिंक रखें। **ऑटोमेशन** - - Scheduled AI Workflows: किसी एजेंट को शेड्यूल पर चलाएं: रोज़ाना ब्रीफ़, साप्ताहिक डाइजेस्ट, आवर्ती रिपोर्ट। (जल्द आ रहा है) - - Event-Triggered Automations: जैसे ही कोई दस्तावेज़ किसी फ़ोल्डर में आता है, एजेंट को चलाएं और परिणाम अपने टूल में पोस्ट करें। (जल्द आ रहा है) - - Chat-Built Automations: सरल भाषा में किसी ऑटोमेशन का वर्णन करें और SurfSense उसे आपके लिए बना देगा। (जल्द आ रहा है) + - Scheduled AI Workflows: किसी एजेंट को शेड्यूल पर चलाएं: रोज़ाना ब्रीफ़, साप्ताहिक डाइजेस्ट, आवर्ती रिपोर्ट। + इस तरह के प्रॉम्प्ट आज़माएं: + + - "हर सुबह मेरे नॉलेज बेस में जुड़े नए दस्तावेज़ों का रोज़ाना ब्रीफ़ मुझे ईमेल करें।" + - "हर शुक्रवार मेरे Slack और Gmail से एक साप्ताहिक स्टेटस रिपोर्ट बनाएं।" + - "एक मासिक प्रतिस्पर्धी विश्लेषण रिपोर्ट चलाएं और उसे मेरे वर्कस्पेस में सेव करें।" + - "मेरी GitHub और Linear गतिविधि को एक रोज़ाना standup अपडेट में सारांशित करें।" + - "मैं जिन विषयों को ट्रैक करता हूं उन पर एक आवर्ती साप्ताहिक रिसर्च रिपोर्ट बनाएं।" + + - Event-Triggered Automations: जैसे ही कोई दस्तावेज़ किसी फ़ोल्डर में आता है, एजेंट को चलाएं और परिणाम अपने टूल में पोस्ट करें। + इस तरह के प्रॉम्प्ट आज़माएं: + + - "जब मेरे Research फ़ोल्डर में कोई PDF आए, तो उद्धरण सहित एक AI सारांश बनाएं।" + - "जब नई मीटिंग नोट्स जुड़ें, तो उन्हें एक्शन आइटम्स के साथ मीटिंग मिनट्स में बदलें।" + - "जब कोई इनवॉइस अपलोड हो, तो विक्रेता, कुल राशि और देय तिथि को एक तालिका में निकालें।" + - "जब मेरे Legal फ़ोल्डर में कोई अनुबंध आए, तो मुख्य शर्तों और नवीनीकरण तिथियों को चिह्नित करें।" + - "जब Candidates में कोई रिज़्यूमे जुड़े, तो उसे जॉब डिस्क्रिप्शन के विरुद्ध स्क्रीन करें।" + + - Chat-Built Automations: सरल भाषा में किसी ऑटोमेशन का वर्णन करें और SurfSense उसे आपके लिए बना देगा। + इस तरह के प्रॉम्प्ट आज़माएं: + + - "एक AI एजेंट बनाएं जो हर सुबह नई Notion पेजों का सारांश मुझे ईमेल करे।" + - "एक नो-कोड ऑटोमेशन बनाएं जो हर सप्ताह एक रिसर्च डाइजेस्ट Slack पर पोस्ट करे।" + - "एक AI नोट-टेकर सेट करें जो नई मीटिंग नोट्स को मिनट्स में बदल दे।" + - "एक वर्कफ़्लो बनाएं जो मीटिंग नोट्स से एक्शन आइटम्स निकाले और ज़िम्मेदार सौंपे।" + - "मेरे Gmail और Google Drive से एक रोज़ाना ईमेल ब्रीफ़ को ऑटोमेट करें।" ### सेल्फ-होस्टेड diff --git a/README.md b/README.md index 93ad51263..25a28c9f7 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,14 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

AI Image Generator

- - AI Resume Builder: draft and format an ATS-ready resume as a polished PDF. (coming soon) + - AI Resume Builder: tailor your existing resume to any job description and beat the ATS. + Try prompts like these: + + - "Tailor my resume to this job description so it gets past ATS and lands an interview." + - "Optimize my resume for ATS by matching the keywords in this job posting." + - "Rewrite my resume bullet points to highlight the skills this role is asking for." + - "Compare my resume against this job description and list the gaps to fix." + - "Write a matching cover letter from my resume and this job description." **Search & Chat** @@ -143,15 +150,44 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

Chat With Uploaded Files

- - Connector Write-Back: let the agent post results back to Notion, Slack, Linear and Drive. (coming soon) + - Connector Write-Back: let the agent post results back to Notion, Slack, Linear and Drive. + Try prompts like these: - - Obsidian & Knowledge Base Sync: keep your Obsidian vault and personal knowledge base in sync. (coming soon) + - "Post this research summary to my Notion workspace." + - "Send these meeting action items to our team Slack channel." + - "Create a Jira ticket from this bug report." + - "Open a Linear issue from this feature request." + - "Save this generated report to Google Drive as a doc." + - Obsidian & Knowledge Base Sync: keep your Obsidian vault and personal knowledge base in sync. **Automations** - - Scheduled AI Workflows: run an agent on a schedule: daily briefs, weekly digests, recurring reports. (coming soon) - - Event-Triggered Automations: fire an agent the moment a document lands in a folder, then post the result to your tools. (coming soon) - - Chat-Built Automations: describe an automation in plain English and SurfSense builds it for you. (coming soon) + - Scheduled AI Workflows: run an agent on a schedule: daily briefs, weekly digests, recurring reports. + Try prompts like these: + + - "Email me a daily brief of new documents in my knowledge base every morning." + - "Generate a weekly status report from my Slack and Gmail every Friday." + - "Run a monthly competitor analysis report and save it to my workspace." + - "Summarize my GitHub and Linear activity into a daily standup update." + - "Create a recurring weekly research report on the topics I track." + + - Event-Triggered Automations: fire an agent the moment a document lands in a folder, then post the result to your tools. + Try prompts like these: + + - "When a PDF lands in my Research folder, generate a cited AI summary." + - "When new meeting notes are added, turn them into meeting minutes with action items." + - "When an invoice is uploaded, extract the vendor, total, and due date into a table." + - "When a contract enters my Legal folder, flag key terms and renewal dates." + - "When a resume is added to Candidates, screen it against the job description." + + - Chat-Built Automations: describe an automation in plain English and SurfSense builds it for you. + Try prompts like these: + + - "Build an AI agent that emails me a summary of new Notion pages each morning." + - "Create a no-code automation that posts a weekly research digest to Slack." + - "Set up an AI note taker that turns new meeting notes into minutes." + - "Make a workflow that extracts action items from meeting notes and assigns owners." + - "Automate a daily email brief from my Gmail and Google Drive." ### Self Hosted diff --git a/README.pt-BR.md b/README.pt-BR.md index e7112bcaa..61d08afaa 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -112,7 +112,14 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

AI Image Generator

- - AI Resume Builder: monte e formate um currículo pronto para ATS em um PDF caprichado. (em breve) + - AI Resume Builder: adapte seu currículo atual a qualquer descrição de vaga e supere o ATS. + Experimente prompts como estes: + + - "Adapte meu currículo a esta descrição de vaga para passar pelo ATS e conseguir uma entrevista." + - "Otimize meu currículo para o ATS combinando as palavras-chave desta vaga." + - "Reescreva os tópicos do meu currículo para destacar as habilidades que esta vaga exige." + - "Compare meu currículo com esta descrição de vaga e liste as lacunas a corrigir." + - "Escreva uma carta de apresentação combinando com meu currículo e esta descrição de vaga." **Busca e Chat** @@ -142,15 +149,44 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

Chat With Uploaded Files

- - Connector Write-Back: deixe o agente publicar os resultados de volta no Notion, Slack, Linear e Drive. (em breve) + - Connector Write-Back: deixe o agente publicar os resultados de volta no Notion, Slack, Linear e Drive. + Experimente prompts como estes: - - Obsidian & Knowledge Base Sync: mantenha seu cofre do Obsidian e sua base de conhecimento pessoal sincronizados. (em breve) + - "Publique este resumo de pesquisa no meu espaço do Notion." + - "Envie estes itens de ação da reunião para o nosso canal do Slack." + - "Crie um ticket no Jira a partir deste relatório de bug." + - "Abra uma issue no Linear a partir desta solicitação de funcionalidade." + - "Salve este relatório gerado no Google Drive como um documento." + - Obsidian & Knowledge Base Sync: mantenha seu cofre do Obsidian e sua base de conhecimento pessoal sincronizados. **Automações** - - Scheduled AI Workflows: execute um agente em uma programação: resumos diários, boletins semanais, relatórios recorrentes. (em breve) - - Event-Triggered Automations: dispare um agente no momento em que um documento chega a uma pasta e publique o resultado nas suas ferramentas. (em breve) - - Chat-Built Automations: descreva uma automação em linguagem simples e o SurfSense a cria para você. (em breve) + - Scheduled AI Workflows: execute um agente em uma programação: resumos diários, boletins semanais, relatórios recorrentes. + Experimente prompts como estes: + + - "Envie-me todas as manhãs um resumo diário dos novos documentos na minha base de conhecimento." + - "Gere um relatório de status semanal a partir do meu Slack e Gmail toda sexta-feira." + - "Execute um relatório mensal de análise da concorrência e salve-o no meu espaço de trabalho." + - "Resuma minha atividade no GitHub e Linear em uma atualização diária de standup." + - "Crie um relatório de pesquisa semanal recorrente sobre os temas que acompanho." + + - Event-Triggered Automations: dispare um agente no momento em que um documento chega a uma pasta e publique o resultado nas suas ferramentas. + Experimente prompts como estes: + + - "Quando um PDF chegar à minha pasta de Pesquisa, gere um resumo com IA e citações." + - "Quando novas notas de reunião forem adicionadas, transforme-as em atas com itens de ação." + - "Quando uma fatura for enviada, extraia o fornecedor, o total e a data de vencimento em uma tabela." + - "Quando um contrato entrar na minha pasta Jurídica, sinalize os termos-chave e as datas de renovação." + - "Quando um currículo for adicionado a Candidatos, avalie-o em relação à descrição da vaga." + + - Chat-Built Automations: descreva uma automação em linguagem simples e o SurfSense a cria para você. + Experimente prompts como estes: + + - "Crie um agente de IA que me envie todas as manhãs um resumo das novas páginas do Notion." + - "Crie uma automação sem código que publique um resumo de pesquisa semanal no Slack." + - "Configure um anotador com IA que transforme as novas notas de reunião em atas." + - "Crie um fluxo que extraia os itens de ação das notas de reunião e atribua responsáveis." + - "Automatize um resumo diário por e-mail a partir do meu Gmail e Google Drive." ### Auto-Hospedado diff --git a/README.zh-CN.md b/README.zh-CN.md index cc73fbe6a..4548456e6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -112,7 +112,14 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

AI Image Generator

- - AI Resume Builder:撰写并排版一份适配 ATS 的精美 PDF 简历。(即将推出) + - AI Resume Builder:根据任意职位描述定制你现有的简历,顺利通过 ATS。 + 可以试试这样的提示: + + - “根据这份职位描述定制我的简历,让它通过 ATS 并赢得面试。” + - “匹配这份招聘启事中的关键词,为 ATS 优化我的简历。” + - “重写我的简历要点,突出这个岗位所需要的技能。” + - “将我的简历与这份职位描述对比,列出需要改进的差距。” + - “根据我的简历和这份职位描述,写一封相匹配的求职信。” **搜索与聊天** @@ -142,15 +149,44 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7

Chat With Uploaded Files

- - Connector Write-Back:让智能体将结果回写到 Notion、Slack、Linear 和 Drive。(即将推出) + - Connector Write-Back:让智能体将结果回写到 Notion、Slack、Linear 和 Drive。 + 可以试试这样的提示: - - Obsidian & Knowledge Base Sync:让你的 Obsidian 库与个人知识库保持同步。(即将推出) + - “把这份研究摘要发布到我的 Notion 工作区。” + - “把这些会议行动项发送到我们的团队 Slack 频道。” + - “根据这份缺陷报告创建一个 Jira 工单。” + - “根据这个功能需求在 Linear 中创建一个 issue。” + - “把这份生成的报告作为文档保存到 Google Drive。” + - Obsidian & Knowledge Base Sync:让你的 Obsidian 库与个人知识库保持同步。 **自动化** - - Scheduled AI Workflows:按计划运行智能体:每日简报、每周摘要、周期性报告。(即将推出) - - Event-Triggered Automations:在文档进入文件夹的那一刻触发智能体,并将结果发布到你的工具中。(即将推出) - - Chat-Built Automations:用通俗的语言描述一个自动化,SurfSense 就会为你构建它。(即将推出) + - Scheduled AI Workflows:按计划运行智能体:每日简报、每周摘要、周期性报告。 + 可以试试这样的提示: + + - “每天早上把我知识库中新增文档的每日简报发邮件给我。” + - “每周五根据我的 Slack 和 Gmail 生成一份每周状态报告。” + - “每月运行一次竞争对手分析报告并保存到我的工作区。” + - “把我的 GitHub 和 Linear 活动汇总成一份每日站会更新。” + - “针对我关注的主题创建一份周期性的每周研究报告。” + + - Event-Triggered Automations:在文档进入文件夹的那一刻触发智能体,并将结果发布到你的工具中。 + 可以试试这样的提示: + + - “当一个 PDF 进入我的 Research 文件夹时,生成一份带引用的 AI 摘要。” + - “当新增会议记录时,把它整理成带行动项的会议纪要。” + - “当上传发票时,把供应商、总额和到期日提取到一张表格中。” + - “当一份合同进入我的 Legal 文件夹时,标记关键条款和续约日期。” + - “当一份简历加入 Candidates 时,根据职位描述对其进行筛选。” + + - Chat-Built Automations:用通俗的语言描述一个自动化,SurfSense 就会为你构建它。 + 可以试试这样的提示: + + - “创建一个 AI 智能体,每天早上把新增 Notion 页面的摘要发邮件给我。” + - “创建一个无代码自动化,每周把研究摘要发布到 Slack。” + - “设置一个 AI 笔记助手,把新增会议记录整理成纪要。” + - “创建一个工作流,从会议记录中提取行动项并指派负责人。” + - “自动化一份来自我的 Gmail 和 Google Drive 的每日邮件简报。” ### 自托管 From 0ae30839aaf35dda31969698fdc7a4653ca7ce42 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 18:36:57 -0700 Subject: [PATCH 195/198] feat(docs): add AI automations and agents section to README files - Introduced a new feature highlighting AI automations and agents across multiple language README files. - Described capabilities for scheduling AI workflows and event-triggered actions, enhancing user understanding of automation functionalities. - Updated feature comparison tables to include AI automations, providing a clearer overview of available features. --- README.es.md | 2 ++ README.hi.md | 2 ++ README.md | 2 ++ README.pt-BR.md | 2 ++ README.zh-CN.md | 2 ++ 5 files changed, 10 insertions(+) diff --git a/README.es.md b/README.es.md index e7b61dd07..ffd4f5e5b 100644 --- a/README.es.md +++ b/README.es.md @@ -41,6 +41,7 @@ NotebookLM es una de las mejores y más útiles plataformas de IA que existen, p - **Sin Dependencia de Proveedores** - Configura cualquier modelo LLM, de imagen, TTS y STT. - **25+ Fuentes de Datos Externas** - Agrega tus fuentes desde Google Drive, OneDrive, Dropbox, Notion y muchos otros servicios externos. - **Soporte Multijugador en Tiempo Real** - Trabaja fácilmente con los miembros de tu equipo en un notebook compartido. +- **Automatizaciones y Agentes de IA** - Ejecuta agentes de IA según una programación o actívalos en el momento en que un documento llega a una carpeta, y luego escribe los resultados de vuelta en Notion, Slack, Linear y Drive. Crea automatizaciones sin código solo describiéndolas en el chat. - **Aplicación de Escritorio** - Obtén asistencia de IA en cualquier aplicación con Quick Assist, General Assist, Screenshot Assist y sincronización de carpetas locales. ...y más por venir. @@ -268,6 +269,7 @@ Todas las funciones operan contra tu espacio de búsqueda elegido, por lo que tu | **Generación de Videos** | Resúmenes en video cinemáticos vía Veo 3 (solo Ultra) | Disponible (NotebookLM es mejor aquí, mejorando activamente) | | **Generación de Presentaciones** | Diapositivas más atractivas pero no editables | Crea presentaciones editables basadas en diapositivas | | **Generación de Podcasts** | Resúmenes de audio con hosts e idiomas personalizables | Disponible con múltiples proveedores TTS (NotebookLM es mejor aquí, mejorando activamente) | +| **Automatizaciones y Agentes de IA** | No | Flujos de trabajo de IA programados, disparadores por eventos en documentos nuevos y automatizaciones sin código creadas por chat con escritura de vuelta a Notion, Slack, Linear y Jira | | **Aplicación de Escritorio** | No | Aplicación nativa con General Assist, Quick Assist, Screenshot Assist y sincronización de carpetas locales | | **Extensión de Navegador** | No | Extensión multi-navegador para guardar cualquier página web, incluyendo páginas protegidas por autenticación | diff --git a/README.hi.md b/README.hi.md index 99bb1c25c..b12842f9c 100644 --- a/README.hi.md +++ b/README.hi.md @@ -41,6 +41,7 @@ NotebookLM वहाँ उपलब्ध सबसे अच्छे और - **कोई विक्रेता लॉक-इन नहीं** - किसी भी LLM, इमेज, TTS और STT मॉडल को कॉन्फ़िगर करें। - **25+ बाहरी डेटा स्रोत** - Google Drive, OneDrive, Dropbox, Notion और कई अन्य बाहरी सेवाओं से अपने स्रोत जोड़ें। - **रीयल-टाइम मल्टीप्लेयर सपोर्ट** - एक साझा notebook में अपनी टीम के सदस्यों के साथ आसानी से काम करें। +- **AI ऑटोमेशन और एजेंट** - AI एजेंट को शेड्यूल पर चलाएं या जैसे ही कोई दस्तावेज़ किसी फ़ोल्डर में आए उसे ट्रिगर करें, फिर परिणाम वापस Notion, Slack, Linear और Drive में लिखें। चैट में बस वर्णन करके बिना-कोड ऑटोमेशन बनाएं। - **डेस्कटॉप ऐप** - Quick Assist, General Assist, Screenshot Assist और लोकल फ़ोल्डर सिंक के साथ किसी भी एप्लिकेशन में AI सहायता प्राप्त करें। ...और भी बहुत कुछ आने वाला है। @@ -268,6 +269,7 @@ SurfSense एक डेस्कटॉप ऐप भी प्रदान क | **वीडियो जनरेशन** | Veo 3 के माध्यम से सिनेमैटिक वीडियो ओवरव्यू (केवल Ultra) | उपलब्ध (NotebookLM यहाँ बेहतर है, सक्रिय रूप से सुधार हो रहा है) | | **प्रेजेंटेशन जनरेशन** | बेहतर दिखने वाली स्लाइड्स लेकिन संपादन योग्य नहीं | संपादन योग्य, स्लाइड आधारित प्रेजेंटेशन बनाएं | | **पॉडकास्ट जनरेशन** | कस्टमाइज़ेबल होस्ट और भाषाओं के साथ ऑडियो ओवरव्यू | कई TTS प्रदाताओं के साथ उपलब्ध (NotebookLM यहाँ बेहतर है, सक्रिय रूप से सुधार हो रहा है) | +| **AI ऑटोमेशन और एजेंट** | नहीं | शेड्यूल किए गए AI वर्कफ़्लो, नए दस्तावेज़ों पर इवेंट ट्रिगर, और चैट से बने बिना-कोड ऑटोमेशन, Notion, Slack, Linear और Jira में कनेक्टर राइट-बैक के साथ | | **डेस्कटॉप ऐप** | नहीं | General Assist, Quick Assist, Screenshot Assist और लोकल फ़ोल्डर सिंक के साथ नेटिव ऐप | | **ब्राउज़र एक्सटेंशन** | नहीं | किसी भी वेबपेज को सहेजने के लिए क्रॉस-ब्राउज़र एक्सटेंशन, प्रमाणीकरण सुरक्षित पेज सहित | diff --git a/README.md b/README.md index 25a28c9f7..d9beb5da4 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ NotebookLM is one of the best and most useful AI platforms out there, but once y - **25+ External Data Sources** - Add your sources from Google Drive, OneDrive, Dropbox, Notion, and many other external services. - **Real-Time Multiplayer Support** - Work easily with your team members in a shared notebook. - **AI File Sorting** - Automatically organize your documents into a smart folder hierarchy using AI-powered categorization by source, date, and topic. +- **AI Automations & Agents** - Run AI agents on a schedule or trigger them the moment a document lands in a folder, then write results back to Notion, Slack, Linear, and Drive. Build no-code automations just by describing them in chat. - **Desktop App** - Get AI assistance in any application with Quick Assist, General Assist, Screenshot Assist, and local folder sync. ...and more to come. @@ -270,6 +271,7 @@ All features operate against your chosen search space, so your answers are alway | **Presentation Generation** | Better looking slides but not editable | Create editable, slide-based presentations | | **Podcast Generation** | Audio Overviews with customizable hosts and languages | Available with multiple TTS providers (NotebookLM is better here, actively improving) | | **AI File Sorting** | No | LLM-powered auto-categorization into source, date, category, and subcategory folders | +| **AI Automations & Agents** | No | Scheduled AI workflows, event triggers on new documents, and chat-built no-code automations with connector write-back to Notion, Slack, Linear & Jira | | **Desktop App** | No | Native app with General Assist, Quick Assist, Screenshot Assist, and local folder sync | | **Browser Extension** | No | Cross-browser extension to save any webpage, including auth-protected pages | diff --git a/README.pt-BR.md b/README.pt-BR.md index 61d08afaa..1b8c6f332 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -41,6 +41,7 @@ O NotebookLM é uma das melhores e mais úteis plataformas de IA disponíveis, m - **Sem Dependência de Fornecedor** - Configure qualquer modelo LLM, de imagem, TTS e STT. - **25+ Fontes de Dados Externas** - Adicione suas fontes do Google Drive, OneDrive, Dropbox, Notion e muitos outros serviços externos. - **Suporte Multiplayer em Tempo Real** - Trabalhe facilmente com os membros da sua equipe em um notebook compartilhado. +- **Automações e Agentes de IA** - Execute agentes de IA em uma programação ou dispare-os no momento em que um documento chega a uma pasta, e escreva os resultados de volta no Notion, Slack, Linear e Drive. Crie automações sem código apenas descrevendo-as no chat. - **Aplicativo Desktop** - Obtenha assistência de IA em qualquer aplicativo com Quick Assist, General Assist, Screenshot Assist e sincronização de pastas locais. ...e mais por vir. @@ -268,6 +269,7 @@ Todos os recursos operam no espaço de busca escolhido, para que suas respostas | **Geração de Vídeos** | Visões gerais cinemáticas via Veo 3 (apenas Ultra) | Disponível (NotebookLM é melhor aqui, melhorando ativamente) | | **Geração de Apresentações** | Slides mais bonitos mas não editáveis | Cria apresentações editáveis baseadas em slides | | **Geração de Podcasts** | Visões gerais em áudio com hosts e idiomas personalizáveis | Disponível com múltiplos provedores TTS (NotebookLM é melhor aqui, melhorando ativamente) | +| **Automações e Agentes de IA** | Não | Fluxos de trabalho de IA agendados, gatilhos por eventos em novos documentos e automações sem código criadas por chat com escrita de volta no Notion, Slack, Linear e Jira | | **Aplicativo Desktop** | Não | Aplicativo nativo com General Assist, Quick Assist, Screenshot Assist e sincronização de pastas locais | | **Extensão de Navegador** | Não | Extensão multi-navegador para salvar qualquer página web, incluindo páginas protegidas por autenticação | diff --git a/README.zh-CN.md b/README.zh-CN.md index 4548456e6..131393c77 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -41,6 +41,7 @@ NotebookLM 是目前最好、最实用的 AI 平台之一,但当你开始经 - **无供应商锁定** - 配置任何 LLM、图像、TTS 和 STT 模型。 - **25+ 外部数据源** - 从 Google Drive、OneDrive、Dropbox、Notion 和许多其他外部服务添加你的来源。 - **实时多人协作支持** - 在共享笔记本中轻松与团队成员协作。 +- **AI 自动化与智能体** - 按计划运行 AI 智能体,或在文档进入文件夹的那一刻触发它们,然后将结果回写到 Notion、Slack、Linear 和 Drive。只需在聊天中描述即可创建无代码自动化。 - **桌面应用** - 通过 Quick Assist、General Assist、Screenshot Assist 和本地文件夹同步在任何应用程序中获得 AI 助手。 ...更多功能即将推出。 @@ -268,6 +269,7 @@ SurfSense 还提供桌面应用,将 AI 助手带到您计算机上的每个应 | **视频生成** | 通过 Veo 3 的电影级视频概览(仅 Ultra) | 可用(NotebookLM 在此方面更好,正在积极改进) | | **演示文稿生成** | 更美观的幻灯片但不可编辑 | 创建可编辑的幻灯片式演示文稿 | | **播客生成** | 可自定义主持人和语言的音频概览 | 可用,支持多种 TTS 提供商(NotebookLM 在此方面更好,正在积极改进) | +| **AI 自动化与智能体** | 否 | 定时 AI 工作流、新文档的事件触发,以及通过聊天构建的无代码自动化,支持回写到 Notion、Slack、Linear 和 Jira | | **桌面应用** | 否 | 原生应用,包含 General Assist、Quick Assist、Screenshot Assist 和本地文件夹同步 | | **浏览器扩展** | 否 | 跨浏览器扩展,保存任何网页,包括需要身份验证的页面 | From ec0342faa27e60c7d90802d6b2b071fa0d6d2e41 Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 18:51:49 -0700 Subject: [PATCH 196/198] feat(announcements): enhance AnnouncementCard and add spotlight feature - Added image support to the AnnouncementCard component for improved visual presentation of announcements. - Introduced a spotlight feature in the announcement types to allow critical announcements to be displayed in a blocking dialog until acknowledged. - Updated AnnouncementToastProvider to skip spotlight announcements to prevent duplicate notifications. - Included a new AI automation announcement with an image in the announcements data for demonstration purposes. --- .../announcements/AnnouncementCard.tsx | 14 ++- .../announcements/AnnouncementSpotlight.tsx | 101 ++++++++++++++++++ .../AnnouncementToastProvider.tsx | 4 +- .../layout/providers/LayoutDataProvider.tsx | 2 + .../contracts/types/announcement.types.ts | 14 +++ .../lib/announcements/announcements-data.ts | 21 ++++ .../public/announcements/automations.png | Bin 0 -> 4420756 bytes 7 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 surfsense_web/components/announcements/AnnouncementSpotlight.tsx create mode 100644 surfsense_web/public/announcements/automations.png diff --git a/surfsense_web/components/announcements/AnnouncementCard.tsx b/surfsense_web/components/announcements/AnnouncementCard.tsx index ea0288b43..83a0e09b8 100644 --- a/surfsense_web/components/announcements/AnnouncementCard.tsx +++ b/surfsense_web/components/announcements/AnnouncementCard.tsx @@ -1,6 +1,7 @@ "use client"; import { Bell, ExternalLink, Info, type LucideIcon, Rocket, Wrench, Zap } from "lucide-react"; +import Image from "next/image"; import Link from "next/link"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -49,7 +50,18 @@ export function AnnouncementCard({ announcement }: { announcement: AnnouncementW const Icon = config.icon; return ( - + + {announcement.image && ( +
+ {announcement.image.alt} +
+ )}
diff --git a/surfsense_web/components/announcements/AnnouncementSpotlight.tsx b/surfsense_web/components/announcements/AnnouncementSpotlight.tsx new file mode 100644 index 000000000..794a3c1cf --- /dev/null +++ b/surfsense_web/components/announcements/AnnouncementSpotlight.tsx @@ -0,0 +1,101 @@ +"use client"; + +import { ExternalLink } from "lucide-react"; +import Image from "next/image"; +import Link from "next/link"; +import { useEffect, useMemo, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogTitle, +} from "@/components/ui/dialog"; +import { useAnnouncements } from "@/hooks/use-announcements"; + +/** + * Proactively shows important "spotlight" announcements in a blocking dialog. + * + * Behaviour: + * - On load, the first active, audience-matched, unread spotlight announcement + * is shown automatically. + * - The user must explicitly acknowledge it ("Got it" or the CTA link), which + * marks it as read so it never shows again. + * - Closing via the X / Escape / outside-click only hides it for the current + * session; it reappears on the next load until the user marks it as seen. + */ +export function AnnouncementSpotlight() { + const { announcements, markRead } = useAnnouncements(); + const [sessionDismissed, setSessionDismissed] = useState>(() => new Set()); + const [ready, setReady] = useState(false); + + // Short delay so the spotlight doesn't flash during initial hydration/layout. + useEffect(() => { + const timer = setTimeout(() => setReady(true), 800); + return () => clearTimeout(timer); + }, []); + + const current = useMemo( + () => + announcements.find( + (a) => a.spotlight && a.isImportant && !a.isRead && !sessionDismissed.has(a.id) + ) ?? null, + [announcements, sessionDismissed] + ); + + if (!current) return null; + + const handleAcknowledge = () => { + markRead(current.id); + }; + + const handleOpenChange = (next: boolean) => { + if (!next) { + setSessionDismissed((prev) => { + const updated = new Set(prev); + updated.add(current.id); + return updated; + }); + } + }; + + return ( + + + {current.image && ( +
+ {current.image.alt} +
+ )} +
+ {current.title} + + {current.description} + + + {current.link && ( + + )} + + +
+
+
+ ); +} diff --git a/surfsense_web/components/announcements/AnnouncementToastProvider.tsx b/surfsense_web/components/announcements/AnnouncementToastProvider.tsx index 3e99f5c32..5578eba17 100644 --- a/surfsense_web/components/announcements/AnnouncementToastProvider.tsx +++ b/surfsense_web/components/announcements/AnnouncementToastProvider.tsx @@ -70,8 +70,10 @@ export function AnnouncementToastProvider() { const outerTimer = setTimeout(() => { const authed = isAuthenticated(); const active = getActiveAnnouncements(announcements, authed); + // Spotlight announcements are handled by the blocking spotlight dialog, + // so skip them here to avoid double-notifying the user. const importantUntoasted = active.filter( - (a) => a.isImportant && !isAnnouncementToasted(a.id) + (a) => a.isImportant && !a.spotlight && !isAnnouncementToasted(a.id) ); for (let i = 0; i < importantUntoasted.length; i++) { diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index 4284e3da7..34fd15e3b 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -18,6 +18,7 @@ import { searchSpacesAtom } from "@/atoms/search-spaces/search-space-query.atoms import { removeChatTabAtom, syncChatTabAtom, type Tab } from "@/atoms/tabs/tabs.atom"; import { currentUserAtom } from "@/atoms/user/user-query.atoms"; import { ActionLogDialog } from "@/components/agent-action-log/action-log-dialog"; +import { AnnouncementSpotlight } from "@/components/announcements/AnnouncementSpotlight"; import { AnnouncementsDialog } from "@/components/announcements/AnnouncementsDialog"; import { AlertDialog, @@ -909,6 +910,7 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid /> + {/* Agent action log + revert dialog */} diff --git a/surfsense_web/contracts/types/announcement.types.ts b/surfsense_web/contracts/types/announcement.types.ts index 6c5206d9d..05cf14d21 100644 --- a/surfsense_web/contracts/types/announcement.types.ts +++ b/surfsense_web/contracts/types/announcement.types.ts @@ -35,6 +35,20 @@ export interface Announcement { audience: AnnouncementAudience; /** If true, the user will see a toast notification for this announcement */ isImportant: boolean; + /** + * If true, this announcement is shown in a blocking spotlight dialog that the + * user must explicitly acknowledge ("Got it"). Until acknowledged it keeps + * reappearing; once acknowledged it never shows again. Spotlight announcements + * are skipped by the lightweight toast provider to avoid double notifications. + */ + spotlight?: boolean; + /** Optional head/banner image shown at the top of the announcement */ + image?: { + /** Image source (public path or absolute URL) */ + src: string; + /** Accessible alt text */ + alt: string; + }; /** Optional CTA link */ link?: { label: string; diff --git a/surfsense_web/lib/announcements/announcements-data.ts b/surfsense_web/lib/announcements/announcements-data.ts index ce44ec539..e5f3f0fce 100644 --- a/surfsense_web/lib/announcements/announcements-data.ts +++ b/surfsense_web/lib/announcements/announcements-data.ts @@ -13,6 +13,27 @@ import type { Announcement } from "@/contracts/types/announcement.types"; * This file can be replaced with an API call in the future. */ export const announcements: Announcement[] = [ + { + id: "2026-05-31-ai-automations", + title: "Introducing AI Automations", + description: + "Turn prompts into hands-off AI agent workflows. Describe an automation in plain English and SurfSense builds it, run it on a schedule, or trigger it the moment a document lands in a folder. Automations work across Notion, Slack, Google Drive, Gmail, GitHub, Linear, Jira and more.", + category: "feature", + date: "2026-05-31T00:00:00Z", + startTime: "2026-05-31T00:00:00Z", + endTime: "2026-07-15T00:00:00Z", + audience: "users", + isImportant: true, + spotlight: true, + image: { + src: "/announcements/automations.png", + alt: "Connector tiles flowing into a central AI core that triggers scheduled and event-driven automations.", + }, + link: { + label: "See what's new", + url: "/changelog", + }, + }, { id: "announcement-1", title: "Introducing What's New", diff --git a/surfsense_web/public/announcements/automations.png b/surfsense_web/public/announcements/automations.png new file mode 100644 index 0000000000000000000000000000000000000000..39eb3583707763ab6b544aad7e76ed5253c1b397 GIT binary patch literal 4420756 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

9`~&$Rm8H3 zu&eu}6Db6DA*+L;;Tk7r?6Ib*qkc#p|=0 zS#Dbs9U~gZB-pU?;A7)$Y~q4b{PfY+;X z9(w!gOiw_QB@|&Ou>X&&ciFY2+p@F1uaCL&f{hKx7|B_24Ot+tge53o2_vw{B2R!Q z@{C+0Bx+FMArM%$N%>@+GqN`pS6_XXWz>#DtL^fQR zerW+$#2fcsg}~j-@1uXoTUi7S9$K6f|7MK0Ldk39214hXQ95$>@JnX-j$h>eCmyNp zGzbhK!`a;%)?PreL6X}n1{gk=*pLAfy9KEadgtNVz0#-zyuEcv|Ea zOh}ZJmxOm^qIPL4>UmecrYT2f?ph4tU58C27t&whwNNX-+Lfebqs`KA?cal7^|d25 z3fY<(D8kOXD{pB!Y_ymGUooH-Av3hzz}L^afCq5XuC_NqQMV7(i4ONBP^~z-+n|&L zlI-Z<*Yg;vy3pY(2LYKOyH#J=>+yneyIn0g0m=vNEUBe#=p=Unz&oQbcj9bU!yt{H zLbl_O2H0L(g;OU2AWh8BK$;|oBhzw`HfJ4KrS{rT|LDEO8(Xv6R^n9vl)Or9|3Iia zK;H{{`E-SxCk8eW5|xxOFQ2qQO>(V#+142~jj)$cV6qC{)1R*Rt`&X`NiT$KcanZImPbjg!rV3ZtDqZIZsL1kEoTu^1I4yBN71qyO+%0A1} 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

XE3lpQr4-&MIC4ICk9b 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%0DpL_GsM3a{wF@9vy7>hC_g= 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 wcLP!sG%VKmuI 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

); diff --git a/surfsense_web/components/homepage/features-bento-grid.tsx b/surfsense_web/components/homepage/features-bento-grid.tsx index 7406223de..49884bf28 100644 --- a/surfsense_web/components/homepage/features-bento-grid.tsx +++ b/surfsense_web/components/homepage/features-bento-grid.tsx @@ -1,5 +1,6 @@ import { IconBinaryTree, + IconBolt, IconMessage, IconMicrophone, IconSearch, @@ -709,6 +710,236 @@ const AiSortIllustration = () => (
); +const AutomationIllustration = () => ( +
+ + + AI automation flow illustration showing a trigger starting an AI agent that acts across + connectors + + + {/* Animated flow connectors */} + + + + + + + + + + + + + + {/* Trigger node */} + + + + Trigger + + {/* Schedule chip */} + + + + + + + Cron + + + {/* Event chip */} + + + + + Event + + + + + {/* AI Agent core */} + + + + AI Agent + + {/* Sparkle */} + + + + + + + + + + + {/* Actions across connectors */} + + + + Act on Connectors + + + + + + + + 25+ + + + + + {/* Sparkle accents */} + + + + + + + + + +
+); + const items = [ { title: "Find, Ask, Act", @@ -749,4 +980,12 @@ const items = [ className: "md:col-span-1", icon: , }, + { + title: "Automate Your Workflows", + description: + "Describe an AI agent in plain English and SurfSense builds it. Run it on a schedule or trigger it when a document lands, acting across all your connectors hands-free.", + header: , + className: "md:col-span-3", + icon: , + }, ]; diff --git a/surfsense_web/components/homepage/why-surfsense.tsx b/surfsense_web/components/homepage/why-surfsense.tsx index 8eeaf9874..38f19559b 100644 --- a/surfsense_web/components/homepage/why-surfsense.tsx +++ b/surfsense_web/components/homepage/why-surfsense.tsx @@ -348,6 +348,11 @@ const comparisonRows: { notebookLm: false, surfSense: true, }, + { + feature: "AI Automations & Agents", + notebookLm: false, + surfSense: "Scheduled & event-triggered", + }, { feature: "AI File Sorting", notebookLm: false, diff --git a/surfsense_web/components/new-chat/chat-example-prompts.tsx b/surfsense_web/components/new-chat/chat-example-prompts.tsx new file mode 100644 index 000000000..95d7a0eaa --- /dev/null +++ b/surfsense_web/components/new-chat/chat-example-prompts.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { CornerDownLeft, Lightbulb } from "lucide-react"; +import { memo, useCallback } from "react"; +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { CHAT_EXAMPLE_CATEGORIES } from "@/lib/chat/example-prompts"; + +interface ChatExamplePromptsProps { + /** Called with the chosen prompt text; the caller prefills the composer. */ + onSelect: (prompt: string) => void; +} + +const ExamplePromptButton = memo(function ExamplePromptButton({ + prompt, + onSelect, +}: { + prompt: string; + onSelect: (prompt: string) => void; +}) { + const handleClick = useCallback(() => onSelect(prompt), [prompt, onSelect]); + + return ( + + ); +}); + +export function ChatExamplePrompts({ onSelect }: ChatExamplePromptsProps) { + return ( +
+
+
+ +
+ + {CHAT_EXAMPLE_CATEGORIES.map((category) => ( + + {category.label} + + ))} + +
+ {CHAT_EXAMPLE_CATEGORIES.map((category) => ( + + +
    + {category.prompts.map((prompt) => ( +
  • + +
  • + ))} +
+
+
+ ))} +
+
+ ); +} diff --git a/surfsense_web/components/pricing/pricing-section.tsx b/surfsense_web/components/pricing/pricing-section.tsx index b2eef2778..46ceee694 100644 --- a/surfsense_web/components/pricing/pricing-section.tsx +++ b/surfsense_web/components/pricing/pricing-section.tsx @@ -4,6 +4,7 @@ import { AnimatePresence, motion } from "motion/react"; import type React from "react"; import { useEffect, useRef, useState } from "react"; import { Pricing } from "@/components/pricing"; +import { FAQJsonLd } from "@/components/seo/json-ld"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; @@ -19,6 +20,8 @@ const demoPlans = [ "500 pages included to start", "$5 in premium credits for paid AI models and premium AI features", "Includes access to OpenAI text, audio and image models", + "AI automations and agents: scheduled and event-triggered workflows", + "Desktop app: Quick, General and Screenshot Assist plus local folder sync", "Realtime Collaborative Group Chats with teammates", "Community support on Discord", ], @@ -37,6 +40,7 @@ const demoPlans = [ "Everything in Free", "Buy 1,000-page packs or $1 in premium credits at $1 each", "Use premium AI models like GPT-5.4, Claude Sonnet 4.6, Gemini 2.5 Pro & 100+ more via OpenRouter", + "Connector write-back to Notion, Slack, Linear & Jira", "Priority support on Discord", ], description: "", @@ -52,6 +56,7 @@ const demoPlans = [ billingText: "", features: [ "Everything in Pay As You Go", + "Custom automation and agent workflows", "On-prem or VPC deployment", "Audit logs and compliance", "SSO, OIDC & SAML", @@ -158,6 +163,31 @@ const faqData: FAQSection[] = [ }, ], }, + { + title: "Automations & Agents", + items: [ + { + question: "What can AI automations and agents do?", + answer: + "AI automations let you run agents on your knowledge base without writing code. You can schedule recurring workflows like daily briefs, weekly status reports, and competitor analysis, or trigger an agent the moment a document lands in a folder. Agents can read across your connected tools, generate summaries and reports, and write results back to Notion, Slack, Linear, and Jira.", + }, + { + question: "Do automations and agents cost extra?", + answer: + "No. There is no separate subscription or add-on fee for automations. Agents use the same page credits and premium credits as the rest of SurfSense. Indexing documents consumes page credits, and premium AI model usage during a workflow consumes premium credits at provider cost. If a workflow only uses free models, it does not touch your premium credits.", + }, + { + question: "How do event-triggered automations work?", + answer: + "Event-triggered automations fire when something happens in your knowledge base, most commonly when a new document enters a folder you are watching. For example, when a PDF lands in your Research folder you can auto-generate a cited summary, or when an invoice is uploaded you can extract the vendor, total, and due date. The agent runs automatically and can post the result to your connected tools.", + }, + { + question: "Can I build an automation without code?", + answer: + "Yes. You can describe the workflow automation you want in plain English in chat, and SurfSense builds the automation for you. For example, ask it to email you a summary of new Notion pages each morning, or post a weekly research digest to Slack, and it sets up the scheduled or event-triggered agent without any code.", + }, + ], + }, { title: "Self-Hosting", items: [ @@ -250,6 +280,7 @@ function PricingFAQ() { return (
+ section.items)} />

Frequently Asked Questions @@ -341,7 +372,7 @@ function PricingBasic() { diff --git a/surfsense_web/components/seo/json-ld.tsx b/surfsense_web/components/seo/json-ld.tsx index 03f3293c3..abfb49f0a 100644 --- a/surfsense_web/components/seo/json-ld.tsx +++ b/surfsense_web/components/seo/json-ld.tsx @@ -77,6 +77,9 @@ export function SoftwareApplicationJsonLd() { "Free access to ChatGPT, Claude AI, and any AI model", "AI-powered semantic search across all connected tools", "Federated search across Slack, Google Drive, Notion, Confluence, GitHub", + "AI automations and agents (scheduled and event-triggered workflows)", + "Connector write-back to Notion, Slack, Linear, Jira", + "Native desktop app with Quick, General, and Screenshot Assist", "No data limits with open source self-hosting", "Real-time collaborative team chats", "Document Q&A with citations", diff --git a/surfsense_web/lib/chat/example-prompts.ts b/surfsense_web/lib/chat/example-prompts.ts new file mode 100644 index 000000000..76b64b2ba --- /dev/null +++ b/surfsense_web/lib/chat/example-prompts.ts @@ -0,0 +1,66 @@ +/** + * Curated example chat prompts shown on the empty new-chat screen. + * + * These mirror the homepage hero's "use case" concept but with runnable chat + * queries, grouped into a few broad categories. Bracketed slots like `[topic]` + * are intentional: clicking a prompt prefills the composer so the user can fill + * them in before sending. + * + * This is a module-scope constant so it is created once, not per render. + */ + +export interface ChatExampleCategory { + /** Stable id used as the Tabs value */ + id: string; + /** Short, human-readable tab label */ + label: string; + /** Runnable example queries for this category */ + prompts: string[]; +} + +export const CHAT_EXAMPLE_CATEGORIES: ChatExampleCategory[] = [ + { + id: "search", + label: "Search & Summarize", + prompts: [ + "Summarize the key points across all the documents in this space.", + "What do my files say about [topic]? Answer with citations.", + "Find every mention of [keyword] and list the sources.", + "Give me a cited briefing on the documents I added this week.", + "Compare these two documents and highlight the differences.", + ], + }, + { + id: "create", + label: "Create", + prompts: [ + "Write a cited research report on [topic] from my documents.", + "Turn this folder into a two-host podcast I can listen to.", + "Create a slide deck and a narrated video overview from these sources.", + "Generate an image to illustrate [concept] for my report.", + "Tailor my resume to this job description so it gets past ATS and lands an interview.", + ], + }, + { + id: "automate", + label: "Automate", + prompts: [ + "Email me a daily brief of new documents in my knowledge base every morning.", + "When a PDF lands in my Research folder, generate a cited AI summary.", + "Generate a weekly status report from my Slack and Gmail every Friday.", + "Build an automation that turns new meeting notes into minutes with action items.", + "Run a monthly competitor analysis report and save it to my workspace.", + ], + }, + { + id: "tools", + label: "Across your tools", + prompts: [ + "Search across my Notion, Slack, Google Drive and Gmail for [topic].", + "Post this research summary to my Notion workspace.", + "Send these meeting action items to our team Slack channel.", + "Create a Jira ticket from this bug report.", + "Open a Linear issue from this feature request.", + ], + }, +]; From cfa52929cc7cf0e3d0874554548a24c047d9a7dd Mon Sep 17 00:00:00 2001 From: "DESKTOP-RTLN3BA\\$punk" Date: Sun, 31 May 2026 19:29:36 -0700 Subject: [PATCH 198/198] fix(docs): add missing line breaks in README files - Added line breaks in the automations section across multiple language README files for improved readability and formatting consistency. --- README.es.md | 1 + README.hi.md | 1 + README.md | 1 + README.pt-BR.md | 1 + README.zh-CN.md | 1 + 5 files changed, 5 insertions(+) diff --git a/README.es.md b/README.es.md index ffd4f5e5b..ea7623617 100644 --- a/README.es.md +++ b/README.es.md @@ -160,6 +160,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 - "Guarda este informe generado en Google Drive como un documento." - Obsidian & Knowledge Base Sync: mantén tu bóveda de Obsidian y tu base de conocimiento personal sincronizadas. + **Automatizaciones** - Scheduled AI Workflows: ejecuta un agente según una programación: resúmenes diarios, boletines semanales, informes recurrentes. diff --git a/README.hi.md b/README.hi.md index b12842f9c..10b246385 100644 --- a/README.hi.md +++ b/README.hi.md @@ -160,6 +160,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 - "इस जनरेट की गई रिपोर्ट को Google Drive में एक डॉक के रूप में सेव करें।" - Obsidian & Knowledge Base Sync: अपने Obsidian vault और व्यक्तिगत नॉलेज बेस को सिंक रखें। + **ऑटोमेशन** - Scheduled AI Workflows: किसी एजेंट को शेड्यूल पर चलाएं: रोज़ाना ब्रीफ़, साप्ताहिक डाइजेस्ट, आवर्ती रिपोर्ट। diff --git a/README.md b/README.md index d9beb5da4..a75122892 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 - "Save this generated report to Google Drive as a doc." - Obsidian & Knowledge Base Sync: keep your Obsidian vault and personal knowledge base in sync. + **Automations** - Scheduled AI Workflows: run an agent on a schedule: daily briefs, weekly digests, recurring reports. diff --git a/README.pt-BR.md b/README.pt-BR.md index 1b8c6f332..db77e5132 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -160,6 +160,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 - "Salve este relatório gerado no Google Drive como um documento." - Obsidian & Knowledge Base Sync: mantenha seu cofre do Obsidian e sua base de conhecimento pessoal sincronizados. + **Automações** - Scheduled AI Workflows: execute um agente em uma programação: resumos diários, boletins semanais, relatórios recorrentes. diff --git a/README.zh-CN.md b/README.zh-CN.md index 131393c77..d3d5330f6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -160,6 +160,7 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7 - “把这份生成的报告作为文档保存到 Google Drive。” - Obsidian & Knowledge Base Sync:让你的 Obsidian 库与个人知识库保持同步。 + **自动化** - Scheduled AI Workflows:按计划运行智能体:每日简报、每周摘要、周期性报告。