diff --git a/docs/ref/extensions/sandbox/aliyun/sandbox.md b/docs/ref/extensions/sandbox/aliyun/sandbox.md new file mode 100644 index 0000000000..dd31d32b3f --- /dev/null +++ b/docs/ref/extensions/sandbox/aliyun/sandbox.md @@ -0,0 +1,3 @@ +# `Sandbox` + +::: agents.extensions.sandbox.aliyun.sandbox diff --git a/docs/sandbox/clients.md b/docs/sandbox/clients.md index bd21da63d3..9ab8093df1 100644 --- a/docs/sandbox/clients.md +++ b/docs/sandbox/clients.md @@ -90,6 +90,7 @@ For provider-specific setup notes and links for the checked-in extension example | Client | Install | Example | | --- | --- | --- | +| `AliyunSandboxClient` | `openai-agents[aliyun]` | [Aliyun runner](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/aliyun_runner.py) | | `BlaxelSandboxClient` | `openai-agents[blaxel]` | [Blaxel runner](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/blaxel_runner.py) | | `CloudflareSandboxClient` | `openai-agents[cloudflare]` | [Cloudflare runner](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/cloudflare_runner.py) | | `DaytonaSandboxClient` | `openai-agents[daytona]` | [Daytona runner](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/daytona/daytona_runner.py) | @@ -114,6 +115,7 @@ Hosted sandbox clients expose provider-specific mount strategies. Choose the bac | `E2BSandboxClient` | Supports rclone-backed cloud storage mounts with `E2BCloudBucketMountStrategy`; use it with `S3Mount`, `GCSMount`, `R2Mount`, `AzureBlobMount`, and `BoxMount`. | | `RunloopSandboxClient` | Supports rclone-backed cloud storage mounts with `RunloopCloudBucketMountStrategy`; use it with `S3Mount`, `GCSMount`, `R2Mount`, `AzureBlobMount`, and `BoxMount`. | | `VercelSandboxClient` | No hosted-specific mount strategy is currently exposed. Use manifest files, repos, or other workspace inputs instead. | +| `AliyunSandboxClient` | No hosted-specific mount strategy is currently exposed. AgentRun also does not expose tunneled ports. Use manifest files or workspace inputs instead. | @@ -131,6 +133,7 @@ The table below summarizes which remote storage entries each backend can mount d | `E2BSandboxClient` | ✓ | ✓ | ✓ | ✓ | ✓ | - | | `RunloopSandboxClient` | ✓ | ✓ | ✓ | ✓ | ✓ | - | | `VercelSandboxClient` | - | - | - | - | - | - | +| `AliyunSandboxClient` | - | - | - | - | - | - | diff --git a/examples/sandbox/extensions/README.md b/examples/sandbox/extensions/README.md index 7b5c3c0615..9cf76893fe 100644 --- a/examples/sandbox/extensions/README.md +++ b/examples/sandbox/extensions/README.md @@ -6,7 +6,7 @@ They intentionally keep the flow simple: 1. Build a tiny manifest in memory. 2. Create a `SandboxAgent` that inspects that workspace through one shell tool. -3. Run the agent against E2B, Modal, Daytona, Cloudflare, Runloop, Blaxel, or Vercel. +3. Run the agent against E2B, Modal, Daytona, Cloudflare, Runloop, Blaxel, Vercel, or Aliyun. All of these examples require `OPENAI_API_KEY`, because they call the model through the normal `Runner` path. Each cloud backend also needs its own provider credentials. @@ -350,4 +350,57 @@ The runner also includes standalone demos for individual features. Pass - `pty` -- agent-driven interactive Python session via PTY - `drive` -- [Blaxel Drive mount](https://docs.blaxel.ai/Agent-drive/Overview) (persistent storage, requires `--drive-name`) -Blaxel sandboxes support cloud bucket mounts (S3, R2, GCS) through `BlaxelCloudBucketMountStrategy` and persistent drive mounts through `BlaxelDriveMountStrategy`. See the [Blaxel Drive docs](https://docs.blaxel.ai/Agent-drive/Overview) for details. +Blaxel sandboxes support cloud bucket mounts (S3, R2, GCS) through +`BlaxelCloudBucketMountStrategy` and persistent drive mounts through +`BlaxelDriveMountStrategy`. See the +[Blaxel Drive docs](https://docs.blaxel.ai/Agent-drive/Overview) for details. + +## Aliyun + +### Setup + +Install the repo extra: + +```bash +uv sync --extra aliyun +``` + +Create an Alibaba Cloud account, enable AgentRun, and obtain access credentials. +The official references are: + +- +- + +Export the required environment variables: + +```bash +export OPENAI_API_KEY=... +export ALIBABA_CLOUD_ACCESS_KEY_ID=... +export ALIBABA_CLOUD_ACCESS_KEY_SECRET=... +# AgentRun builds its data-plane endpoint from the account id, so set one of these +# (or pass `--account-id`) — otherwise sandbox creation fails with "account id is not set": +export AGENTRUN_ACCOUNT_ID=... # or FC_ACCOUNT_ID, or AGENTRUN_DATA_ENDPOINT +``` + +`agentrun-sdk` resolves credentials through the standard Alibaba Cloud +credential providers, so any of the supported credential sources (env vars, +config file, RAM role) will work. You can also pass credentials explicitly via +CLI flags below. + +### Run + +```bash +uv run python examples/sandbox/extensions/aliyun_runner.py --stream +``` + +Useful flags: + +- `--region cn-hangzhou` -- AgentRun region. Default `cn-hangzhou`. +- `--template-name code-interpreter` -- AgentRun template to launch. +- `--sandbox-idle-timeout-seconds 3600` -- Idle timeout before the remote sandbox is reclaimed. +- `--access-key-id` / `--access-key-secret` / `--account-id` / `--api-key` -- override credentials per run. +- `--stream` -- stream model output to the terminal. + +The Aliyun example stays on the non-PTY path. It covers command execution, +workspace materialization, and stop/resume persistence. AgentRun does not +currently expose tunneled ports or hosted-specific mount strategies. diff --git a/examples/sandbox/extensions/aliyun_runner.py b/examples/sandbox/extensions/aliyun_runner.py new file mode 100644 index 0000000000..b0e6aa12b7 --- /dev/null +++ b/examples/sandbox/extensions/aliyun_runner.py @@ -0,0 +1,359 @@ +""" +Minimal Aliyun-backed sandbox example for manual validation. + +This mirrors the other cloud extension examples: it creates a tiny workspace, +verifies stop/resume persistence, then asks a sandboxed agent to inspect the +workspace through one shell tool. + +AgentRun (Alibaba Cloud) does not currently expose tunneled ports or +hosted-specific mount strategies, so this runner stays on the non-PTY, +non-mount path. +""" + +from __future__ import annotations + +import argparse +import asyncio +import io +import os +import sys +import tempfile +from pathlib import Path +from typing import cast + +from openai.types.responses import ResponseTextDeltaEvent + +from agents import ModelSettings, Runner +from agents.models.openai_provider import OpenAIProvider +from agents.run import RunConfig +from agents.sandbox import LocalSnapshotSpec, Manifest, SandboxAgent, SandboxRunConfig +from agents.sandbox.session import BaseSandboxSession + +if __package__ is None or __package__ == "": + sys.path.insert(0, str(Path(__file__).resolve().parents[3])) + +from examples.sandbox.misc.example_support import text_manifest +from examples.sandbox.misc.workspace_shell import WorkspaceShellCapability + +try: + from agents.extensions.sandbox import AliyunSandboxClient, AliyunSandboxClientOptions +except Exception as exc: # pragma: no cover - import path depends on optional extras + raise SystemExit( + "Aliyun sandbox examples require the optional repo extra.\n" + "Install it with: uv sync --extra aliyun" + ) from exc + + +DEFAULT_QUESTION = "Summarize this cloud sandbox workspace in 2 sentences." +SNAPSHOT_CHECK_PATH = Path("snapshot-check.txt") +SNAPSHOT_CHECK_CONTENT = "aliyun snapshot round-trip ok\n" +LIVE_RESUME_CHECK_PATH = Path("live-resume-check.txt") +LIVE_RESUME_CHECK_CONTENT = "aliyun live resume ok\n" + + +def _build_manifest() -> Manifest: + return text_manifest( + { + "README.md": ( + "# Aliyun Demo Workspace\n\n" + "This workspace exists to validate the Aliyun AgentRun sandbox backend manually.\n" + ), + "handoff.md": ( + "# Handoff\n\n" + "- Customer: Northwind Traders.\n" + "- Goal: validate Aliyun sandbox exec and persistence flows.\n" + "- Current status: non-PTY backend slice is wired and under test.\n" + ), + "todo.md": ( + "# Todo\n\n" + "1. Inspect the workspace files.\n" + "2. Summarize the current status in two sentences.\n" + ), + } + ) + + +async def _read_text(session: BaseSandboxSession, path: Path) -> str: + data = await session.read(path) + text = cast(str | bytes, data.read()) + if isinstance(text, bytes): + return text.decode("utf-8") + return text + + +def _require_env(name: str) -> None: + if os.environ.get(name): + return + raise SystemExit(f"{name} must be set before running this example.") + + +def _require_aliyun_credentials( + *, + access_key_id: str | None, + access_key_secret: str | None, + account_id: str | None, + api_key: str | None, +) -> None: + del access_key_id, access_key_secret, api_key # resolved by the SDK, not preflighted here. + # The one hard requirement is the account id: AgentRun builds the data-plane + # endpoint from it (unless a data endpoint is set). Access credentials themselves + # are left to `agentrun-sdk`, which resolves them from CLI flags, env vars, an + # Alibaba Cloud config file, or a RAM role — so we do not second-guess them here. + env = os.environ + has_account = bool( + account_id + or env.get("AGENTRUN_ACCOUNT_ID") + or env.get("FC_ACCOUNT_ID") + or env.get("AGENTRUN_DATA_ENDPOINT") + ) + if not has_account: + raise SystemExit( + "AgentRun needs an account id to build the data-plane endpoint. Set " + "AGENTRUN_ACCOUNT_ID (or FC_ACCOUNT_ID / AGENTRUN_DATA_ENDPOINT), or pass " + "--account-id." + ) + + +def _make_client( + *, + access_key_id: str | None, + access_key_secret: str | None, + account_id: str | None, + api_key: str | None, + region: str | None, +) -> AliyunSandboxClient: + return AliyunSandboxClient( + access_key_id=access_key_id, + access_key_secret=access_key_secret, + account_id=account_id, + api_key=api_key, + region=region, + ) + + +def _make_options( + *, + region: str, + template_name: str, + sandbox_idle_timeout_seconds: int, +) -> AliyunSandboxClientOptions: + return AliyunSandboxClientOptions( + region=region, + template_name=template_name, + sandbox_idle_timeout_seconds=sandbox_idle_timeout_seconds, + ) + + +async def _verify_stop_resume( + *, + manifest: Manifest, + client: AliyunSandboxClient, + options: AliyunSandboxClientOptions, +) -> None: + with tempfile.TemporaryDirectory(prefix="aliyun-snapshot-example-") as snapshot_dir: + sandbox = await client.create( + manifest=manifest, + snapshot=LocalSnapshotSpec(base_path=Path(snapshot_dir)), + options=options, + ) + + try: + await sandbox.start() + await sandbox.write( + SNAPSHOT_CHECK_PATH, + io.BytesIO(SNAPSHOT_CHECK_CONTENT.encode("utf-8")), + ) + await sandbox.stop() + finally: + await sandbox.shutdown() + + resumed_sandbox = await client.resume(sandbox.state) + try: + await resumed_sandbox.start() + restored_text = await _read_text(resumed_sandbox, SNAPSHOT_CHECK_PATH) + if restored_text != SNAPSHOT_CHECK_CONTENT: + raise RuntimeError( + "Snapshot resume verification failed: " + f"expected {SNAPSHOT_CHECK_CONTENT!r}, got {restored_text!r}" + ) + finally: + await resumed_sandbox.aclose() + + print("snapshot round-trip ok") + + +async def _verify_resume_running_sandbox( + *, + manifest: Manifest, + client: AliyunSandboxClient, + options: AliyunSandboxClientOptions, +) -> None: + sandbox = await client.create(manifest=manifest, options=options) + + try: + await sandbox.start() + await sandbox.write( + LIVE_RESUME_CHECK_PATH, + io.BytesIO(LIVE_RESUME_CHECK_CONTENT.encode("utf-8")), + ) + serialized = client.serialize_session_state(sandbox.state) + resumed_sandbox = await client.resume(client.deserialize_session_state(serialized)) + try: + restored_text = await _read_text(resumed_sandbox, LIVE_RESUME_CHECK_PATH) + if restored_text != LIVE_RESUME_CHECK_CONTENT: + raise RuntimeError( + "Running sandbox resume verification failed: " + f"expected {LIVE_RESUME_CHECK_CONTENT!r}, got {restored_text!r}" + ) + finally: + await resumed_sandbox.aclose() + finally: + await sandbox.shutdown() + + print("running sandbox resume ok") + + +async def main( + *, + model: str, + question: str, + region: str, + template_name: str, + sandbox_idle_timeout_seconds: int, + access_key_id: str | None, + access_key_secret: str | None, + account_id: str | None, + api_key: str | None, + stream: bool, +) -> None: + _require_env("OPENAI_API_KEY") + _require_aliyun_credentials( + access_key_id=access_key_id, + access_key_secret=access_key_secret, + account_id=account_id, + api_key=api_key, + ) + + manifest = _build_manifest() + client = _make_client( + access_key_id=access_key_id, + access_key_secret=access_key_secret, + account_id=account_id, + api_key=api_key, + region=region, + ) + options = _make_options( + region=region, + template_name=template_name, + sandbox_idle_timeout_seconds=sandbox_idle_timeout_seconds, + ) + + await _verify_stop_resume(manifest=manifest, client=client, options=options) + await _verify_resume_running_sandbox(manifest=manifest, client=client, options=options) + + agent = SandboxAgent( + name="Aliyun Sandbox Assistant", + model=model, + instructions=( + "Answer questions about the sandbox workspace. Inspect the files before answering " + "and keep the response concise. " + "Do not invent files or statuses that are not present in the workspace. Cite the " + "file names you inspected." + ), + default_manifest=manifest, + capabilities=[WorkspaceShellCapability()], + model_settings=ModelSettings(tool_choice="required"), + ) + + sandbox = await client.create(manifest=manifest, options=options) + + run_config = RunConfig( + model_provider=OpenAIProvider(), + sandbox=SandboxRunConfig(session=sandbox), + # Disable tracing because it does not currently work reliably with alternate + # upstreams such as AI Gateway, and provider config already comes from env. + tracing_disabled=True, + workflow_name="Aliyun sandbox example", + ) + + try: + async with sandbox: + if not stream: + result = await Runner.run(agent, question, run_config=run_config) + print(result.final_output) + return + + stream_result = Runner.run_streamed(agent, question, run_config=run_config) + saw_text_delta = False + async for event in stream_result.stream_events(): + if event.type == "raw_response_event" and isinstance( + event.data, ResponseTextDeltaEvent + ): + if not saw_text_delta: + print("assistant> ", end="", flush=True) + saw_text_delta = True + print(event.data.delta, end="", flush=True) + + if saw_text_delta: + print() + finally: + await client.delete(sandbox) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--model", default="gpt-5.5", help="Model name to use.") + parser.add_argument("--question", default=DEFAULT_QUESTION, help="Prompt to send to the agent.") + parser.add_argument( + "--region", + default="cn-hangzhou", + help="AgentRun region id, for example `cn-hangzhou`.", + ) + parser.add_argument( + "--template-name", + default="code-interpreter", + help="AgentRun sandbox template name.", + ) + parser.add_argument( + "--sandbox-idle-timeout-seconds", + type=int, + default=3600, + help="Idle timeout before AgentRun reclaims the remote sandbox.", + ) + parser.add_argument( + "--access-key-id", + default=None, + help="Alibaba Cloud access key id (overrides ALIBABA_CLOUD_ACCESS_KEY_ID).", + ) + parser.add_argument( + "--access-key-secret", + default=None, + help="Alibaba Cloud access key secret (overrides ALIBABA_CLOUD_ACCESS_KEY_SECRET).", + ) + parser.add_argument( + "--account-id", + default=None, + help="Alibaba Cloud account id, if your AgentRun deployment requires it.", + ) + parser.add_argument( + "--api-key", + default=None, + help="AgentRun X-API-Key value, if your deployment uses API-key auth.", + ) + parser.add_argument("--stream", action="store_true", default=False, help="Stream the response.") + args = parser.parse_args() + + asyncio.run( + main( + model=args.model, + question=args.question, + region=args.region, + template_name=args.template_name, + sandbox_idle_timeout_seconds=args.sandbox_idle_timeout_seconds, + access_key_id=args.access_key_id, + access_key_secret=args.access_key_secret, + account_id=args.account_id, + api_key=args.api_key, + stream=args.stream, + ) + ) diff --git a/pyproject.toml b/pyproject.toml index 799391c73d..874030b781 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,7 @@ redis = ["redis>=7"] dapr = ["dapr>=1.16.0", "grpcio>=1.60.0"] mongodb = ["pymongo>=4.14"] docker = ["docker>=6.1"] +aliyun = ["agentrun-sdk==0.0.35"] blaxel = ["blaxel>=0.2.50", "aiohttp>=3.12,<4"] daytona = ["daytona>=0.155.0"] cloudflare = ["aiohttp>=3.12,<4"] @@ -163,6 +164,10 @@ ignore_missing_imports = true module = ["vercel", "vercel.*"] ignore_missing_imports = true +[[tool.mypy.overrides]] +module = ["agentrun", "agentrun.*"] +ignore_missing_imports = true + [tool.coverage.run] source = ["src/agents"] omit = [ diff --git a/src/agents/extensions/sandbox/__init__.py b/src/agents/extensions/sandbox/__init__.py index d7b082ba1f..d197fa9340 100644 --- a/src/agents/extensions/sandbox/__init__.py +++ b/src/agents/extensions/sandbox/__init__.py @@ -109,6 +109,19 @@ except Exception: # pragma: no cover _HAS_VERCEL = False +try: + from .aliyun import ( + DEFAULT_ALIYUN_WORKSPACE_ROOT as DEFAULT_ALIYUN_WORKSPACE_ROOT, + AliyunSandboxClient as AliyunSandboxClient, + AliyunSandboxClientOptions as AliyunSandboxClientOptions, + AliyunSandboxSession as AliyunSandboxSession, + AliyunSandboxSessionState as AliyunSandboxSessionState, + ) + + _HAS_ALIYUN = True +except Exception: # pragma: no cover + _HAS_ALIYUN = False + __all__: list[str] = [] if _HAS_E2B: @@ -207,3 +220,14 @@ "RunloopUserParameters", ] ) + +if _HAS_ALIYUN: + __all__.extend( + [ + "DEFAULT_ALIYUN_WORKSPACE_ROOT", + "AliyunSandboxClient", + "AliyunSandboxClientOptions", + "AliyunSandboxSession", + "AliyunSandboxSessionState", + ] + ) diff --git a/src/agents/extensions/sandbox/aliyun/__init__.py b/src/agents/extensions/sandbox/aliyun/__init__.py new file mode 100644 index 0000000000..33f75e6f9f --- /dev/null +++ b/src/agents/extensions/sandbox/aliyun/__init__.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from .sandbox import ( + DEFAULT_ALIYUN_WORKSPACE_ROOT, + AliyunSandboxClient, + AliyunSandboxClientOptions, + AliyunSandboxSession, + AliyunSandboxSessionState, +) + +__all__ = [ + "AliyunSandboxClient", + "AliyunSandboxClientOptions", + "AliyunSandboxSession", + "AliyunSandboxSessionState", + "DEFAULT_ALIYUN_WORKSPACE_ROOT", +] diff --git a/src/agents/extensions/sandbox/aliyun/sandbox.py b/src/agents/extensions/sandbox/aliyun/sandbox.py new file mode 100644 index 0000000000..13edf26bd9 --- /dev/null +++ b/src/agents/extensions/sandbox/aliyun/sandbox.py @@ -0,0 +1,1137 @@ +""" +Aliyun (AgentRun) sandbox implementation. + +This module provides an Aliyun-backed sandbox client/session implementation backed by +`agentrun-sdk`'s sandbox classes. + +Structure mirrors `agents.extensions.sandbox.vercel.sandbox` so that callers can use the +same lifecycle (`create` / `resume` / `delete` / `SandboxSession` context manager) +regardless of backend. + +The `agentrun-sdk` dependency is optional, so package-level exports should guard imports of +this module. Within this module, AgentRun SDK imports are normal so users with the extra +installed get full type navigation. +""" + +from __future__ import annotations + +import asyncio +import io +import logging +import math +import os +import re +import shlex +import tarfile +import tempfile +import uuid +from pathlib import Path, PurePosixPath +from typing import Any, Literal, cast + +from agentrun.sandbox.client import SandboxClient +from agentrun.sandbox.code_interpreter_sandbox import CodeInterpreterSandbox +from agentrun.sandbox.model import NASConfig, OSSMountConfig, PolarFsConfig +from agentrun.utils.config import Config + +from ....sandbox.errors import ( + ConfigurationError, + ErrorCode, + ExecNonZeroError, + ExecTimeoutError, + ExecTransportError, + ExposedPortUnavailableError, + WorkspaceArchiveReadError, + WorkspaceArchiveWriteError, + WorkspaceReadNotFoundError, + WorkspaceStartError, + WorkspaceWriteTypeError, +) +from ....sandbox.manifest import Manifest +from ....sandbox.session import SandboxSession, SandboxSessionState +from ....sandbox.session.base_sandbox_session import BaseSandboxSession +from ....sandbox.session.dependencies import Dependencies +from ....sandbox.session.manager import Instrumentation +from ....sandbox.session.runtime_helpers import RESOLVE_WORKSPACE_PATH_HELPER, RuntimeHelperScript +from ....sandbox.session.sandbox_client import BaseSandboxClient, BaseSandboxClientOptions +from ....sandbox.snapshot import SnapshotBase, SnapshotSpec, resolve_snapshot +from ....sandbox.types import ExecResult, ExposedPortEndpoint, User +from ....sandbox.util.tar_utils import UnsafeTarMemberError, validate_tarfile +from ....sandbox.workspace_paths import coerce_posix_path, posix_path_as_path, sandbox_path_str + +logger = logging.getLogger(__name__) + + +DEFAULT_ALIYUN_WORKSPACE_ROOT = "/home/user" +DEFAULT_ALIYUN_REGION = "cn-hangzhou" +DEFAULT_ALIYUN_TEMPLATE_NAME = "sandbox-code-interpreter" +DEFAULT_ALIYUN_SANDBOX_TIMEOUT_S = 1800 +DEFAULT_ALIYUN_WAIT_FOR_RUNNING_TIMEOUT_S = 45.0 +# Poll interval while waiting for a freshly created sandbox to report healthy. +ALIYUN_HEALTH_POLL_INTERVAL_S = 1.0 +# Default per-command timeout when a caller does not pass one. Matches the SDK's +# own `cmd_async` default; callers may request more and it is forwarded as-is. +DEFAULT_ALIYUN_EXEC_TIMEOUT_S = 30 +# Transient gateway 502s on /processes/cmd — retry twice with backoff before surfacing. +ALIYUN_GATEWAY_502_RETRIES = 2 +ALIYUN_GATEWAY_502_BACKOFF_BASE_S = 0.5 +# /filesystem/upload responds 413 above this size. +ALIYUN_FILESYSTEM_UPLOAD_MAX_BYTES = 100 * 1024 * 1024 + +# AgentRun's cmd API has no env parameter, so env is inlined as a `KEY=val ...` +# prefix on each command. An unsafe key would be shell injection, so only allow +# POSIX-ish identifier names; values are `shlex.quote`d. +_VALID_ENV_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") + + +def _resolve_manifest_root(manifest: Manifest | None) -> Manifest: + """Default manifest root to AgentRun's `/home/user` when unset.""" + if manifest is None: + return Manifest(root=DEFAULT_ALIYUN_WORKSPACE_ROOT) + if manifest.root == Manifest.model_fields["root"].default: + return manifest.model_copy(update={"root": DEFAULT_ALIYUN_WORKSPACE_ROOT}) + return manifest + + +def _build_config( + *, + access_key_id: str | None, + access_key_secret: str | None, + account_id: str | None, + api_key: str | None, + region: str, +) -> Config: + """Build an agentrun Config from option-provided credentials. + + `agentrun.utils.config.Config` falls back to environment variables and the + Alibaba Cloud credential providers when keyword arguments are `None`, so we + pass values through verbatim. + """ + headers: dict[str, str] = {} + if api_key is not None: + headers["X-API-Key"] = api_key + return Config( + access_key_id=access_key_id, + access_key_secret=access_key_secret, + account_id=account_id, + region_id=region, + headers=headers or None, + ) + + +def _is_not_found_error(exc: BaseException) -> bool: + """Recognize AgentRun's 'file missing' signals across the SDK surface. + + Preferred: the SDK's ``HTTPError`` carries an explicit ``status_code`` (404). + Fallback: substring sniff for cases where the SDK wraps the error differently + or returns the raw gateway message. + """ + status_code = getattr(exc, "status_code", None) + if status_code == 404: + return True + msg = str(exc).lower() + return "not found" in msg or "no such file" in msg or "does not exist" in msg + + +def _is_retryable_gateway(exc: BaseException) -> bool: + """True when AgentRun's data-plane gateway returned HTTP 502 Bad Gateway.""" + retry_codes = (502,) + status_code = getattr(exc, "status_code", None) + if status_code in retry_codes: + return True + msg = str(exc).lower() + return "http 502" in msg or "502: bad gateway" in msg + + +def _health_is_ready(health: Any) -> bool: + """Interpret an AgentRun health payload as ready / not-ready. + + The documented ``check_health`` response is a dict with a ``status`` field + (``"ok"`` when ready); we also accept a few synonyms defensively. A non-dict, + non-``None`` return is treated as ready so an SDK that changes the shape does + not wedge startup. + """ + if isinstance(health, dict): + return str(health.get("status", "") or "").lower() in {"ok", "healthy", "ready", "running"} + return health is not None + + +class AliyunSandboxClientOptions(BaseSandboxClientOptions): + """Client options for the Aliyun (AgentRun) sandbox backend. + + Credentials default to `None`; when unset they fall through to whatever the + underlying `agentrun-sdk` resolves from environment variables / Alibaba Cloud + credential providers (e.g. `ALIBABA_CLOUD_ACCESS_KEY_ID`). + + Optional mount configs (``oss_mount_config`` / ``nas_config`` / ``polar_fs_config``) + are passed straight through to ``SandboxClient.create_sandbox_async``. The caller + is responsible for shaping the per-session bucket path / mount point — this class + does not know about tenancy or workspace layout. + """ + + type: Literal["aliyun"] = "aliyun" + access_key_id: str | None = None + access_key_secret: str | None = None + account_id: str | None = None + api_key: str | None = None + region: str | None = None + template_name: str | None = None + sandbox_idle_timeout_seconds: int | None = DEFAULT_ALIYUN_SANDBOX_TIMEOUT_S + env: dict[str, str] | None = None + exposed_ports: tuple[int, ...] = () + oss_mount_config: OSSMountConfig | None = None + nas_config: NASConfig | None = None + polar_fs_config: PolarFsConfig | None = None + + def __init__( + self, + access_key_id: str | None = None, + access_key_secret: str | None = None, + account_id: str | None = None, + api_key: str | None = None, + region: str | None = None, + template_name: str | None = None, + sandbox_idle_timeout_seconds: int | None = DEFAULT_ALIYUN_SANDBOX_TIMEOUT_S, + env: dict[str, str] | None = None, + exposed_ports: tuple[int, ...] = (), + oss_mount_config: OSSMountConfig | None = None, + nas_config: NASConfig | None = None, + polar_fs_config: PolarFsConfig | None = None, + *, + type: Literal["aliyun"] = "aliyun", + ) -> None: + super().__init__( + type=type, + access_key_id=access_key_id, + access_key_secret=access_key_secret, + account_id=account_id, + api_key=api_key, + region=region, + template_name=template_name, + sandbox_idle_timeout_seconds=sandbox_idle_timeout_seconds, + env=env, + exposed_ports=exposed_ports, + oss_mount_config=oss_mount_config, + nas_config=nas_config, + polar_fs_config=polar_fs_config, + ) + + +class AliyunSandboxSessionState(SandboxSessionState): + """Serializable state for an Aliyun-backed session. + + Credentials are intentionally not persisted here; they are stored on the + :class:`AliyunSandboxClient` and re-injected as an agentrun ``Config`` on the + :class:`AliyunSandboxSession` at create/resume time so that serialized session + state never carries access keys or API tokens. + """ + + type: Literal["aliyun"] = "aliyun" + sandbox_id: str = "" + region: str = DEFAULT_ALIYUN_REGION + template_name: str | None = None + sandbox_idle_timeout_seconds: int | None = None + env: dict[str, str] | None = None + oss_mount_config: OSSMountConfig | None = None + nas_config: NASConfig | None = None + polar_fs_config: PolarFsConfig | None = None + + +class AliyunSandboxSession(BaseSandboxSession): + """SandboxSession implementation backed by an AgentRun sandbox.""" + + state: AliyunSandboxSessionState + _sandbox: Any | None + _client: Any | None + _config: Any | None + _env_prefix: str | None + + def __init__( + self, + *, + state: AliyunSandboxSessionState, + sandbox: Any | None = None, + client: Any | None = None, + config: Any | None = None, + ) -> None: + self.state = state + self._sandbox = sandbox + self._client = client + self._config = config + # Cached `KEY=val ...` shell prefix built from options + manifest env, applied + # to every command (AgentRun's cmd API has no env parameter). Resolved lazily + # on first use; `None` means "not resolved yet". + self._env_prefix = None + + @classmethod + def from_state( + cls, + state: AliyunSandboxSessionState, + *, + sandbox: Any | None = None, + client: Any | None = None, + config: Any | None = None, + ) -> AliyunSandboxSession: + return cls(state=state, sandbox=sandbox, client=client, config=config) + + def supports_pty(self) -> bool: + return False + + def _reject_user_arg(self, *, op: Literal["exec", "read", "write"], user: str | User) -> None: + user_name = user.name if isinstance(user, User) else user + raise ConfigurationError( + message=( + "AliyunSandboxSession does not support sandbox-local users; " + f"`{op}` must be called without `user`" + ), + error_code=ErrorCode.SANDBOX_CONFIG_INVALID, + op=op, + context={"backend": "aliyun", "user": user_name}, + ) + + def _prepare_exec_command( + self, + *command: str | Path, + shell: bool | list[str], + user: str | User | None, + ) -> list[str]: + if user is not None: + self._reject_user_arg(op="exec", user=user) + return super()._prepare_exec_command(*command, shell=shell, user=user) + + async def _validate_path_access(self, path: Path | str, *, for_write: bool = False) -> Path: + return await self._validate_remote_path_access(path, for_write=for_write) + + def _runtime_helpers(self) -> tuple[RuntimeHelperScript, ...]: + return (RESOLVE_WORKSPACE_PATH_HELPER,) + + def _validate_tar_bytes(self, raw: bytes) -> None: + """Wrap the shared tar validator to surface a `ValueError` for legacy callers.""" + try: + with tarfile.open(fileobj=io.BytesIO(raw), mode="r:*") as tar: + # The archive is handed to a remote `tar xf`, so reject symlinks that + # point outside the workspace root — otherwise the restored workspace + # could contain links user commands follow off-root (as other hosted + # backends do before remote extraction). + validate_tarfile(tar, allow_external_symlink_targets=False) + except UnsafeTarMemberError as exc: + raise ValueError(str(exc)) from exc + except (tarfile.TarError, OSError) as exc: + raise ValueError("invalid tar stream") from exc + + # ------------------------------------------------------------------ + # AgentRun lifecycle + # ------------------------------------------------------------------ + async def _prepare_backend_workspace(self) -> None: + root = PurePosixPath(self.state.manifest.root) + try: + sandbox = await self._ensure_sandbox() + # Run mkdir from `/` because `manifest.root` might not exist yet — this + # call is what creates it. If we let `_run_command` fall back to the + # workspace root as cwd, the AgentRun gateway rejects the command before + # mkdir ever runs. + exit_code, stdout, stderr = await self._run_command( + sandbox, "mkdir", ["-p", "--", root.as_posix()], cwd="/" + ) + except WorkspaceStartError: + raise + except Exception as exc: + raise WorkspaceStartError(path=posix_path_as_path(root), cause=exc) from exc + + if exit_code != 0: + raise WorkspaceStartError( + path=posix_path_as_path(root), + context={ + "exit_code": exit_code, + "stdout": stdout, + "stderr": stderr, + }, + ) + + async def _env_command_prefix(self) -> str: + """Build the `KEY=val ...` prefix that injects the configured environment. + + AgentRun's cmd API has no env parameter, so — like other backends inject env + per exec — we prepend the resolved environment to every command. Options env + is merged with the manifest environment (manifest wins on collisions), unsafe + key names are dropped, and values are `shlex.quote`d. The result is cached on + the session since the environment is static for its lifetime. Because env + travels inline with each command, it needs no workspace file, so it is immune + to snapshot restore, `.profile` login-shell semantics, and shell=False execs. + """ + if self._env_prefix is not None: + return self._env_prefix + manifest_env = cast(dict[str, str | None], await self.state.manifest.environment.resolve()) + merged = { + key: value + for key, value in {**(self.state.env or {}), **manifest_env}.items() + if value is not None + } + parts: list[str] = [] + for key, value in merged.items(): + if not _VALID_ENV_NAME.match(key): + logger.warning("[AliyunSandboxSession] skipping env var with unsafe name %r", key) + continue + parts.append(f"{key}={shlex.quote(value)}") + self._env_prefix = (" ".join(parts) + " ") if parts else "" + return self._env_prefix + + async def _ensure_sandbox(self) -> Any: + """Lazily provision the underlying AgentRun sandbox. + + Mirrors `VercelSandboxSession._ensure_sandbox`: if we already have a live + backend handle, reuse it; otherwise create one using the state's template / + idle-timeout settings. + """ + sandbox = self._sandbox + if sandbox is not None: + return sandbox + + if self._config is None: + self._config = _build_config( + access_key_id=None, + access_key_secret=None, + account_id=None, + api_key=None, + region=self.state.region, + ) + if self._client is None: + self._client = SandboxClient(config=self._config) + + template_name = self.state.template_name or DEFAULT_ALIYUN_TEMPLATE_NAME + idle_timeout = ( + self.state.sandbox_idle_timeout_seconds + if self.state.sandbox_idle_timeout_seconds is not None + else DEFAULT_ALIYUN_SANDBOX_TIMEOUT_S + ) + try: + base_sandbox = await self._client.create_sandbox_async( + template_name=template_name, + sandbox_idle_timeout_seconds=idle_timeout, + oss_mount_config=self.state.oss_mount_config, + nas_config=self.state.nas_config, + polar_fs_config=self.state.polar_fs_config, + ) + except Exception as exc: + raise WorkspaceStartError( + path=posix_path_as_path(coerce_posix_path(self.state.manifest.root)), + cause=exc, + ) from exc + + sandbox = CodeInterpreterSandbox.model_validate(base_sandbox.model_dump(by_alias=False)) + sandbox._config = self._config + + self._sandbox = sandbox + self.state.sandbox_id = sandbox.sandbox_id + logger.info( + "AliyunSandboxSession created sandbox: template=%s sandbox_id=%s", + template_name, + sandbox.sandbox_id, + ) + # A freshly created sandbox may still be booting; the SDK's own + # CodeInterpreterSandbox context manager polls health for this reason. + # We bypass that context manager, so wait explicitly before the first + # exec (e.g. the workspace mkdir) can hit a not-yet-ready sandbox. If it + # never becomes healthy, delete the just-created sandbox before surfacing + # the error — otherwise `create()` raises without handing back a session + # and the remote sandbox would leak until its idle timeout. + try: + await self._wait_until_healthy(sandbox) + except Exception: + await self._stop_attached_sandbox() + raise + return sandbox + + async def _wait_until_healthy(self, sandbox: Any) -> None: + """Poll the AgentRun health endpoint until the sandbox is ready. + + Mirrors the SDK's ``CodeInterpreterSandbox.__enter__``: check roughly once + per second until the sandbox reports healthy, up to + ``DEFAULT_ALIYUN_WAIT_FOR_RUNNING_TIMEOUT_S``. Raises ``WorkspaceStartError`` + on timeout so callers get a clean start failure instead of an opaque error + from the first exec against a still-booting sandbox. + """ + max_attempts = max(1, int(DEFAULT_ALIYUN_WAIT_FOR_RUNNING_TIMEOUT_S)) + last_error: BaseException | None = None + for attempt in range(1, max_attempts + 1): + try: + health = await sandbox.check_health_async() + except Exception as exc: # noqa: BLE001 — booting sandbox may reject probes. + last_error = exc + else: + if _health_is_ready(health): + return + last_error = None + if attempt < max_attempts: + await asyncio.sleep(ALIYUN_HEALTH_POLL_INTERVAL_S) + raise WorkspaceStartError( + path=posix_path_as_path(coerce_posix_path(self.state.manifest.root)), + context={ + "backend": "aliyun", + "reason": "sandbox_not_healthy", + "sandbox_id": self.state.sandbox_id, + }, + cause=last_error, + ) + + async def _run_command( + self, + sandbox: Any, + command: str, + args: list[str], + *, + cwd: str | None = None, + timeout_s: float | None = None, + ) -> tuple[int, str, str]: + """Run a single command in the sandbox; return (exit_code, stdout, stderr). + + Thin wrapper so the rest of the session class doesn't need to know how the + AgentRun SDK shapes its command responses. The configured environment is + inlined as a `KEY=val ...` prefix; that prefix is never logged so secret + values stay out of the logs. + """ + argv = [command, *args] + shell_cmd = shlex.join(argv) + effective_cwd = cwd or self.state.manifest.root + # Forward the caller's timeout as-is (defaulting to the SDK default) rather + # than silently shortening it: the outer `wait_for` in `_exec_internal` uses + # the same value, so a longer command a caller allowed time for isn't killed + # early. If a specific AgentRun gateway enforces a shorter ceiling, that + # surfaces as a provider error instead of a silent cap. + if timeout_s is None: + timeout_int = DEFAULT_ALIYUN_EXEC_TIMEOUT_S + else: + # Round up: the SDK wants an int, and truncating a budget like 1.9 -> 1 + # would let the provider kill the command before the outer wait_for. + timeout_int = max(1, math.ceil(timeout_s)) + + # Log the bare command only — the env prefix may contain secret values. + logger.info( + "AliyunSandboxSession._run_command sandbox_id=%s cwd=%s timeout=%ss cmd=%r", + self.state.sandbox_id, + effective_cwd, + timeout_int, + shell_cmd[:500], + ) + # The resolve-workspace-path helper is on the critical sandbox-startup path, + # and its caller treats exit0+empty-stdout as a fatal ExecTransportError. + # AgentRun occasionally drops stdout on an otherwise-successful exec, so retry + # that specific case within the same attempt budget. Gateway 502s are retried + # with exponential backoff before surfacing as ExecTransportError. + is_resolve_helper = "resolve-workspace-path" in command + max_attempts = 1 + ALIYUN_GATEWAY_502_RETRIES + + # Inline the configured environment as a shell prefix (AgentRun's cmd API has + # no env parameter). Applies to every command — shell=True and shell=False — + # with no workspace file, so it survives snapshot restore and needs no login + # shell. Empty when no env is configured. + command_to_run = f"{await self._env_command_prefix()}{shell_cmd}" + + exit_code, stdout, stderr = 0, "", "" + for attempt in range(1, max_attempts + 1): + try: + result = await sandbox.process.cmd_async( + command=command_to_run, + cwd=effective_cwd, + timeout=timeout_int, + ) + except Exception as exc: + if _is_retryable_gateway(exc) and attempt < max_attempts: + backoff_s = ALIYUN_GATEWAY_502_BACKOFF_BASE_S * (2 ** (attempt - 1)) + logger.warning( + "AliyunSandboxSession._run_command HTTP 502 " + "(attempt %d/%d); retrying in %.1fs sandbox_id=%s: %s", + attempt, + max_attempts, + backoff_s, + self.state.sandbox_id, + exc, + ) + await asyncio.sleep(backoff_s) + continue + raise + logger.info( + "AliyunSandboxSession._run_command sandbox_id=%s returned (cwd=%s)", + self.state.sandbox_id, + effective_cwd, + ) + + if isinstance(result, dict): + inner = result.get("result", result) + stdout = str(inner.get("stdout", "") or "") + stderr = str(inner.get("stderr", "") or "") + # Doc-mandated field name is `exitCode`; keep `exit_code` as a defensive + # fallback in case the SDK or older gateway versions normalize it. + exit_code = int(inner.get("exitCode", inner.get("exit_code", 0)) or 0) + else: + stdout = str(result or "") + stderr = "" + exit_code = 0 + + if not (is_resolve_helper and exit_code == 0 and not stdout.strip()): + break + if attempt < max_attempts: + logger.warning( + "AliyunSandboxSession._run_command resolve-workspace-path returned " + "exit0 with empty stdout (attempt %d/%d); retrying sandbox_id=%s", + attempt, + max_attempts, + self.state.sandbox_id, + ) + await asyncio.sleep(0.2 * attempt) + + return exit_code, stdout, stderr + + async def _close_sandbox_client(self) -> None: + # The agentrun-sdk's SandboxClient does not expose an explicit aclose hook — + # its HTTP client is short-lived per request. Nothing to do. + return + + async def running(self) -> bool: + sandbox = self._sandbox + if sandbox is None: + return False + try: + # AgentRun exposes a cheap GET /sandboxes/{id}/health probe; prefer it + # over a no-op shell command, which would consume a /processes/cmd slot + # and pay the data-plane gateway round-trip. + health = await sandbox.check_health_async() + except Exception: # noqa: BLE001 — treat any probe failure as not-running. + return False + return _health_is_ready(health) + + async def shutdown(self) -> None: + await self._stop_attached_sandbox() + + async def _stop_attached_sandbox(self) -> None: + sandbox = self._sandbox + client = self._client + sandbox_id = self.state.sandbox_id or (sandbox.sandbox_id if sandbox is not None else None) + if sandbox is None: + return + try: + if client is not None and sandbox_id: + await client.delete_sandbox_async(sandbox_id=sandbox_id) + logger.info("AliyunSandboxSession deleted sandbox: %s", sandbox_id) + elif hasattr(sandbox, "delete_async"): + await sandbox.delete_async() + logger.info("AliyunSandboxSession deleted sandbox via handle: %s", sandbox_id) + except Exception: # noqa: BLE001 — teardown is best-effort. + logger.exception("AliyunSandboxSession failed to delete sandbox %s", sandbox_id) + finally: + await self._close_sandbox_client() + self._sandbox = None + self._client = None + + # ------------------------------------------------------------------ + # exec + # ------------------------------------------------------------------ + async def _exec_internal( + self, + *command: str | Path, + timeout: float | None = None, + ) -> ExecResult: + sandbox = await self._ensure_sandbox() + normalized = [str(part) for part in command] + if not normalized: + return ExecResult(stdout=b"", stderr=b"", exit_code=0) + + try: + exit_code, stdout, stderr = await asyncio.wait_for( + self._run_command( + sandbox, + normalized[0], + normalized[1:], + cwd=self.state.manifest.root, + timeout_s=timeout, + ), + timeout=timeout, + ) + return ExecResult( + stdout=stdout.encode("utf-8"), + stderr=stderr.encode("utf-8"), + exit_code=exit_code, + ) + except TimeoutError as exc: + raise ExecTimeoutError(command=normalized, timeout_s=timeout, cause=exc) from exc + except ExecTimeoutError: + raise + except Exception as exc: + logger.exception( + "AliyunSandboxSession _exec_internal failed sandbox_id=%s cmd=%s", + self.state.sandbox_id, + normalized, + ) + raise ExecTransportError( + command=normalized, + context={"backend": "aliyun", "sandbox_id": self.state.sandbox_id}, + cause=exc, + ) from exc + + async def _resolve_exposed_port(self, port: int) -> ExposedPortEndpoint: + # AgentRun does not currently expose tunneled ports. + raise ExposedPortUnavailableError( + port=port, + exposed_ports=self.state.exposed_ports, + reason="backend_unavailable", + context={"backend": "aliyun", "sandbox_id": self.state.sandbox_id}, + ) + + # ------------------------------------------------------------------ + # File IO + # ------------------------------------------------------------------ + async def read(self, path: Path, *, user: str | User | None = None) -> io.IOBase: + if user is not None: + self._reject_user_arg(op="read", user=user) + + normalized_path = await self._validate_path_access(path) + sandbox = await self._ensure_sandbox() + try: + payload = await self._sandbox_read_file(sandbox, normalized_path) + except Exception as exc: + raise WorkspaceArchiveReadError(path=normalized_path, cause=exc) from exc + if payload is None: + raise WorkspaceReadNotFoundError(path=normalized_path) + return io.BytesIO(payload) + + async def write( + self, + path: Path, + data: io.IOBase, + *, + user: str | User | None = None, + ) -> None: + if user is not None: + self._reject_user_arg(op="write", user=user) + + normalized_path = await self._validate_path_access(path, for_write=True) + payload = data.read() + if isinstance(payload, str): + payload = payload.encode("utf-8") + if not isinstance(payload, bytes | bytearray): + raise WorkspaceWriteTypeError( + path=normalized_path, + actual_type=type(payload).__name__, + ) + try: + await self._sandbox_write_file( + await self._ensure_sandbox(), + normalized_path, + bytes(payload), + ) + except Exception as exc: + raise WorkspaceArchiveWriteError(path=normalized_path, cause=exc) from exc + + async def append( + self, + path: Path, + data: io.IOBase, + *, + user: str | User | None = None, + ) -> None: + """Append bytes to a file in the workspace, creating it if absent. + + Unlike :meth:`write` — which uploads a fresh file and therefore replaces + whatever was there — ``append`` preserves the existing contents. It reads + the current bytes (empty when the file does not exist yet), concatenates + the new payload, and uploads the combined result. This lets a file such + as ``outputs/evidence-map.csv`` accumulate rows across turns/conversations + that each start with a fresh in-memory context but share the same + OSS-backed workspace. + + This is a read-modify-write, not an atomic O_APPEND: concurrent appends + to the same path can lose data. The per-thread sandbox pool serializes a + thread's turns, so that is safe here; callers fanning out parallel + appends to one path must coordinate externally. + """ + if user is not None: + self._reject_user_arg(op="write", user=user) + + normalized_path = await self._validate_path_access(path, for_write=True) + payload = data.read() + if isinstance(payload, str): + payload = payload.encode("utf-8") + if not isinstance(payload, bytes | bytearray): + raise WorkspaceWriteTypeError( + path=normalized_path, + actual_type=type(payload).__name__, + ) + + sandbox = await self._ensure_sandbox() + try: + existing = await self._sandbox_read_file(sandbox, normalized_path) + except Exception as exc: + raise WorkspaceArchiveReadError(path=normalized_path, cause=exc) from exc + + combined = (existing or b"") + bytes(payload) + try: + await self._sandbox_write_file(sandbox, normalized_path, combined) + except Exception as exc: + raise WorkspaceArchiveWriteError(path=normalized_path, cause=exc) from exc + + async def _sandbox_read_file(self, sandbox: Any, path: Path) -> bytes | None: + """Read a file off the sandbox via ``file_system.download_async``. + + The SDK's ``file.read`` API returns text and is lossy for binary payloads; + ``file_system.download`` writes the raw bytes to a local path, so we round + through a temp file to give callers an authoritative byte stream. + """ + remote = sandbox_path_str(path) + tmp_fd, tmp_path = tempfile.mkstemp(prefix="aliyun-read-") + os.close(tmp_fd) + try: + try: + await sandbox.file_system.download_async(path=remote, save_path=tmp_path) + except FileNotFoundError: + return None + except Exception as exc: + if _is_not_found_error(exc): + return None + raise + with open(tmp_path, "rb") as fh: + return fh.read() + finally: + try: + os.unlink(tmp_path) + except OSError: + pass + + async def _sandbox_write_file(self, sandbox: Any, path: Path, data: bytes) -> None: + """Write bytes into the sandbox via ``file_system.upload_async``. + + The text-based ``file.write`` API truncates / mishandles non-utf8 payloads, + so we always go through ``file_system.upload`` with a local temp file. + """ + if len(data) > ALIYUN_FILESYSTEM_UPLOAD_MAX_BYTES: + # /filesystem/upload returns 413 above 100MB; fail fast with a clear + # message instead of letting the gateway error bubble up opaquely. + raise WorkspaceArchiveWriteError( + path=path, + context={ + "backend": "aliyun", + "reason": "upload_exceeds_100mb", + "size_bytes": len(data), + "limit_bytes": ALIYUN_FILESYSTEM_UPLOAD_MAX_BYTES, + }, + ) + + remote = sandbox_path_str(path) + tmp_fd, tmp_path = tempfile.mkstemp(prefix="aliyun-write-") + try: + with os.fdopen(tmp_fd, "wb") as fh: + fh.write(data) + await sandbox.file_system.upload_async( + local_file_path=tmp_path, + target_file_path=remote, + ) + finally: + try: + os.unlink(tmp_path) + except OSError: + pass + + # ------------------------------------------------------------------ + # Workspace persistence (tar-based; uses SDK's binary download/upload) + # ------------------------------------------------------------------ + async def persist_workspace(self) -> io.IOBase: + root = self._workspace_root_path() + sandbox = await self._ensure_sandbox() + archive_path = posix_path_as_path( + coerce_posix_path(f"/tmp/openai-agents-aliyun-{self.state.session_id.hex}.tar") + ) + excludes = [ + f"--exclude=./{rel_path.as_posix()}" + for rel_path in sorted( + self._persist_workspace_skip_relpaths(), + key=lambda item: item.as_posix(), + ) + ] + tar_command = ("tar", "cf", archive_path.as_posix(), *excludes, ".") + try: + result = await self.exec(*tar_command, shell=False) + if not result.ok(): + raise WorkspaceArchiveReadError( + path=root, + cause=ExecNonZeroError( + result, + command=tar_command, + context={"backend": "aliyun", "sandbox_id": self.state.sandbox_id}, + ), + ) + archive = await self._sandbox_read_file(sandbox, archive_path) + if archive is None: + raise WorkspaceReadNotFoundError(path=archive_path) + return io.BytesIO(archive) + except WorkspaceReadNotFoundError: + raise + except WorkspaceArchiveReadError: + raise + except Exception as exc: + raise WorkspaceArchiveReadError(path=root, cause=exc) from exc + finally: + try: + await self._run_command( + sandbox, + "rm", + [archive_path.as_posix()], + cwd=self.state.manifest.root, + ) + except Exception: # noqa: BLE001 — cleanup is best-effort. + pass + + async def hydrate_workspace(self, data: io.IOBase) -> None: + raw = data.read() + if isinstance(raw, str): + raw = raw.encode("utf-8") + if not isinstance(raw, bytes | bytearray): + raise WorkspaceWriteTypeError( + path=self._workspace_root_path(), + actual_type=type(raw).__name__, + ) + + raw_bytes = bytes(raw) + root = self._workspace_root_path() + sandbox = await self._ensure_sandbox() + archive_path = posix_path_as_path( + coerce_posix_path(f"/tmp/openai-agents-aliyun-{self.state.session_id.hex}.tar") + ) + tar_command = ("tar", "xf", archive_path.as_posix(), "-C", root.as_posix()) + try: + self._validate_tar_bytes(raw_bytes) + await self.mkdir(root, parents=True) + await self._sandbox_write_file(sandbox, archive_path, raw_bytes) + result = await self.exec(*tar_command, shell=False) + if not result.ok(): + raise WorkspaceArchiveWriteError( + path=root, + cause=ExecNonZeroError( + result, + command=tar_command, + context={"backend": "aliyun", "sandbox_id": self.state.sandbox_id}, + ), + ) + except WorkspaceArchiveWriteError: + raise + except Exception as exc: + raise WorkspaceArchiveWriteError(path=root, cause=exc) from exc + finally: + try: + await self._run_command( + sandbox, + "rm", + [archive_path.as_posix()], + cwd=self.state.manifest.root, + ) + except Exception: # noqa: BLE001 — cleanup is best-effort. + pass + + +class AliyunSandboxSessionWrapper(SandboxSession): + """SDK wrapper that also exposes Aliyun-specific workspace helpers.""" + + async def append( + self, + path: Path, + data: io.IOBase, + *, + user: str | User | None = None, + ) -> None: + await self._inner.append(path, data, user=user) # type: ignore[attr-defined] + + +class AliyunSandboxClient(BaseSandboxClient[AliyunSandboxClientOptions]): + """Aliyun-backed sandbox client. Wraps `agentrun.sandbox.client.SandboxClient`.""" + + backend_id = "aliyun" + _instrumentation: Instrumentation + _access_key_id: str | None + _access_key_secret: str | None + _account_id: str | None + _api_key: str | None + _region: str | None + + def __init__( + self, + *, + access_key_id: str | None = None, + access_key_secret: str | None = None, + account_id: str | None = None, + api_key: str | None = None, + region: str | None = None, + instrumentation: Instrumentation | None = None, + dependencies: Dependencies | None = None, + ) -> None: + super().__init__() + self._access_key_id = access_key_id + self._access_key_secret = access_key_secret + self._account_id = account_id + self._api_key = api_key + self._region = region + self._instrumentation = instrumentation or Instrumentation() + self._dependencies = dependencies + + def _resolve_credential( + self, + from_options: str | None, + from_client: str | None, + ) -> str | None: + return from_options if from_options is not None else from_client + + def _wrap_session( + self, + inner: BaseSandboxSession, + *, + instrumentation: Instrumentation | None = None, + ) -> SandboxSession: + return AliyunSandboxSessionWrapper( + inner, + instrumentation=instrumentation, + dependencies=self._resolve_dependencies(), + ) + + async def create( + self, + *, + snapshot: SnapshotSpec | SnapshotBase | None = None, + manifest: Manifest | None = None, + options: AliyunSandboxClientOptions, + ) -> SandboxSession: + resolved_manifest = _resolve_manifest_root(manifest) + region = self._resolve_credential(options.region, self._region) or DEFAULT_ALIYUN_REGION + session_id = uuid.uuid4() + snapshot_instance = resolve_snapshot(snapshot, str(session_id)) + state = AliyunSandboxSessionState( + session_id=session_id, + manifest=resolved_manifest, + snapshot=snapshot_instance, + sandbox_id="", + region=region, + template_name=options.template_name, + sandbox_idle_timeout_seconds=options.sandbox_idle_timeout_seconds, + env=dict(options.env or {}) or None, + exposed_ports=options.exposed_ports, + oss_mount_config=options.oss_mount_config, + nas_config=options.nas_config, + polar_fs_config=options.polar_fs_config, + ) + # Resolve option-over-client credentials for THIS session's Config only. We + # deliberately do not cache them back onto the client: the client may be + # reused across tenants/accounts via per-call options, and mutating shared + # state would let a later resume() rebuild Config under the wrong account. + # resume() therefore relies on client-level credentials (or the SDK's env / + # credential providers), since serialized state never carries secrets. + config = _build_config( + access_key_id=self._resolve_credential(options.access_key_id, self._access_key_id), + access_key_secret=self._resolve_credential( + options.access_key_secret, self._access_key_secret + ), + account_id=self._resolve_credential(options.account_id, self._account_id), + api_key=self._resolve_credential(options.api_key, self._api_key), + region=region, + ) + inner = AliyunSandboxSession.from_state(state, config=config) + # Eagerly bring up the underlying remote sandbox so callers get an + # error here instead of on the first exec call. + await inner._ensure_sandbox() + return self._wrap_session(inner, instrumentation=self._instrumentation) + + async def delete(self, session: SandboxSession) -> SandboxSession: + inner = session._inner + if not isinstance(inner, AliyunSandboxSession): + raise TypeError("AliyunSandboxClient.delete expects an AliyunSandboxSession") + try: + await inner.shutdown() + except Exception: # noqa: BLE001 — delete is best-effort teardown. + logger.exception("[AliyunSandboxClient.delete] shutdown failed") + return session + + async def resume(self, state: SandboxSessionState) -> SandboxSession: + if not isinstance(state, AliyunSandboxSessionState): + raise TypeError("AliyunSandboxClient.resume expects an AliyunSandboxSessionState") + + config = _build_config( + access_key_id=self._access_key_id, + access_key_secret=self._access_key_secret, + account_id=self._account_id, + api_key=self._api_key, + region=state.region, + ) + sandbox: Any | None = None + client: Any | None = None + reconnected = False + if state.sandbox_id: + try: + sandbox, client = await self._reattach_sandbox(state.sandbox_id, config) + reconnected = sandbox is not None + except Exception: # noqa: BLE001 — fall back to a fresh sandbox on any error. + sandbox = None + client = None + + inner = AliyunSandboxSession.from_state( + state, sandbox=sandbox, client=client, config=config + ) + if sandbox is None: + # AgentRun sandboxes are not always re-addressable by id once the + # client process has exited, so resume provisions a fresh sandbox and + # relies on the snapshot/manifest pipeline to repopulate the workspace. + state.workspace_root_ready = False + await inner._ensure_sandbox() + inner._set_start_state_preserved(reconnected) + return self._wrap_session(inner, instrumentation=self._instrumentation) + + async def _reattach_sandbox( + self, sandbox_id: str, config: Config + ) -> tuple[Any | None, Any | None]: + """Try to GET an existing AgentRun sandbox by id. + + Returns ``(sandbox, client)`` on success and ``(None, None)`` when the + sandbox is gone (404), unhealthy, or the underlying SDK call fails. Errors + are swallowed so ``resume`` can fall back to creating a fresh sandbox and + rehydrating from the snapshot. + """ + client = SandboxClient(config=config) + try: + base_sandbox = await client.get_sandbox_async(sandbox_id=sandbox_id) + except Exception as exc: + if _is_not_found_error(exc): + return None, None + raise + + sandbox = CodeInterpreterSandbox.model_validate(base_sandbox.model_dump(by_alias=False)) + sandbox._config = config + # `get_sandbox_async` can return a record for an expired/stopped instance + # instead of a 404. Reusing it would make the next `start()` fail on the + # workspace probe/mkdir; verify it is actually healthy, else fall back to a + # fresh sandbox so the snapshot restore path runs. + try: + healthy = _health_is_ready(await sandbox.check_health_async()) + except Exception: # noqa: BLE001 — an unreachable sandbox is not reusable. + healthy = False + if not healthy: + logger.info( + "AliyunSandboxClient reattach found unhealthy sandbox %s; provisioning fresh", + sandbox_id, + ) + return None, None + logger.info("AliyunSandboxClient reattached sandbox: sandbox_id=%s", sandbox_id) + return sandbox, client + + def serialize_session_state(self, state: SandboxSessionState) -> dict[str, object]: + # The agentrun-sdk's mount config models (OSSMountConfig / NASConfig / + # PolarFsConfig) serialize to camelCase (`mountPoints`) but only validate + # from snake_case (`mount_points`) because `validate_by_alias=False`. Dump + # the state with `by_alias=False` so the persisted JSON round-trips through + # `deserialize_session_state` cleanly. The SDK still emits the camelCase + # wire form when it actually sends to the data plane. + return state.model_dump(mode="json", by_alias=False) + + def deserialize_session_state(self, payload: dict[str, object]) -> SandboxSessionState: + return AliyunSandboxSessionState.model_validate(payload) + + +__all__ = [ + "AliyunSandboxClient", + "AliyunSandboxClientOptions", + "AliyunSandboxSession", + "AliyunSandboxSessionState", + "DEFAULT_ALIYUN_WORKSPACE_ROOT", +] diff --git a/tests/extensions/sandbox/test_aliyun.py b/tests/extensions/sandbox/test_aliyun.py new file mode 100644 index 0000000000..7787ac2704 --- /dev/null +++ b/tests/extensions/sandbox/test_aliyun.py @@ -0,0 +1,1407 @@ +"""Unit tests for the Aliyun AgentRun sandbox backend. + +The `agentrun-sdk` is mocked via `sys.modules` injection so these tests do not +need the real package installed and never touch Alibaba Cloud. +""" + +from __future__ import annotations + +import asyncio +import importlib +import io +import logging +import sys +import tarfile +import types +import uuid +from pathlib import Path +from typing import Any, Literal, cast +from unittest.mock import MagicMock + +import pytest +from pydantic import BaseModel + +from agents.sandbox.errors import ( + ConfigurationError, + ExecTimeoutError, + ExecTransportError, + ExposedPortUnavailableError, + WorkspaceArchiveReadError, + WorkspaceArchiveWriteError, + WorkspaceReadNotFoundError, + WorkspaceStartError, + WorkspaceWriteTypeError, +) +from agents.sandbox.manifest import Environment, Manifest +from agents.sandbox.session.sandbox_session_state import SandboxSessionState +from agents.sandbox.snapshot import NoopSnapshot +from agents.sandbox.types import User + +# --------------------------------------------------------------------------- # +# Fake agentrun-sdk # +# --------------------------------------------------------------------------- # + + +class _FakeHTTPError(Exception): + """Stand-in for the SDK's HTTP error carrying a `status_code`.""" + + def __init__(self, status_code: int, message: str | None = None) -> None: + super().__init__(message or f"HTTP {status_code}") + self.status_code = status_code + + +class _FakeProcess: + """Stand-in for `sandbox.process`. Records calls + returns scripted results.""" + + def __init__(self) -> None: + self.calls: list[dict[str, Any]] = [] + self.result: dict[str, Any] = { + "stdout": "", + "stderr": "", + "exitCode": 0, + } + self.exception: BaseException | None = None + # A queue of per-call outcomes: an exception is raised, `None` falls through + # to `result`. Used to script gateway-502 retry behavior. + self.exceptions: list[BaseException | None] = [] + self.side_effect: Any = None + + async def cmd_async(self, *, command: str, cwd: str | None = None, timeout: int = 30) -> Any: + self.calls.append({"command": command, "cwd": cwd, "timeout": timeout}) + if self.exceptions: + exc = self.exceptions.pop(0) + if exc is not None: + raise exc + if self.side_effect is not None: + return self.side_effect(command=command, cwd=cwd, timeout=timeout) + if self.exception is not None: + raise self.exception + return self.result + + +class _FakeFileSystem: + """Stand-in for `sandbox.file_system`. Tracks uploads/downloads in-memory.""" + + def __init__(self) -> None: + self.files: dict[str, bytes] = {} + self.upload_calls: list[tuple[str, str]] = [] + self.download_calls: list[tuple[str, str]] = [] + self.upload_exception: BaseException | None = None + self.download_exception: BaseException | None = None + + async def upload_async(self, *, local_file_path: str, target_file_path: str) -> None: + self.upload_calls.append((local_file_path, target_file_path)) + if self.upload_exception is not None: + raise self.upload_exception + with open(local_file_path, "rb") as fh: + self.files[target_file_path] = fh.read() + + async def download_async(self, *, path: str, save_path: str) -> None: + self.download_calls.append((path, save_path)) + if self.download_exception is not None: + raise self.download_exception + if path not in self.files: + raise FileNotFoundError(path) + with open(save_path, "wb") as fh: + fh.write(self.files[path]) + + +class _FakeCodeInterpreterSandbox: + """Stand-in for `agentrun.sandbox.code_interpreter_sandbox.CodeInterpreterSandbox`.""" + + def __init__(self, *, sandbox_id: str = "fake-sandbox-id") -> None: + self.sandbox_id = sandbox_id + self.process = _FakeProcess() + self.file_system = _FakeFileSystem() + # `running()` reads this via `check_health_async`; may also be an exception. + self.health: Any = {"status": "ok"} + self.delete_async_calls = 0 + self._config: Any = None + + def model_dump(self, by_alias: bool = False) -> dict[str, Any]: + # Carry the live object so `model_validate` can hand back the same instance, + # preserving the in-memory process / file_system the test configured. + return {"sandbox_id": self.sandbox_id, "__self__": self} + + @classmethod + def model_validate(cls, data: dict[str, Any]) -> _FakeCodeInterpreterSandbox: + return cast(_FakeCodeInterpreterSandbox, data["__self__"]) + + async def check_health_async(self) -> Any: + health = self.health + if isinstance(health, list): + # A scripted sequence of health payloads; falls back to ready once drained. + health = health.pop(0) if health else {"status": "ok"} + if isinstance(health, BaseException): + raise health + return health + + async def delete_async(self) -> None: + self.delete_async_calls += 1 + + +class _FakeSandboxClient: + """Stand-in for `agentrun.sandbox.client.SandboxClient`.""" + + create_calls: list[dict[str, Any]] = [] + delete_calls: list[str] = [] + get_calls: list[str] = [] + create_failures: list[BaseException] = [] + next_sandbox: _FakeCodeInterpreterSandbox | None = None + get_result: Any = None + + def __init__(self, *, config: Any) -> None: + self.config = config + + @classmethod + def reset(cls) -> None: + cls.create_calls = [] + cls.delete_calls = [] + cls.get_calls = [] + cls.create_failures = [] + cls.next_sandbox = None + cls.get_result = None + + async def create_sandbox_async( + self, + *, + template_name: str, + sandbox_idle_timeout_seconds: int | None = None, + oss_mount_config: Any | None = None, + nas_config: Any | None = None, + polar_fs_config: Any | None = None, + ) -> _FakeCodeInterpreterSandbox: + type(self).create_calls.append( + { + "template_name": template_name, + "sandbox_idle_timeout_seconds": sandbox_idle_timeout_seconds, + "oss_mount_config": oss_mount_config, + "nas_config": nas_config, + "polar_fs_config": polar_fs_config, + } + ) + if type(self).create_failures: + raise type(self).create_failures.pop(0) + return type(self).next_sandbox or _FakeCodeInterpreterSandbox() + + async def delete_sandbox_async(self, *, sandbox_id: str) -> None: + type(self).delete_calls.append(sandbox_id) + + async def get_sandbox_async(self, *, sandbox_id: str) -> Any: + type(self).get_calls.append(sandbox_id) + result = type(self).get_result + if isinstance(result, BaseException): + raise result + return result + + +class _FakeConfig: + """Stand-in for `agentrun.utils.config.Config`.""" + + def __init__(self, **kwargs: Any) -> None: + self.kwargs = kwargs + + +class _FakeOSSMountConfig(BaseModel): + pass + + +class _FakeNASConfig(BaseModel): + pass + + +class _FakePolarFsConfig(BaseModel): + pass + + +def _load_aliyun_module(monkeypatch: pytest.MonkeyPatch) -> Any: + """Inject a fake `agentrun` package and reload the wrapper module.""" + _FakeSandboxClient.reset() + + fake_agentrun = types.ModuleType("agentrun") + fake_sandbox_pkg = cast(Any, types.ModuleType("agentrun.sandbox")) + fake_client_mod = cast(Any, types.ModuleType("agentrun.sandbox.client")) + fake_client_mod.SandboxClient = _FakeSandboxClient + fake_cis_mod = cast(Any, types.ModuleType("agentrun.sandbox.code_interpreter_sandbox")) + fake_cis_mod.CodeInterpreterSandbox = _FakeCodeInterpreterSandbox + fake_model_mod = cast(Any, types.ModuleType("agentrun.sandbox.model")) + fake_model_mod.OSSMountConfig = _FakeOSSMountConfig + fake_model_mod.NASConfig = _FakeNASConfig + fake_model_mod.PolarFsConfig = _FakePolarFsConfig + fake_utils_pkg = cast(Any, types.ModuleType("agentrun.utils")) + fake_config_mod = cast(Any, types.ModuleType("agentrun.utils.config")) + fake_config_mod.Config = _FakeConfig + + monkeypatch.setitem(sys.modules, "agentrun", fake_agentrun) + monkeypatch.setitem(sys.modules, "agentrun.sandbox", fake_sandbox_pkg) + monkeypatch.setitem(sys.modules, "agentrun.sandbox.client", fake_client_mod) + monkeypatch.setitem(sys.modules, "agentrun.sandbox.code_interpreter_sandbox", fake_cis_mod) + monkeypatch.setitem(sys.modules, "agentrun.sandbox.model", fake_model_mod) + monkeypatch.setitem(sys.modules, "agentrun.utils", fake_utils_pkg) + monkeypatch.setitem(sys.modules, "agentrun.utils.config", fake_config_mod) + + sys.modules.pop("agents.extensions.sandbox.aliyun.sandbox", None) + sys.modules.pop("agents.extensions.sandbox.aliyun", None) + + return importlib.import_module("agents.extensions.sandbox.aliyun.sandbox") + + +# --------------------------------------------------------------------------- # +# Helpers # +# --------------------------------------------------------------------------- # + + +def _make_state(aliyun_sandbox: Any, **overrides: Any) -> Any: + base: dict[str, Any] = { + "session_id": uuid.uuid4(), + "manifest": Manifest(root=aliyun_sandbox.DEFAULT_ALIYUN_WORKSPACE_ROOT), + "snapshot": NoopSnapshot(id="test"), + "sandbox_id": "test-sandbox", + } + base.update(overrides) + return aliyun_sandbox.AliyunSandboxSessionState(**base) + + +def _make_session( + aliyun_sandbox: Any, + *, + state: Any | None = None, + sandbox: _FakeCodeInterpreterSandbox | None = None, + client: _FakeSandboxClient | None = None, + config: Any | None = None, + bypass_validate: bool = True, +) -> Any: + """Build an `AliyunSandboxSession` wired up with fake AgentRun primitives.""" + state = state or _make_state(aliyun_sandbox) + sandbox = sandbox if sandbox is not None else _FakeCodeInterpreterSandbox() + session = aliyun_sandbox.AliyunSandboxSession.from_state( + state, + sandbox=sandbox, + client=client, + config=config, + ) + if bypass_validate: + + async def _identity(path: Any, *, for_write: bool = False) -> Path: + return path if isinstance(path, Path) else Path(path) + + session._validate_path_access = _identity + return session + + +def _make_tar(members: dict[str, bytes]) -> bytes: + buf = io.BytesIO() + with tarfile.open(fileobj=buf, mode="w") as tar: + for name, content in members.items(): + info = tarfile.TarInfo(name=name) + info.size = len(content) + tar.addfile(info, io.BytesIO(content)) + return buf.getvalue() + + +def _make_tar_with_symlink(name: str, target: str) -> bytes: + buf = io.BytesIO() + with tarfile.open(fileobj=buf, mode="w") as tar: + info = tarfile.TarInfo(name=name) + info.type = tarfile.SYMTYPE + info.linkname = target + tar.addfile(info) + return buf.getvalue() + + +def _env_prefix(command: str) -> str: + """Return the leading `KEY=val ...` env prefix of a recorded command (or '').""" + parts = [] + for token in command.split(" "): + if "=" in token and not token.startswith("-"): + parts.append(token) + else: + break + return " ".join(parts) + + +# --------------------------------------------------------------------------- # +# A. Module structure & imports # +# --------------------------------------------------------------------------- # + + +def test_package_re_exports_backend_symbols(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + package_module = importlib.import_module("agents.extensions.sandbox.aliyun") + + assert package_module.AliyunSandboxClient is aliyun_sandbox.AliyunSandboxClient + assert package_module.AliyunSandboxSessionState is aliyun_sandbox.AliyunSandboxSessionState + assert set(package_module.__all__) == { + "AliyunSandboxClient", + "AliyunSandboxClientOptions", + "AliyunSandboxSession", + "AliyunSandboxSessionState", + "DEFAULT_ALIYUN_WORKSPACE_ROOT", + } + + +def test_type_discriminators(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + assert aliyun_sandbox.AliyunSandboxClientOptions().type == "aliyun" + assert _make_state(aliyun_sandbox).type == "aliyun" + + +def test_options_pydantic_roundtrip(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + opts = aliyun_sandbox.AliyunSandboxClientOptions( + access_key_id="ak", + access_key_secret="sk", + account_id="acct", + api_key="key", + region="cn-shanghai", + template_name="custom", + sandbox_idle_timeout_seconds=900, + env={"FOO": "bar"}, + ) + payload = opts.model_dump() + restored = aliyun_sandbox.AliyunSandboxClientOptions.model_validate(payload) + assert restored == opts + assert payload["type"] == "aliyun" + + +def test_options_positional_args(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + opts = aliyun_sandbox.AliyunSandboxClientOptions("ak", "sk", "acct", "key") + assert opts.access_key_id == "ak" + assert opts.access_key_secret == "sk" + assert opts.account_id == "acct" + assert opts.api_key == "key" + + +# --------------------------------------------------------------------------- # +# B. Helper functions # +# --------------------------------------------------------------------------- # + + +def test_resolve_manifest_root_none(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + manifest = aliyun_sandbox._resolve_manifest_root(None) + assert manifest.root == aliyun_sandbox.DEFAULT_ALIYUN_WORKSPACE_ROOT + + +def test_resolve_manifest_root_default_overridden(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + manifest = aliyun_sandbox._resolve_manifest_root(Manifest()) + assert manifest.root == aliyun_sandbox.DEFAULT_ALIYUN_WORKSPACE_ROOT + + +def test_resolve_manifest_root_custom_preserved(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + manifest = aliyun_sandbox._resolve_manifest_root(Manifest(root="/custom/root")) + assert manifest.root == "/custom/root" + + +def test_build_config_passes_credentials(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + config = aliyun_sandbox._build_config( + access_key_id="ak", + access_key_secret="sk", + account_id="acct", + api_key="key", + region="cn-shanghai", + ) + assert isinstance(config, _FakeConfig) + assert config.kwargs["access_key_id"] == "ak" + assert config.kwargs["access_key_secret"] == "sk" + assert config.kwargs["account_id"] == "acct" + assert config.kwargs["region_id"] == "cn-shanghai" + assert config.kwargs["headers"] == {"X-API-Key": "key"} + + +def test_build_config_without_api_key_omits_headers(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + config = aliyun_sandbox._build_config( + access_key_id=None, + access_key_secret=None, + account_id=None, + api_key=None, + region="cn-hangzhou", + ) + assert config.kwargs["headers"] is None + + +def test_is_retryable_gateway_only_502(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + assert aliyun_sandbox._is_retryable_gateway(_FakeHTTPError(502)) is True + assert aliyun_sandbox._is_retryable_gateway(_FakeHTTPError(400)) is False + assert aliyun_sandbox._is_retryable_gateway(RuntimeError("502: bad gateway")) is True + assert aliyun_sandbox._is_retryable_gateway(RuntimeError("nope")) is False + + +def test_is_not_found_error(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + assert aliyun_sandbox._is_not_found_error(_FakeHTTPError(404)) is True + assert aliyun_sandbox._is_not_found_error(RuntimeError("file not found")) is True + assert aliyun_sandbox._is_not_found_error(RuntimeError("boom")) is False + + +# --------------------------------------------------------------------------- # +# C. User-arg rejection # +# --------------------------------------------------------------------------- # + + +async def test_exec_rejects_user(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + session = _make_session(aliyun_sandbox) + with pytest.raises(ConfigurationError) as excinfo: + await session.exec("echo", "hi", user="root") + assert excinfo.value.context["backend"] == "aliyun" + + +async def test_read_rejects_user(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + session = _make_session(aliyun_sandbox) + with pytest.raises(ConfigurationError): + await session.read(Path("/home/user/x"), user="root") + + +async def test_write_rejects_user(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + session = _make_session(aliyun_sandbox) + with pytest.raises(ConfigurationError): + await session.write( + Path("/home/user/x"), + io.BytesIO(b""), + user=User(name="root"), + ) + + +async def test_append_rejects_user(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + session = _make_session(aliyun_sandbox) + with pytest.raises(ConfigurationError): + await session.append(Path("/home/user/x"), io.BytesIO(b""), user="root") + + +# --------------------------------------------------------------------------- # +# D. _exec_internal # +# --------------------------------------------------------------------------- # + + +async def test_exec_happy_path(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.process.result = {"stdout": "hello\n", "stderr": "", "exitCode": 0} + session = _make_session(aliyun_sandbox, sandbox=sandbox) + + result = await session._exec_internal("echo", "hello") + assert result.exit_code == 0 + assert result.stdout == b"hello\n" + assert result.stderr == b"" + assert result.ok() is True + assert sandbox.process.calls[-1]["command"] == "echo hello" + + +async def test_exec_runs_in_manifest_root( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + await session._exec_internal("pwd") + # All execs (user and internal) run from the workspace root. + assert sandbox.process.calls[-1]["cwd"] == aliyun_sandbox.DEFAULT_ALIYUN_WORKSPACE_ROOT + + +async def test_exec_without_env_has_no_prefix(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + await session._exec_internal("echo", "hi") + # No configured env → command is unprefixed. + assert sandbox.process.calls[-1]["command"] == "echo hi" + + +async def test_exec_shell_false_gets_env_prefix( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session( + aliyun_sandbox, + state=_make_state(aliyun_sandbox, env={"FOO": "bar"}), + sandbox=sandbox, + ) + # Env is inlined per command, so even a direct shell=False exec carries it. + await session.exec("python", "app.py", shell=False) + cmd = sandbox.process.calls[-1]["command"] + assert cmd == "FOO=bar python app.py" + + +async def test_exec_multi_arg_shlex_joined(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + await session._exec_internal("ls", "-la", "/tmp dir with space") + sent = sandbox.process.calls[-1]["command"] + assert "'/tmp dir with space'" in sent or '"/tmp dir with space"' in sent + + +async def test_exec_empty_command_no_call(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + result = await session._exec_internal() + assert result.stdout == b"" + assert result.stderr == b"" + assert result.exit_code == 0 + assert sandbox.process.calls == [] + + +async def test_exec_outer_timeout(monkeypatch: pytest.MonkeyPatch) -> None: + """When the wrapper's outer `asyncio.wait_for` fires, raise ExecTimeoutError.""" + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + + async def _slow(*, command: str, cwd: str | None = None, timeout: int = 30) -> Any: + sandbox.process.calls.append({"command": command, "cwd": cwd, "timeout": timeout}) + await asyncio.sleep(10) + return {"stdout": "", "stderr": "", "exitCode": 0} + + sandbox.process.cmd_async = _slow # type: ignore[method-assign] + session = _make_session(aliyun_sandbox, sandbox=sandbox) + with pytest.raises(ExecTimeoutError): + await session._exec_internal("sleep", "10", timeout=0.01) + + +async def test_exec_transport_error_wraps_exception(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.process.exception = RuntimeError("boom") + session = _make_session(aliyun_sandbox, sandbox=sandbox) + # A non-502, non-timeout failure surfaces as ExecTransportError. + with pytest.raises(ExecTransportError) as excinfo: + await session._exec_internal("echo", "hi") + assert excinfo.value.context["backend"] == "aliyun" + + +async def test_exec_provider_timeout_becomes_exec_timeout( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.process.exception = TimeoutError("command timed out after 30s") + session = _make_session(aliyun_sandbox, sandbox=sandbox) + with pytest.raises(ExecTimeoutError) as excinfo: + await session._exec_internal("echo", "hi") + assert "timed out" in str(excinfo.value.__cause__).lower() + + +async def test_exec_nonzero_exit_returns_result(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.process.result = {"stdout": "", "stderr": "fail", "exitCode": 2} + session = _make_session(aliyun_sandbox, sandbox=sandbox) + result = await session._exec_internal("false") + assert result.exit_code == 2 + assert result.stderr == b"fail" + assert result.ok() is False + + +# --------------------------------------------------------------------------- # +# D2. _run_command gateway-502 retry # +# --------------------------------------------------------------------------- # + + +async def test_run_command_retries_on_gateway_502(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + monkeypatch.setattr(aliyun_sandbox, "ALIYUN_GATEWAY_502_BACKOFF_BASE_S", 0.0) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.process.exceptions = [_FakeHTTPError(502)] # first attempt fails, then succeeds + sandbox.process.result = {"stdout": "ok", "stderr": "", "exitCode": 0} + session = _make_session(aliyun_sandbox, sandbox=sandbox) + + exit_code, stdout, _stderr = await session._run_command(sandbox, "echo", ["hi"]) + assert exit_code == 0 + assert stdout == "ok" + assert len(sandbox.process.calls) == 2 + + +async def test_run_command_no_retry_on_non_502(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.process.exception = _FakeHTTPError(400) + session = _make_session(aliyun_sandbox, sandbox=sandbox) + + with pytest.raises(_FakeHTTPError): + await session._run_command(sandbox, "echo", ["hi"]) + assert len(sandbox.process.calls) == 1 + + +async def test_run_command_forwards_caller_timeout(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + # A caller-requested timeout above the SDK default is forwarded as-is, not clamped. + await session._run_command(sandbox, "echo", ["hi"], timeout_s=999) + assert sandbox.process.calls[-1]["timeout"] == 999 + + +async def test_run_command_defaults_timeout_when_unset(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + await session._run_command(sandbox, "echo", ["hi"]) + assert sandbox.process.calls[-1]["timeout"] == aliyun_sandbox.DEFAULT_ALIYUN_EXEC_TIMEOUT_S + + +async def test_run_command_rounds_up_fractional_timeout(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + # A fractional budget must round up, not truncate down (1.9 -> 2, not 1). + await session._run_command(sandbox, "echo", ["hi"], timeout_s=1.9) + assert sandbox.process.calls[-1]["timeout"] == 2 + + +# --------------------------------------------------------------------------- # +# E. File I/O # +# --------------------------------------------------------------------------- # + + +async def test_write_bytes(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + await session.write(Path("/home/user/file.bin"), io.BytesIO(b"\x00\x01")) + assert sandbox.file_system.files["/home/user/file.bin"] == b"\x00\x01" + + +async def test_write_string_utf8_encoded(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + await session.write(Path("/home/user/file.txt"), io.StringIO("héllo")) + assert sandbox.file_system.files["/home/user/file.txt"] == "héllo".encode() + + +async def test_write_invalid_payload_type(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + + class WeirdStream: + def read(self) -> int: + return 42 + + session = _make_session(aliyun_sandbox) + with pytest.raises(WorkspaceWriteTypeError): + await session.write(Path("/home/user/file.bin"), WeirdStream()) + + +async def test_write_upload_failure(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.file_system.upload_exception = RuntimeError("upload fail") + session = _make_session(aliyun_sandbox, sandbox=sandbox) + with pytest.raises(WorkspaceArchiveWriteError) as excinfo: + await session.write(Path("/home/user/file.bin"), io.BytesIO(b"x")) + assert "upload fail" in str(excinfo.value.__cause__) + + +async def test_write_rejects_oversized_payload(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + monkeypatch.setattr(aliyun_sandbox, "ALIYUN_FILESYSTEM_UPLOAD_MAX_BYTES", 4) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + with pytest.raises(WorkspaceArchiveWriteError) as excinfo: + await session._sandbox_write_file(sandbox, Path("/home/user/big.bin"), b"12345") + assert excinfo.value.context["reason"] == "upload_exceeds_100mb" + + +async def test_read_happy(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.file_system.files["/home/user/file.bin"] = b"hello\xff\x00world" + session = _make_session(aliyun_sandbox, sandbox=sandbox) + buf = await session.read(Path("/home/user/file.bin")) + assert buf.read() == b"hello\xff\x00world" + + +async def test_read_not_found_raises_workspace_read_not_found( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + # The file is absent, so download_async raises FileNotFoundError → not found. + session = _make_session(aliyun_sandbox, sandbox=sandbox) + with pytest.raises(WorkspaceReadNotFoundError): + await session.read(Path("/home/user/missing.bin")) + + +async def test_read_download_transport_error(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.file_system.download_exception = RuntimeError("network") + session = _make_session(aliyun_sandbox, sandbox=sandbox) + with pytest.raises(WorkspaceArchiveReadError): + await session.read(Path("/home/user/file.bin")) + + +async def test_append_creates_then_appends(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + await session.append(Path("/home/user/log.txt"), io.BytesIO(b"a")) + await session.append(Path("/home/user/log.txt"), io.BytesIO(b"b")) + assert sandbox.file_system.files["/home/user/log.txt"] == b"ab" + + +async def test_append_invalid_payload_type(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + + class WeirdStream: + def read(self) -> int: + return 7 + + session = _make_session(aliyun_sandbox) + with pytest.raises(WorkspaceWriteTypeError): + await session.append(Path("/home/user/log.txt"), WeirdStream()) + + +async def test_append_via_wrapper(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + fake_sandbox = _FakeCodeInterpreterSandbox(sandbox_id="wrap") + _FakeSandboxClient.next_sandbox = fake_sandbox + client = aliyun_sandbox.AliyunSandboxClient() + session = await client.create(options=aliyun_sandbox.AliyunSandboxClientOptions()) + + async def _identity(path: Any, *, for_write: bool = False) -> Path: + return path if isinstance(path, Path) else Path(path) + + session._inner._validate_path_access = _identity + await session.append(Path("/home/user/w.txt"), io.BytesIO(b"z")) + assert fake_sandbox.file_system.files["/home/user/w.txt"] == b"z" + + +# --------------------------------------------------------------------------- # +# F. Tar validation # +# --------------------------------------------------------------------------- # + + +def test_validate_tar_bytes_safe(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + session = _make_session(aliyun_sandbox) + raw = _make_tar({"a.txt": b"x", "sub/b.txt": b"y"}) + session._validate_tar_bytes(raw) # should not raise + + +def test_validate_tar_bytes_absolute_rejected(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + session = _make_session(aliyun_sandbox) + raw = _make_tar({"/etc/passwd": b"x"}) + with pytest.raises(ValueError): + session._validate_tar_bytes(raw) + + +def test_validate_tar_bytes_dotdot_rejected(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + session = _make_session(aliyun_sandbox) + raw = _make_tar({"../escape.txt": b"x"}) + with pytest.raises(ValueError): + session._validate_tar_bytes(raw) + + +def test_validate_tar_bytes_invalid_stream(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + session = _make_session(aliyun_sandbox) + with pytest.raises(ValueError): + session._validate_tar_bytes(b"not a tar") + + +def test_validate_tar_bytes_external_symlink_rejected(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + session = _make_session(aliyun_sandbox) + # A symlink whose target escapes the workspace root must be rejected before the + # archive is handed to a remote `tar xf`. + raw = _make_tar_with_symlink("link", "../../etc/passwd") + with pytest.raises(ValueError): + session._validate_tar_bytes(raw) + + +# --------------------------------------------------------------------------- # +# G. Lifecycle # +# --------------------------------------------------------------------------- # + + +async def test_running_true(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.health = {"status": "ok"} + session = _make_session(aliyun_sandbox, sandbox=sandbox) + assert await session.running() is True + + +async def test_running_no_sandbox(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + state = _make_state(aliyun_sandbox) + session = aliyun_sandbox.AliyunSandboxSession.from_state(state) # no sandbox injected + assert await session.running() is False + + +async def test_running_health_raises(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.health = RuntimeError("dead") + session = _make_session(aliyun_sandbox, sandbox=sandbox) + assert await session.running() is False + + +async def test_running_non_dict_health(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.health = object() # non-dict, non-None → treated as running + session = _make_session(aliyun_sandbox, sandbox=sandbox) + assert await session.running() is True + + +async def test_shutdown_deletes_sandbox(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox(sandbox_id="owned-sandbox") + client = _FakeSandboxClient(config=_FakeConfig()) + session = _make_session( + aliyun_sandbox, + state=_make_state(aliyun_sandbox, sandbox_id="owned-sandbox"), + sandbox=sandbox, + client=client, + ) + await session.shutdown() + assert "owned-sandbox" in _FakeSandboxClient.delete_calls + assert session._sandbox is None + + +async def test_shutdown_without_client_uses_handle_delete( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox(sandbox_id="handle") + session = _make_session(aliyun_sandbox, sandbox=sandbox, client=None) + await session.shutdown() + assert sandbox.delete_async_calls == 1 + assert session._sandbox is None + + +async def test_shutdown_idempotent(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox(sandbox_id="owned-sandbox") + client = _FakeSandboxClient(config=_FakeConfig()) + session = _make_session( + aliyun_sandbox, + state=_make_state(aliyun_sandbox, sandbox_id="owned-sandbox"), + sandbox=sandbox, + client=client, + ) + await session.shutdown() + await session.shutdown() # must not raise + assert _FakeSandboxClient.delete_calls.count("owned-sandbox") == 1 + + +# --------------------------------------------------------------------------- # +# H. Port exposure # +# --------------------------------------------------------------------------- # + + +async def test_exposed_port_always_unavailable(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + session = _make_session(aliyun_sandbox) + with pytest.raises(ExposedPortUnavailableError): + await session._resolve_exposed_port(8080) + + +# --------------------------------------------------------------------------- # +# I. AliyunSandboxClient # +# --------------------------------------------------------------------------- # + + +async def test_client_create_threads_options_into_state(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + fake_sandbox = _FakeCodeInterpreterSandbox(sandbox_id="created-sandbox") + _FakeSandboxClient.next_sandbox = fake_sandbox + + client = aliyun_sandbox.AliyunSandboxClient(access_key_id="root-ak") + options = aliyun_sandbox.AliyunSandboxClientOptions( + access_key_id="opt-ak", + access_key_secret="opt-sk", + api_key="opt-key", + region="cn-shanghai", + template_name="t1", + sandbox_idle_timeout_seconds=60, + env={"X": "y"}, + ) + session = await client.create(options=options) + inner = session._inner + st = inner.state + # Credentials are resolved into the agentrun Config held on the session, not state. + assert inner._config.kwargs["access_key_id"] == "opt-ak" + assert inner._config.kwargs["access_key_secret"] == "opt-sk" + assert inner._config.kwargs["headers"] == {"X-API-Key": "opt-key"} + assert inner._config.kwargs["region_id"] == "cn-shanghai" + # Non-credential options pass through to the serializable state. + assert st.template_name == "t1" + assert st.sandbox_idle_timeout_seconds == 60 + assert st.env == {"X": "y"} + assert st.region == "cn-shanghai" + assert st.sandbox_id == "created-sandbox" + + +async def test_client_create_falls_back_to_client_credentials( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + _FakeSandboxClient.next_sandbox = _FakeCodeInterpreterSandbox() + client = aliyun_sandbox.AliyunSandboxClient( + access_key_id="root-ak", + access_key_secret="root-sk", + ) + session = await client.create(options=aliyun_sandbox.AliyunSandboxClientOptions()) + inner = session._inner + assert inner._config.kwargs["access_key_id"] == "root-ak" + assert inner._config.kwargs["access_key_secret"] == "root-sk" + + +async def test_client_create_honors_client_region_default( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + _FakeSandboxClient.next_sandbox = _FakeCodeInterpreterSandbox() + # Client-level region must win when options leaves region unset (default None). + client = aliyun_sandbox.AliyunSandboxClient(region="cn-shanghai") + session = await client.create(options=aliyun_sandbox.AliyunSandboxClientOptions()) + inner = session._inner + assert inner.state.region == "cn-shanghai" + assert inner._config.kwargs["region_id"] == "cn-shanghai" + + +async def test_client_create_options_region_overrides_client( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + _FakeSandboxClient.next_sandbox = _FakeCodeInterpreterSandbox() + client = aliyun_sandbox.AliyunSandboxClient(region="cn-shanghai") + options = aliyun_sandbox.AliyunSandboxClientOptions(region="cn-beijing") + session = await client.create(options=options) + assert session._inner.state.region == "cn-beijing" + + +def test_session_state_serialization_omits_credentials(monkeypatch: pytest.MonkeyPatch) -> None: + """Serialized session state must not leak credentials to disk.""" + aliyun_sandbox = _load_aliyun_module(monkeypatch) + state = _make_state(aliyun_sandbox) + payload = state.model_dump(mode="json") + for forbidden in ("access_key_id", "access_key_secret", "account_id", "api_key"): + assert forbidden not in payload, f"{forbidden!r} unexpectedly present in serialized state" + + +async def test_client_create_raises_on_create_failure(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + _FakeSandboxClient.create_failures = [RuntimeError("boom")] + client = aliyun_sandbox.AliyunSandboxClient() + with pytest.raises(WorkspaceStartError): + await client.create(options=aliyun_sandbox.AliyunSandboxClientOptions()) + + +async def test_client_delete_calls_inner_shutdown(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + fake_sandbox = _FakeCodeInterpreterSandbox(sandbox_id="to-delete") + _FakeSandboxClient.next_sandbox = fake_sandbox + + client = aliyun_sandbox.AliyunSandboxClient() + session = await client.create(options=aliyun_sandbox.AliyunSandboxClientOptions()) + await client.delete(session) + assert "to-delete" in _FakeSandboxClient.delete_calls + + +async def test_client_delete_rejects_wrong_session_type(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + client = aliyun_sandbox.AliyunSandboxClient() + bogus_session = MagicMock() + bogus_session._inner = MagicMock() # not an AliyunSandboxSession + with pytest.raises(TypeError): + await client.delete(bogus_session) + + +async def test_client_resume_rejects_wrong_state_type(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + + class _OtherState(SandboxSessionState): + type: Literal["other"] = "other" + + bad_state = _OtherState( + manifest=Manifest(), + snapshot=NoopSnapshot(id="x"), + ) + client = aliyun_sandbox.AliyunSandboxClient() + with pytest.raises(TypeError): + await client.resume(bad_state) + + +async def test_client_resume_provisions_fresh_when_no_sandbox_id( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + _FakeSandboxClient.next_sandbox = _FakeCodeInterpreterSandbox() + client = aliyun_sandbox.AliyunSandboxClient(access_key_id="resumed-ak") + state = _make_state(aliyun_sandbox, sandbox_id="") + state.workspace_root_ready = True + session = await client.resume(state) + inner = session._inner + assert inner.state.workspace_root_ready is False + assert inner._start_workspace_state_preserved is False + # Resume re-injects credentials from the client onto the session Config. + assert inner._config.kwargs["access_key_id"] == "resumed-ak" + + +async def test_client_resume_reattaches_existing_sandbox( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + existing = _FakeCodeInterpreterSandbox(sandbox_id="live-sandbox") + _FakeSandboxClient.get_result = existing + client = aliyun_sandbox.AliyunSandboxClient(access_key_id="ak", access_key_secret="sk") + state = _make_state(aliyun_sandbox, sandbox_id="live-sandbox") + session = await client.resume(state) + inner = session._inner + assert inner._sandbox is existing + assert inner._start_workspace_state_preserved is True + assert "live-sandbox" in _FakeSandboxClient.get_calls + + +async def test_client_resume_reattach_unhealthy_falls_back( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + # get_sandbox_async returns a record, but the instance is not healthy (stopped). + stale = _FakeCodeInterpreterSandbox(sandbox_id="stale") + stale.health = {"status": "stopped"} + _FakeSandboxClient.get_result = stale + _FakeSandboxClient.next_sandbox = _FakeCodeInterpreterSandbox(sandbox_id="fresh") + client = aliyun_sandbox.AliyunSandboxClient() + state = _make_state(aliyun_sandbox, sandbox_id="stale") + session = await client.resume(state) + inner = session._inner + # Unhealthy reattach → fresh sandbox provisioned, not preserved. + assert inner._start_workspace_state_preserved is False + assert inner.state.sandbox_id == "fresh" + + +async def test_client_resume_falls_back_when_reattach_not_found( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + _FakeSandboxClient.get_result = _FakeHTTPError(404) + _FakeSandboxClient.next_sandbox = _FakeCodeInterpreterSandbox(sandbox_id="fresh") + client = aliyun_sandbox.AliyunSandboxClient() + state = _make_state(aliyun_sandbox, sandbox_id="gone-sandbox") + session = await client.resume(state) + inner = session._inner + assert inner._start_workspace_state_preserved is False + assert inner.state.sandbox_id == "fresh" + + +async def test_create_does_not_mutate_client_credentials( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + _FakeSandboxClient.next_sandbox = _FakeCodeInterpreterSandbox() + client = aliyun_sandbox.AliyunSandboxClient(access_key_id="client-ak") + # A per-call option credential must not overwrite the shared client credential, + # or a later resume() of another session could run under the wrong account. + await client.create(options=aliyun_sandbox.AliyunSandboxClientOptions(access_key_id="opt-ak")) + assert client._access_key_id == "client-ak" + aliyun_sandbox = _load_aliyun_module(monkeypatch) + state = _make_state(aliyun_sandbox, template_name="t", region="cn-shanghai") + client = aliyun_sandbox.AliyunSandboxClient() + payload = client.serialize_session_state(state) + restored = client.deserialize_session_state(payload) + assert isinstance(restored, aliyun_sandbox.AliyunSandboxSessionState) + assert restored.template_name == "t" + assert restored.region == "cn-shanghai" + + +# --------------------------------------------------------------------------- # +# K. Env injection # +# --------------------------------------------------------------------------- # + + +async def test_env_prefix_applied_to_commands(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session( + aliyun_sandbox, state=_make_state(aliyun_sandbox, env={"FOO": "bar"}), sandbox=sandbox + ) + await session._exec_internal("echo", "hi") + assert sandbox.process.calls[-1]["command"] == "FOO=bar echo hi" + + +async def test_env_prefix_merges_manifest_and_options(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + manifest = Manifest( + root=aliyun_sandbox.DEFAULT_ALIYUN_WORKSPACE_ROOT, + environment=Environment(value={"SECRET": "s3cr3t", "SHARED": "from-manifest"}), + ) + state = _make_state(aliyun_sandbox, manifest=manifest, env={"A": "b", "SHARED": "from-options"}) + session = _make_session(aliyun_sandbox, state=state, sandbox=sandbox) + await session._exec_internal("echo", "hi") + prefix = _env_prefix(sandbox.process.calls[-1]["command"]) + assert "A=b" in prefix + assert "SECRET=s3cr3t" in prefix + # Manifest wins on key collisions. + assert "SHARED=from-manifest" in prefix + assert "from-options" not in prefix + + +async def test_env_prefix_quotes_values_with_spaces(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session( + aliyun_sandbox, state=_make_state(aliyun_sandbox, env={"K": "v 1"}), sandbox=sandbox + ) + await session._exec_internal("echo", "hi") + assert "K='v 1'" in sandbox.process.calls[-1]["command"] + + +async def test_env_prefix_skips_unsafe_names(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + state = _make_state( + aliyun_sandbox, env={"GOOD": "1", "BAD; curl evil | sh #": "x", "also bad": "y"} + ) + session = _make_session(aliyun_sandbox, state=state, sandbox=sandbox) + await session._exec_internal("echo", "hi") + cmd = sandbox.process.calls[-1]["command"] + assert "GOOD=1" in cmd + assert "curl evil" not in cmd + assert "also bad" not in cmd + + +async def test_env_prefix_value_with_metachars_is_safe(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + state = _make_state(aliyun_sandbox, env={"EVIL": "x; touch /pwned"}) + session = _make_session(aliyun_sandbox, state=state, sandbox=sandbox) + await session._exec_internal("echo", "hi") + cmd = sandbox.process.calls[-1]["command"] + # The metacharacters are shlex-quoted into the value, not executed. + assert "EVIL='x; touch /pwned'" in cmd + assert cmd.endswith("echo hi") + + +async def test_env_value_not_logged( + monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session( + aliyun_sandbox, + state=_make_state(aliyun_sandbox, env={"SECRET": "s3cr3t-value"}), + sandbox=sandbox, + ) + with caplog.at_level(logging.INFO, logger="agents.extensions.sandbox.aliyun.sandbox"): + await session._exec_internal("echo", "hi") + # The secret reaches the sandbox in the command prefix... + assert "s3cr3t-value" in sandbox.process.calls[-1]["command"] + # ...but the log records only the bare command, never the env prefix. + assert "s3cr3t-value" not in caplog.text + + +# --------------------------------------------------------------------------- # +# L. _prepare_backend_workspace # +# --------------------------------------------------------------------------- # + + +async def test_prepare_backend_workspace_mkdir_ok( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + await session._prepare_backend_workspace() + assert any("mkdir" in c["command"] for c in sandbox.process.calls) + + +async def test_prepare_backend_workspace_mkdir_nonzero_raises( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.process.result = {"stdout": "", "stderr": "denied", "exitCode": 1} + session = _make_session(aliyun_sandbox, sandbox=sandbox) + with pytest.raises(WorkspaceStartError): + await session._prepare_backend_workspace() + + +# --------------------------------------------------------------------------- # +# L2. Health wait after create # +# --------------------------------------------------------------------------- # + + +async def test_wait_until_healthy_polls_until_ready(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + monkeypatch.setattr(aliyun_sandbox, "ALIYUN_HEALTH_POLL_INTERVAL_S", 0.0) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.health = [{"status": "starting"}, {"status": "starting"}, {"status": "ok"}] + session = _make_session(aliyun_sandbox, sandbox=sandbox) + await session._wait_until_healthy(sandbox) # returns once status flips to ok + assert sandbox.health == [] # all three scripted probes consumed + + +async def test_wait_until_healthy_timeout_raises(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + monkeypatch.setattr(aliyun_sandbox, "ALIYUN_HEALTH_POLL_INTERVAL_S", 0.0) + monkeypatch.setattr(aliyun_sandbox, "DEFAULT_ALIYUN_WAIT_FOR_RUNNING_TIMEOUT_S", 3.0) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.health = {"status": "starting"} # never becomes ready + session = _make_session(aliyun_sandbox, sandbox=sandbox) + with pytest.raises(WorkspaceStartError) as excinfo: + await session._wait_until_healthy(sandbox) + assert excinfo.value.context["reason"] == "sandbox_not_healthy" + + +async def test_client_create_waits_for_health_timeout(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + monkeypatch.setattr(aliyun_sandbox, "ALIYUN_HEALTH_POLL_INTERVAL_S", 0.0) + monkeypatch.setattr(aliyun_sandbox, "DEFAULT_ALIYUN_WAIT_FOR_RUNNING_TIMEOUT_S", 2.0) + booting = _FakeCodeInterpreterSandbox(sandbox_id="booting") + booting.health = {"status": "starting"} + _FakeSandboxClient.next_sandbox = booting + client = aliyun_sandbox.AliyunSandboxClient() + with pytest.raises(WorkspaceStartError): + await client.create(options=aliyun_sandbox.AliyunSandboxClientOptions()) + # The sandbox was created but never became healthy → it must be deleted so it + # doesn't leak (create raises without returning a session to tear down). + assert "booting" in _FakeSandboxClient.delete_calls + + +async def test_run_command_retries_resolve_helper_empty_stdout( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + results = [ + {"stdout": "", "stderr": "", "exitCode": 0}, # empty stdout → retry + {"stdout": "/home/user/x", "stderr": "", "exitCode": 0}, + ] + + def _side(*, command: str, cwd: str | None = None, timeout: int = 30) -> Any: + return results.pop(0) + + sandbox.process.side_effect = _side + session = _make_session(aliyun_sandbox, sandbox=sandbox) + exit_code, stdout, _stderr = await session._run_command( + sandbox, "resolve-workspace-path", ["--foo"] + ) + assert stdout == "/home/user/x" + assert len(sandbox.process.calls) == 2 + + +# --------------------------------------------------------------------------- # +# M. Workspace persistence (persist / hydrate) # +# --------------------------------------------------------------------------- # + + +def _archive_path_for(session: Any) -> str: + return f"/tmp/openai-agents-aliyun-{session.state.session_id.hex}.tar" + + +async def test_persist_workspace_happy(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + # `tar cf` succeeds (default exit 0); the archive is present for download. + tar_bytes = _make_tar({"a.txt": b"data"}) + sandbox.file_system.files[_archive_path_for(session)] = tar_bytes + + buf = await session.persist_workspace() + assert buf.read() == tar_bytes + # The temp archive is cleaned up afterward (rm command issued). + assert any(c["command"].startswith("rm ") for c in sandbox.process.calls) + + +async def test_persist_workspace_tar_nonzero_raises(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.process.result = {"stdout": "", "stderr": "tar failed", "exitCode": 2} + session = _make_session(aliyun_sandbox, sandbox=sandbox) + with pytest.raises(WorkspaceArchiveReadError): + await session.persist_workspace() + + +async def test_persist_workspace_missing_archive_raises_not_found( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + # tar reports success but the archive file is absent → not found. + session = _make_session(aliyun_sandbox, sandbox=sandbox) + with pytest.raises(WorkspaceReadNotFoundError): + await session.persist_workspace() + + +async def test_hydrate_workspace_happy(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + tar_bytes = _make_tar({"a.txt": b"data"}) + await session.hydrate_workspace(io.BytesIO(tar_bytes)) + # The archive was uploaded before extraction. + assert sandbox.file_system.files[_archive_path_for(session)] == tar_bytes + cmds = [c["command"] for c in sandbox.process.calls] + assert any(c.startswith("tar xf") for c in cmds) + + +async def test_hydrate_workspace_invalid_tar_raises(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + session = _make_session(aliyun_sandbox, sandbox=sandbox) + with pytest.raises(WorkspaceArchiveWriteError): + await session.hydrate_workspace(io.BytesIO(b"not a tar")) + + +async def test_hydrate_workspace_extract_nonzero_raises( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + sandbox = _FakeCodeInterpreterSandbox() + sandbox.process.result = {"stdout": "", "stderr": "extract failed", "exitCode": 2} + session = _make_session(aliyun_sandbox, sandbox=sandbox) + tar_bytes = _make_tar({"a.txt": b"data"}) + with pytest.raises(WorkspaceArchiveWriteError): + await session.hydrate_workspace(io.BytesIO(tar_bytes)) + + +# --------------------------------------------------------------------------- # +# N. Client error branches # +# --------------------------------------------------------------------------- # + + +async def test_client_delete_swallows_shutdown_failure(monkeypatch: pytest.MonkeyPatch) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + _FakeSandboxClient.next_sandbox = _FakeCodeInterpreterSandbox(sandbox_id="x") + client = aliyun_sandbox.AliyunSandboxClient() + session = await client.create(options=aliyun_sandbox.AliyunSandboxClientOptions()) + + async def _boom() -> None: + raise RuntimeError("shutdown failed") + + session._inner.shutdown = _boom + result = await client.delete(session) # must not raise + assert result is session + + +async def test_client_resume_reattach_error_falls_back_to_fresh( + monkeypatch: pytest.MonkeyPatch, +) -> None: + aliyun_sandbox = _load_aliyun_module(monkeypatch) + # Non-404 error from get_sandbox_async → reattach re-raises → resume falls back. + _FakeSandboxClient.get_result = RuntimeError("boom") + _FakeSandboxClient.next_sandbox = _FakeCodeInterpreterSandbox(sandbox_id="fresh") + client = aliyun_sandbox.AliyunSandboxClient() + state = _make_state(aliyun_sandbox, sandbox_id="stale-id") + session = await client.resume(state) + inner = session._inner + assert inner.state.sandbox_id == "fresh" + assert inner._start_workspace_state_preserved is False diff --git a/uv.lock b/uv.lock index a84b7c1695..fc15bbdd60 100644 --- a/uv.lock +++ b/uv.lock @@ -3,15 +3,61 @@ revision = 3 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.14'", - "python_full_version >= '3.12' and python_full_version < '3.14'", + "python_full_version == '3.13.*'", + "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", "python_full_version < '3.11'", ] [options] -exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. +exclude-newer = "2026-06-28T05:55:49.691228Z" exclude-newer-span = "P7D" +[[package]] +name = "agentrun-mem0ai" +version = "0.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mysql-connector-python" }, + { name = "openai" }, + { name = "posthog" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "pytz" }, + { name = "qdrant-client" }, + { name = "sqlalchemy" }, + { name = "tablestore-for-agent-memory" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/83/1696d24eb17a62038d713a491a235f7818968c23577f85ffce19cf8f0781/agentrun_mem0ai-0.0.12.tar.gz", hash = "sha256:c52e7ba6fd1dba39c07a1fd5ce635e2a9f1cd390f6284ba0f2ab32ecbae4a93b", size = 184613, upload-time = "2026-01-26T07:53:22.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f4/09700f1bdbe2dcabbacdf5894cb6f2bb3a2af7602d1584399c51e21a7475/agentrun_mem0ai-0.0.12-py3-none-any.whl", hash = "sha256:4028139966458fe9f21c4989e5bc3f4cdededf68471e86f118c9839ce0aaa03a", size = 282033, upload-time = "2026-01-26T07:53:19.645Z" }, +] + +[[package]] +name = "agentrun-sdk" +version = "0.0.35" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "agentrun-mem0ai" }, + { name = "alibabacloud-agentrun20250910" }, + { name = "alibabacloud-bailian20231229" }, + { name = "alibabacloud-devs20230714" }, + { name = "alibabacloud-gpdb20160503" }, + { name = "alibabacloud-tea-openapi" }, + { name = "crcmod" }, + { name = "httpx" }, + { name = "litellm" }, + { name = "pydantic" }, + { name = "pydash" }, + { name = "python-dotenv" }, + { name = "tablestore-agent-storage" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c8/f9/2be8be302a4411574aee2b036d69403c6c132020c662060c16d2063d5f49/agentrun_sdk-0.0.35.tar.gz", hash = "sha256:9b71629eefefc74f16c402e0b3d1febe1ef6796b9809b24ad149b23eaaec644a", size = 367386, upload-time = "2026-05-07T10:28:41.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/1d/61e9bdaf939cd33719618a5b33470d87ce0901ea71967013c273687ccc5a/agentrun_sdk-0.0.35-py3-none-any.whl", hash = "sha256:1cada914c060c7a0bc7f79d207974b5f47cca92c26e0275f46d7bb22fd9fd060", size = 476088, upload-time = "2026-05-07T10:28:38.995Z" }, +] + [[package]] name = "aiofiles" version = "24.1.0" @@ -153,6 +199,176 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f5/10/6c25ed6de94c49f88a91fa5018cb4c0f3625f31d5be9f771ebe5cc7cd506/aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0", size = 15792, upload-time = "2025-02-03T07:30:13.6Z" }, ] +[[package]] +name = "alibabacloud-agentrun20250910" +version = "5.7.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alibabacloud-tea-openapi" }, + { name = "darabonba-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/27/c50eb2ecfea19f0703581c278e65dd294467b198c08a330032e92734303a/alibabacloud_agentrun20250910-5.7.4.tar.gz", hash = "sha256:3e4d83619a7b7573ca3967fb6cac34e8442ecfceb15607a31bea48e217941932", size = 148410, upload-time = "2026-06-16T17:28:33.496Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/b3/28d571a6fb6ea710f3769ad9b7e0e1e74dc6e6abacb9e0fc72b5ffed8bd6/alibabacloud_agentrun20250910-5.7.4-py3-none-any.whl", hash = "sha256:fbfcb01cde85ce9c655c62e87fab75d33047bc391c9598b6d42fb1d773c5c3cf", size = 447357, upload-time = "2026-06-16T17:28:32.368Z" }, +] + +[[package]] +name = "alibabacloud-bailian20231229" +version = "2.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alibabacloud-tea-openapi" }, + { name = "darabonba-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/ea/999914758c5c7ce845859edb87b85e1d39d4fd74ee3e0479a21547f89971/alibabacloud_bailian20231229-2.13.1.tar.gz", hash = "sha256:ae1be289078b31bb03c3f1a7237e50ccacdf5961dad014fa9a55dadb7ccd7563", size = 93315, upload-time = "2026-06-18T06:56:28.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/4e/d1b9bcad82bba28f4ffd2ae4b092094fdae5b73ef22cf116eed944aacaa8/alibabacloud_bailian20231229-2.13.1-py3-none-any.whl", hash = "sha256:285a81432908ae91610f1120af9699d7155f7df1dfbcad2ac501d126672b4767", size = 231470, upload-time = "2026-06-18T06:56:27.008Z" }, +] + +[[package]] +name = "alibabacloud-credentials" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "alibabacloud-credentials-api" }, + { name = "alibabacloud-tea" }, + { name = "apscheduler" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/0d/1f340b3cd601174ea1fe92eca788a58283974b75d59dc1c7fabbcc1b935e/alibabacloud_credentials-1.0.9.tar.gz", hash = "sha256:11ad6206ce2bacc5b4ba5f2c7acfec93d70444dfe2e2c065c8017512e828019b", size = 42100, upload-time = "2026-06-09T12:23:39.095Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/9e/0f0c9e100113f30496992e08cde2f21607967a589c55ed1e20f1023a875a/alibabacloud_credentials-1.0.9-py3-none-any.whl", hash = "sha256:e572cdfb46e4cbf62afa4d61e8516488731cd69aed7da24fe57222d0b976944f", size = 50879, upload-time = "2026-06-09T12:23:37.965Z" }, +] + +[[package]] +name = "alibabacloud-credentials-api" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/89/7e/6145e2d132752f0de72701df556304c59dda311405a252a598ad6fd9b4de/alibabacloud_credentials_api-1.0.1.tar.gz", hash = "sha256:8ea0668a6558f6956b8d20b2e561d19a80ea29c22cf56a3004d434b24a981b36", size = 2314, upload-time = "2026-06-25T06:50:33.198Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/49/ff401af96d77d40ab918096d831bd3360f3eec60f10e65125e5789c4e617/alibabacloud_credentials_api-1.0.1-py3-none-any.whl", hash = "sha256:5f27889113214fc53493b3e918eb26d73dfab2022e22bdaac262849b8e58cbc0", size = 2230, upload-time = "2026-06-25T06:49:09.991Z" }, +] + +[[package]] +name = "alibabacloud-devs20230714" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alibabacloud-endpoint-util" }, + { name = "alibabacloud-openapi-util" }, + { name = "alibabacloud-tea-openapi" }, + { name = "alibabacloud-tea-util" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/b4/a6425a5d54dbdd83206b9c0418e9fded4764a1125bbefbe9ff9511ed2a72/alibabacloud_devs20230714-2.4.1.tar.gz", hash = "sha256:461e7614dc382b49d576ac8713d949beb48b1979cea002922bdb284883360f20", size = 60979, upload-time = "2025-08-08T07:40:29.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/c6/7d375cc1b1cab0f46950f556b70a2b17235747429a0889b73f3d46ff6023/alibabacloud_devs20230714-2.4.1-py3-none-any.whl", hash = "sha256:dbd260718e6db50021d804218b40bc99ee9c7e40b1def382aef8e542f5921113", size = 59307, upload-time = "2025-08-08T07:40:28.504Z" }, +] + +[[package]] +name = "alibabacloud-endpoint-util" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/7d/8cc92a95c920e344835b005af6ea45a0db98763ad6ad19299d26892e6c8d/alibabacloud_endpoint_util-0.0.4.tar.gz", hash = "sha256:a593eb8ddd8168d5dc2216cd33111b144f9189fcd6e9ca20e48f358a739bbf90", size = 2813, upload-time = "2025-06-12T07:20:52.572Z" } + +[[package]] +name = "alibabacloud-gateway-spi" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alibabacloud-credentials" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/67/7cd36a36bf0ec53efd3fe31eaf398713f375b7b1cdcd32c51b6c0fdc09e2/alibabacloud_gateway_spi-0.0.4.tar.gz", hash = "sha256:73d6e20d65b54eed26d89c19640d3a7572e18c45ecada627f806f5dbe8ed2130", size = 4253, upload-time = "2026-06-25T06:34:08.238Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/85/81170c45e9d1240736f9776a1c690b958c975509381221b5f8d960b5376d/alibabacloud_gateway_spi-0.0.4-py3-none-any.whl", hash = "sha256:0d5256e95d8719da8ec9611b7ffbb12c2d26fdf7ce52c8f64e4e06350731e893", size = 4290, upload-time = "2026-06-25T06:34:07.22Z" }, +] + +[[package]] +name = "alibabacloud-gpdb20160503" +version = "5.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alibabacloud-credentials" }, + { name = "alibabacloud-tea-openapi" }, + { name = "darabonba-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/b0/970269fb8ef22696ec39870cca61e1c200e551ddc39ab672f6d19414f173/alibabacloud_gpdb20160503-5.6.0.tar.gz", hash = "sha256:e612a64b07e904ee78369c610bcd052460b4082652499500916894b6aa61a97a", size = 328251, upload-time = "2026-06-26T17:34:44.3Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/3b/cbb9c8da01256d0eddbe26da3eedf4882154c497f1d5230732242b87eab6/alibabacloud_gpdb20160503-5.6.0-py3-none-any.whl", hash = "sha256:45b06d0efe2edafd010435d0630989e5eda8b60aedd99075513d88b940c2d100", size = 934788, upload-time = "2026-06-26T17:34:42.976Z" }, +] + +[[package]] +name = "alibabacloud-openapi-util" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alibabacloud-tea-util" }, + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/51/be5802851a4ed20ac2c6db50ac8354a6e431e93db6e714ca39b50983626f/alibabacloud_openapi_util-0.2.4.tar.gz", hash = "sha256:87022b9dcb7593a601f7a40ca698227ac3ccb776b58cb7b06b8dc7f510995c34", size = 7981, upload-time = "2026-01-15T08:05:03.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/46/9b217343648b366eb93447f5d93116e09a61956005794aed5ef95a2e9e2e/alibabacloud_openapi_util-0.2.4-py3-none-any.whl", hash = "sha256:a2474f230b5965ae9a8c286e0dc86132a887928d02d20b8182656cf6b1b6c5bd", size = 7661, upload-time = "2026-01-15T08:05:01.374Z" }, +] + +[[package]] +name = "alibabacloud-tea" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/7d/b22cb9a0d4f396ee0f3f9d7f26b76b9ed93d4101add7867a2c87ed2534f5/alibabacloud-tea-0.4.3.tar.gz", hash = "sha256:ec8053d0aa8d43ebe1deb632d5c5404339b39ec9a18a0707d57765838418504a", size = 8785, upload-time = "2025-03-24T07:34:42.958Z" } + +[[package]] +name = "alibabacloud-tea-openapi" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alibabacloud-credentials" }, + { name = "alibabacloud-gateway-spi" }, + { name = "alibabacloud-tea-util" }, + { name = "cryptography" }, + { name = "darabonba-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/93/138bcdc8fc596add73e37cf2073798f285284d1240bda9ee02f9384fc6be/alibabacloud_tea_openapi-0.4.4.tar.gz", hash = "sha256:1b0917bc03cd49417da64945e92731716d53e2eb8707b235f54e45b7473221ce", size = 21960, upload-time = "2026-03-26T10:16:16.792Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/5a/6bfc4506438c1809c486f66217ad11eab78157192b3d5707b4e2f4212f6c/alibabacloud_tea_openapi-0.4.4-py3-none-any.whl", hash = "sha256:cea6bc1fe35b0319a8752cb99eb0ecb0dab7ca1a71b99c12970ba0867410995f", size = 26236, upload-time = "2026-03-26T10:16:15.861Z" }, +] + +[[package]] +name = "alibabacloud-tea-util" +version = "0.3.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alibabacloud-tea" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/ee/ea90be94ad781a5055db29556744681fc71190ef444ae53adba45e1be5f3/alibabacloud_tea_util-0.3.14.tar.gz", hash = "sha256:708e7c9f64641a3c9e0e566365d2f23675f8d7c2a3e2971d9402ceede0408cdb", size = 7515, upload-time = "2025-11-19T06:01:08.504Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/9e/c394b4e2104766fb28a1e44e3ed36e4c7773b4d05c868e482be99d5635c9/alibabacloud_tea_util-0.3.14-py3-none-any.whl", hash = "sha256:10d3e5c340d8f7ec69dd27345eb2fc5a1dab07875742525edf07bbe86db93bfe", size = 6697, upload-time = "2025-11-19T06:01:07.355Z" }, +] + +[[package]] +name = "aliyun-python-sdk-core" +version = "2.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jmespath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/09/da9f58eb38b4fdb97ba6523274fbf445ef6a06be64b433693da8307b4bec/aliyun-python-sdk-core-2.16.0.tar.gz", hash = "sha256:651caad597eb39d4fad6cf85133dffe92837d53bdf62db9d8f37dab6508bb8f9", size = 449555, upload-time = "2024-10-09T06:01:01.762Z" } + +[[package]] +name = "aliyun-python-sdk-kms" +version = "2.16.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aliyun-python-sdk-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/2c/9877d0e6b18ecf246df671ac65a5d1d9fecbf85bdcb5d43efbde0d4662eb/aliyun-python-sdk-kms-2.16.5.tar.gz", hash = "sha256:f328a8a19d83ecbb965ffce0ec1e9930755216d104638cd95ecd362753b813b3", size = 12018, upload-time = "2024-08-30T09:01:20.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/5c/0132193d7da2c735669a1ed103b142fd63c9455984d48c5a88a1a516efaa/aliyun_python_sdk_kms-2.16.5-py2.py3-none-any.whl", hash = "sha256:24b6cdc4fd161d2942619479c8d050c63ea9cd22b044fe33b60bbb60153786f0", size = 99495, upload-time = "2024-08-30T09:01:18.462Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -194,6 +410,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, ] +[[package]] +name = "apscheduler" +version = "3.11.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzlocal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/12/3e4389e5920b4c1763390c6d371162f3784f86f85cd6d6c1bfe68eef14e2/apscheduler-3.11.2.tar.gz", hash = "sha256:2a9966b052ec805f020c8c4c3ae6e6a06e24b1bf19f2e11d91d8cca0473eef41", size = 108683, upload-time = "2025-12-22T00:39:34.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/64/2e54428beba8d9992aa478bb8f6de9e4ecaa5f8f513bcfd567ed7fb0262d/apscheduler-3.11.2-py3-none-any.whl", hash = "sha256:ce005177f741409db4e4dd40a7431b76feb856b9dd69d57e0da49d6715bfd26d", size = 64439, upload-time = "2025-12-22T00:39:33.303Z" }, +] + [[package]] name = "asttokens" version = "3.0.0" @@ -273,6 +501,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, ] +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, +] + [[package]] name = "backports-asyncio-runner" version = "1.2.0" @@ -669,6 +906,88 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/19/e67f4ae24e232c7f713337f3f4f7c9c58afd0c02866fb07c7b9255a19ed7/coverage-7.10.3-py3-none-any.whl", hash = "sha256:416a8d74dc0adfd33944ba2f405897bab87b7e9e84a391e09d241956bd953ce1", size = 207921, upload-time = "2025-08-10T21:27:38.254Z" }, ] +[[package]] +name = "crc32c" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/66/7e97aa77af7cf6afbff26e3651b564fe41932599bc2d3dce0b2f73d4829a/crc32c-2.8.tar.gz", hash = "sha256:578728964e59c47c356aeeedee6220e021e124b9d3e8631d95d9a5e5f06e261c", size = 48179, upload-time = "2025-10-17T06:20:13.61Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/a0/28b4686a8db0bb0f77970f4c6ccede90d1d5740a1d4b4703bd54c3e75655/crc32c-2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2c0f4eb01fe7c0a3e3f973a418e04d52101bb077dd77626fd80c658ec60aaf95", size = 66321, upload-time = "2025-10-17T06:18:53.543Z" }, + { url = "https://files.pythonhosted.org/packages/76/1f/1697f5b8b770f715ed9b264d79e36b4f77ae0527f81f3c749ef08937a32e/crc32c-2.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6baefcfbca82b1a9678455416da24f18629769a76920c640d5a538620a7d12bb", size = 62985, upload-time = "2025-10-17T06:18:54.97Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/333cfa5ffa8d5779733aced2b984b5e5139b4a8ceaa2c6bc563e9a1092f3/crc32c-2.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d7f959fcf6c5aad1c4a653ee1a50f05760dab1d1c35d98ec4d7f0f68643f7612", size = 61517, upload-time = "2025-10-17T06:18:55.795Z" }, + { url = "https://files.pythonhosted.org/packages/e1/d8/362a009e8140dd926a153b44d56753e3aa7cb50aca243779a84adadbff11/crc32c-2.8-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9bb678507a4e4cf3f0506607b046ecc4ed1c58a19e08a3fb3c2d25441c480bf1", size = 79385, upload-time = "2025-10-17T06:18:56.598Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0d4ea3aa71ffb15f1285669d23024cc40779388ce32157d339dc2584491c/crc32c-2.8-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a16f7ffa4c242a909558565567cbba95148603717b53538ea299c98da68e7a9", size = 80965, upload-time = "2025-10-17T06:18:57.384Z" }, + { url = "https://files.pythonhosted.org/packages/20/44/d77657aaca4a2c0283f2356a3da6f8e91b003567bb8f09daaf540cbf192f/crc32c-2.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0184369aad562d801f91f454c81f56b9ecb966f6b96684c4d6cf82fc8741d2ad", size = 79993, upload-time = "2025-10-17T06:18:58.503Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c0/07017a93ebf85d9408028b7e03ef96d5c6bfb14cb77cfe90d35eedcc1501/crc32c-2.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:86d2eeb5f0189bd803720abe7387019328ea34c4acde62999e5723f789bc316b", size = 79243, upload-time = "2025-10-17T06:18:59.273Z" }, + { url = "https://files.pythonhosted.org/packages/c7/1a/b3c5ac4cf2fd1f82395173d0bd8e1a15d09f0bc1eccdf10ea7f8caaccd67/crc32c-2.8-cp310-cp310-win32.whl", hash = "sha256:51da61904a9e753780a2e6011885677d601db1fa840be4b68799643a113e6f08", size = 64888, upload-time = "2025-10-17T06:19:00.089Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f2/60c45fc7bb2221d3c93c7a872e921be591f40d45228fe46f879b1d8c0424/crc32c-2.8-cp310-cp310-win_amd64.whl", hash = "sha256:b2d6a1f2500daaf2e4b08f97ad0349aa2eff5faaaa5fd3350314a26eade334cd", size = 66639, upload-time = "2025-10-17T06:19:00.974Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0b/5e03b22d913698e9cc563f39b9f6bbd508606bf6b8e9122cd6bf196b87ea/crc32c-2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e560a97fbb96c9897cb1d9b5076ef12fc12e2e25622530a1afd0de4240f17e1f", size = 66329, upload-time = "2025-10-17T06:19:01.771Z" }, + { url = "https://files.pythonhosted.org/packages/6b/38/2fe0051ffe8c6a650c8b1ac0da31b8802d1dbe5fa40a84e4b6b6f5583db5/crc32c-2.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6762d276d90331a490ef7e71ffee53b9c0eb053bd75a272d786f3b08d3fe3671", size = 62988, upload-time = "2025-10-17T06:19:02.953Z" }, + { url = "https://files.pythonhosted.org/packages/3e/30/5837a71c014be83aba1469c58820d287fc836512a0cad6b8fdd43868accd/crc32c-2.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:60670569f5ede91e39f48fb0cb4060e05b8d8704dd9e17ede930bf441b2f73ef", size = 61522, upload-time = "2025-10-17T06:19:03.796Z" }, + { url = "https://files.pythonhosted.org/packages/ca/29/63972fc1452778e2092ae998c50cbfc2fc93e3fa9798a0278650cd6169c5/crc32c-2.8-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:711743da6ccc70b3c6718c328947b0b6f34a1fe6a6c27cc6c1d69cc226bf70e9", size = 80200, upload-time = "2025-10-17T06:19:04.617Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3a/60eb49d7bdada4122b3ffd45b0df54bdc1b8dd092cda4b069a287bdfcff4/crc32c-2.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5eb4094a2054774f13b26f21bf56792bb44fa1fcee6c6ad099387a43ffbfb4fa", size = 81757, upload-time = "2025-10-17T06:19:05.496Z" }, + { url = "https://files.pythonhosted.org/packages/f5/63/6efc1b64429ef7d23bd58b75b7ac24d15df327e3ebbe9c247a0f7b1c2ed1/crc32c-2.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fff15bf2bd3e95780516baae935ed12be88deaa5ebe6143c53eb0d26a7bdc7b7", size = 80830, upload-time = "2025-10-17T06:19:06.621Z" }, + { url = "https://files.pythonhosted.org/packages/e1/eb/0ae9f436f8004f1c88f7429e659a7218a3879bd11a6b18ed1257aad7e98b/crc32c-2.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c0e11e3826668121fa53e0745635baf5e4f0ded437e8ff63ea56f38fc4f970a", size = 80095, upload-time = "2025-10-17T06:19:07.381Z" }, + { url = "https://files.pythonhosted.org/packages/9e/81/4afc9d468977a4cd94a2eb62908553345009a7c0d30e74463a15d4b48ec3/crc32c-2.8-cp311-cp311-win32.whl", hash = "sha256:38f915336715d1f1353ab07d7d786f8a789b119e273aea106ba55355dfc9101d", size = 64886, upload-time = "2025-10-17T06:19:08.497Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e8/94e839c9f7e767bf8479046a207afd440a08f5c59b52586e1af5e64fa4a0/crc32c-2.8-cp311-cp311-win_amd64.whl", hash = "sha256:60e0a765b1caab8d31b2ea80840639253906a9351d4b861551c8c8625ea20f86", size = 66639, upload-time = "2025-10-17T06:19:09.338Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/fd18ef23c42926b79c7003e16cb0f79043b5b179c633521343d3b499e996/crc32c-2.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:572ffb1b78cce3d88e8d4143e154d31044a44be42cb3f6fbbf77f1e7a941c5ab", size = 66379, upload-time = "2025-10-17T06:19:10.115Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b8/c584958e53f7798dd358f5bdb1bbfc97483134f053ee399d3eeb26cca075/crc32c-2.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cf827b3758ee0c4aacd21ceca0e2da83681f10295c38a10bfeb105f7d98f7a68", size = 63042, upload-time = "2025-10-17T06:19:10.946Z" }, + { url = "https://files.pythonhosted.org/packages/62/e6/6f2af0ec64a668a46c861e5bc778ea3ee42171fedfc5440f791f470fd783/crc32c-2.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:106fbd79013e06fa92bc3b51031694fcc1249811ed4364ef1554ee3dd2c7f5a2", size = 61528, upload-time = "2025-10-17T06:19:11.768Z" }, + { url = "https://files.pythonhosted.org/packages/17/8b/4a04bd80a024f1a23978f19ae99407783e06549e361ab56e9c08bba3c1d3/crc32c-2.8-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6dde035f91ffbfe23163e68605ee5a4bb8ceebd71ed54bb1fb1d0526cdd125a2", size = 80028, upload-time = "2025-10-17T06:19:12.554Z" }, + { url = "https://files.pythonhosted.org/packages/21/8f/01c7afdc76ac2007d0e6a98e7300b4470b170480f8188475b597d1f4b4c6/crc32c-2.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e41ebe7c2f0fdcd9f3a3fd206989a36b460b4d3f24816d53e5be6c7dba72c5e1", size = 81531, upload-time = "2025-10-17T06:19:13.406Z" }, + { url = "https://files.pythonhosted.org/packages/32/2b/8f78c5a8cc66486be5f51b6f038fc347c3ba748d3ea68be17a014283c331/crc32c-2.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ecf66cf90266d9c15cea597d5cc86c01917cd1a238dc3c51420c7886fa750d7e", size = 80608, upload-time = "2025-10-17T06:19:14.223Z" }, + { url = "https://files.pythonhosted.org/packages/db/86/fad1a94cdeeeb6b6e2323c87f970186e74bfd6fbfbc247bf5c88ad0873d5/crc32c-2.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:59eee5f3a69ad0793d5fa9cdc9b9d743b0cd50edf7fccc0a3988a821fef0208c", size = 79886, upload-time = "2025-10-17T06:19:15.345Z" }, + { url = "https://files.pythonhosted.org/packages/d5/db/1a7cb6757a1e32376fa2dfce00c815ea4ee614a94f9bff8228e37420c183/crc32c-2.8-cp312-cp312-win32.whl", hash = "sha256:a73d03ce3604aa5d7a2698e9057a0eef69f529c46497b27ee1c38158e90ceb76", size = 64896, upload-time = "2025-10-17T06:19:16.457Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8e/2024de34399b2e401a37dcb54b224b56c747b0dc46de4966886827b4d370/crc32c-2.8-cp312-cp312-win_amd64.whl", hash = "sha256:56b3b7d015247962cf58186e06d18c3d75a1a63d709d3233509e1c50a2d36aa2", size = 66645, upload-time = "2025-10-17T06:19:17.235Z" }, + { url = "https://files.pythonhosted.org/packages/e8/d8/3ae227890b3be40955a7144106ef4dd97d6123a82c2a5310cdab58ca49d8/crc32c-2.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:36f1e03ee9e9c6938e67d3bcb60e36f260170aa5f37da1185e04ef37b56af395", size = 66380, upload-time = "2025-10-17T06:19:18.009Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8b/178d3f987cd0e049b484615512d3f91f3d2caeeb8ff336bb5896ae317438/crc32c-2.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b2f3226b94b85a8dd9b3533601d7a63e9e3e8edf03a8a169830ee8303a199aeb", size = 63048, upload-time = "2025-10-17T06:19:18.853Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a1/48145ae2545ebc0169d3283ebe882da580ea4606bfb67cf4ca922ac3cfc3/crc32c-2.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6e08628bc72d5b6bc8e0730e8f142194b610e780a98c58cb6698e665cb885a5b", size = 61530, upload-time = "2025-10-17T06:19:19.974Z" }, + { url = "https://files.pythonhosted.org/packages/06/4b/cf05ed9d934cc30e5ae22f97c8272face420a476090e736615d9a6b53de0/crc32c-2.8-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:086f64793c5ec856d1ab31a026d52ad2b895ac83d7a38fce557d74eb857f0a82", size = 80001, upload-time = "2025-10-17T06:19:20.784Z" }, + { url = "https://files.pythonhosted.org/packages/15/ab/4b04801739faf36345f6ba1920be5b1c70282fec52f8280afd3613fb13e2/crc32c-2.8-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcf72ee7e0135b3d941c34bb2c26c3fc6bc207106b49fd89aaafaeae223ae209", size = 81543, upload-time = "2025-10-17T06:19:21.557Z" }, + { url = "https://files.pythonhosted.org/packages/a9/1b/6e38dde5bfd2ea69b7f2ab6ec229fcd972a53d39e2db4efe75c0ac0382ce/crc32c-2.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8a717dd9c3fd777d9bc6603717eae172887d402c4ab589d124ebd0184a83f89e", size = 80644, upload-time = "2025-10-17T06:19:22.325Z" }, + { url = "https://files.pythonhosted.org/packages/ce/45/012176ffee90059ae8ec7131019c71724ea472aa63e72c0c8edbd1fad1d7/crc32c-2.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0450bb845b3c3c7b9bdc0b4e95620ec9a40824abdc8c86d6285c919a90743c1a", size = 79919, upload-time = "2025-10-17T06:19:23.101Z" }, + { url = "https://files.pythonhosted.org/packages/f0/2b/f557629842f9dec2b3461cb3a0d854bb586ec45b814cea58b082c32f0dde/crc32c-2.8-cp313-cp313-win32.whl", hash = "sha256:765d220bfcbcffa6598ac11eb1e10af0ee4802b49fe126aa6bf79f8ddb9931d1", size = 64896, upload-time = "2025-10-17T06:19:23.88Z" }, + { url = "https://files.pythonhosted.org/packages/d0/db/fd0f698c15d1e21d47c64181a98290665a08fcbb3940cd559e9c15bda57e/crc32c-2.8-cp313-cp313-win_amd64.whl", hash = "sha256:171ff0260d112c62abcce29332986950a57bddee514e0a2418bfde493ea06bb3", size = 66646, upload-time = "2025-10-17T06:19:24.702Z" }, + { url = "https://files.pythonhosted.org/packages/db/b9/8e5d7054fe8e7eecab10fd0c8e7ffb01439417bdb6de1d66a81c38fc4a20/crc32c-2.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b977a32a3708d6f51703c8557008f190aaa434d7347431efb0e86fcbe78c2a50", size = 66203, upload-time = "2025-10-17T06:19:25.872Z" }, + { url = "https://files.pythonhosted.org/packages/55/5f/cc926c70057a63cc0c98a3c8a896eb15fc7e74d3034eadd53c94917c6cc3/crc32c-2.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7399b01db4adaf41da2fb36fe2408e75a8d82a179a9564ed7619412e427b26d6", size = 62956, upload-time = "2025-10-17T06:19:26.652Z" }, + { url = "https://files.pythonhosted.org/packages/a1/8a/0660c44a2dd2cb6ccbb529eb363b9280f5c766f1017bc8355ed8d695bd94/crc32c-2.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4379f73f9cdad31958a673d11a332ec725ca71572401ca865867229f5f15e853", size = 61442, upload-time = "2025-10-17T06:19:27.74Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5a/6108d2dfc0fe33522ce83ba07aed4b22014911b387afa228808a278e27cd/crc32c-2.8-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2e68264555fab19bab08331550dab58573e351a63ed79c869d455edd3b0aa417", size = 79109, upload-time = "2025-10-17T06:19:28.535Z" }, + { url = "https://files.pythonhosted.org/packages/84/1e/c054f9e390090c197abf3d2936f4f9effaf0c6ee14569ae03d6ddf86958a/crc32c-2.8-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b48f2486727b8d0e7ccbae4a34cb0300498433d2a9d6b49cb13cb57c2e3f19cb", size = 80987, upload-time = "2025-10-17T06:19:29.305Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ad/1650e5c3341e4a485f800ea83116d72965030c5d48ccc168fcc685756e4d/crc32c-2.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ecf123348934a086df8c8fde7f9f2d716d523ca0707c5a1367b8bb00d8134823", size = 79994, upload-time = "2025-10-17T06:19:30.109Z" }, + { url = "https://files.pythonhosted.org/packages/d7/3b/f2ed924b177729cbb2ab30ca2902abff653c31d48c95e7b66717a9ca9fcc/crc32c-2.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e636ac60f76de538f7a2c0d0f3abf43104ee83a8f5e516f6345dc283ed1a4df7", size = 79046, upload-time = "2025-10-17T06:19:30.894Z" }, + { url = "https://files.pythonhosted.org/packages/4b/80/413b05ee6ace613208b31b3670c3135ee1cf451f0e72a9c839b4946acc04/crc32c-2.8-cp313-cp313t-win32.whl", hash = "sha256:8dd4a19505e0253892e1b2f1425cc3bd47f79ae5a04cb8800315d00aad7197f2", size = 64837, upload-time = "2025-10-17T06:19:32.03Z" }, + { url = "https://files.pythonhosted.org/packages/3b/1b/85eddb6ac5b38496c4e35c20298aae627970c88c3c624a22ab33e84f16c7/crc32c-2.8-cp313-cp313t-win_amd64.whl", hash = "sha256:4bb18e4bd98fb266596523ffc6be9c5b2387b2fa4e505ec56ca36336f49cb639", size = 66574, upload-time = "2025-10-17T06:19:33.143Z" }, + { url = "https://files.pythonhosted.org/packages/aa/df/50e9079b532ff53dbfc0e66eed781374bd455af02ed5df8b56ad538de4ff/crc32c-2.8-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3a3b2e4bcf7b3ee333050e7d3ff38e2ba46ea205f1d73d8949b248aaffe937ac", size = 66399, upload-time = "2025-10-17T06:19:34.279Z" }, + { url = "https://files.pythonhosted.org/packages/5a/2e/67e3b0bc3d30e46ea5d16365cc81203286387671e22f2307eb41f19abb9c/crc32c-2.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:445e559e66dff16be54f8a4ef95aa6b01db799a639956d995c5498ba513fccc2", size = 63044, upload-time = "2025-10-17T06:19:35.062Z" }, + { url = "https://files.pythonhosted.org/packages/36/ea/1723b17437e4344ed8d067456382ecb1f5b535d83fdc5aaebab676c6d273/crc32c-2.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bf3040919e17afa5782e01b1875d6a05f44b8f19c05f211d8b9f8a1deb8bbd9c", size = 61541, upload-time = "2025-10-17T06:19:36.204Z" }, + { url = "https://files.pythonhosted.org/packages/4c/6a/cbec8a235c5b46a01f319939b538958662159aec0ed3a74944e3a6de21f1/crc32c-2.8-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5607ab8221e1ffd411f64aa40dbb6850cf06dd2908c9debd05d371e1acf62ff3", size = 80139, upload-time = "2025-10-17T06:19:37.351Z" }, + { url = "https://files.pythonhosted.org/packages/21/31/d096722fe74b692d6e8206c27da1ea5f6b2a12ff92c54a62a6ba2f376254/crc32c-2.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f5db4f16816926986d3c94253314920689706ae13a9bf4888b47336c6735ce", size = 81736, upload-time = "2025-10-17T06:19:38.16Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a2/f75ef716ff7e3c22f385ba6ef30c5de80c19a21ebe699dc90824a1903275/crc32c-2.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70b0153c4d418b673309d3529334d117e1074c4a3b2d7f676e430d72c14de67b", size = 80795, upload-time = "2025-10-17T06:19:38.948Z" }, + { url = "https://files.pythonhosted.org/packages/d8/94/6d647a12d96ab087d9b8eacee3da073f981987827d57c7072f89ffc7b6cd/crc32c-2.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c8933531442042438753755a5c8a9034e4d88b01da9eb796f7e151b31a7256c", size = 80042, upload-time = "2025-10-17T06:19:39.725Z" }, + { url = "https://files.pythonhosted.org/packages/cd/dc/32b8896b40a0afee7a3c040536d0da5a73e68df2be9fadd21770fd158e16/crc32c-2.8-cp314-cp314-win32.whl", hash = "sha256:cdc83a3fe6c4e5df9457294cfd643de7d95bd4e9382c1dd6ed1e0f0f9169172c", size = 64914, upload-time = "2025-10-17T06:19:40.527Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b4/4308b27d307e8ecaf8dd1dcc63bbb0e47ae1826d93faa3e62d1ee00ee2d5/crc32c-2.8-cp314-cp314-win_amd64.whl", hash = "sha256:509e10035106df66770fe24b9eb8d9e32b6fb967df17744402fb67772d8b2bc7", size = 66723, upload-time = "2025-10-17T06:19:42.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/d5/a19d2489fa997a143bfbbf971a5c9a43f8b1ba9e775b1fb362d8fb15260c/crc32c-2.8-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:864359a39777a07b09b28eb31337c0cc603d5c1bf0fc328c3af736a8da624ec0", size = 66201, upload-time = "2025-10-17T06:19:43.273Z" }, + { url = "https://files.pythonhosted.org/packages/98/c2/5f82f22d2c1242cb6f6fe92aa9a42991ebea86de994b8f9974d9c1d128e2/crc32c-2.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:14511d7cfc5d9f5e1a6c6b64caa6225c2bdc1ed00d725e9a374a3e84073ce180", size = 62956, upload-time = "2025-10-17T06:19:44.099Z" }, + { url = "https://files.pythonhosted.org/packages/9b/61/3d43d33489cf974fb78bfb3500845770e139ae6d1d83473b660bd8f79a6c/crc32c-2.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:918b7999b52b5dcbcea34081e9a02d46917d571921a3f209956a9a429b2e06e5", size = 61443, upload-time = "2025-10-17T06:19:44.89Z" }, + { url = "https://files.pythonhosted.org/packages/52/6d/f306ce64a352a3002f76b0fc88a1373f4541f9d34fad3668688610bab14b/crc32c-2.8-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cc445da03fc012a5a03b71da1df1b40139729e6a5571fd4215ab40bfb39689c7", size = 79106, upload-time = "2025-10-17T06:19:45.688Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b7/1f74965dd7ea762954a69d172dfb3a706049c84ffa45d31401d010a4a126/crc32c-2.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e3dde2ec59a8a830511d72a086ead95c0b0b7f0d418f93ea106244c5e77e350", size = 80983, upload-time = "2025-10-17T06:19:46.792Z" }, + { url = "https://files.pythonhosted.org/packages/1b/50/af93f0d91ccd61833ce77374ebfbd16f5805f5c17d18c6470976d9866d76/crc32c-2.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:61d51681a08b6a2a2e771b7f0cd1947fb87cb28f38ed55a01cb7c40b2ac4cdd8", size = 80009, upload-time = "2025-10-17T06:19:47.619Z" }, + { url = "https://files.pythonhosted.org/packages/ee/fa/94f394beb68a88258af694dab2f1284f55a406b615d7900bdd6235283bc4/crc32c-2.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:67c0716c3b1a02d5235be649487b637eed21f2d070f2b3f63f709dcd2fefb4c7", size = 79066, upload-time = "2025-10-17T06:19:48.409Z" }, + { url = "https://files.pythonhosted.org/packages/91/c6/a6050e0c64fd73c67a97da96cb59f08b05111e00b958fb87ecdce99f17ac/crc32c-2.8-cp314-cp314t-win32.whl", hash = "sha256:2e8fe863fbbd8bdb6b414a2090f1b0f52106e76e9a9c96a413495dbe5ebe492a", size = 64869, upload-time = "2025-10-17T06:19:49.197Z" }, + { url = "https://files.pythonhosted.org/packages/08/1f/c7735034e401cb1ea14f996a224518e3a3fa9987cb13680e707328a7d779/crc32c-2.8-cp314-cp314t-win_amd64.whl", hash = "sha256:20a9cfb897693eb6da19e52e2a7be2026fd4d9fc8ae318f086c0d71d5dd2d8e0", size = 66633, upload-time = "2025-10-17T06:19:50.003Z" }, + { url = "https://files.pythonhosted.org/packages/a7/1d/dd926c68eb8aac8b142a1a10b8eb62d95212c1cf81775644373fe7cceac2/crc32c-2.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5833f4071da7ea182c514ba17d1eee8aec3c5be927d798222fbfbbd0f5eea02c", size = 62345, upload-time = "2025-10-17T06:20:09.39Z" }, + { url = "https://files.pythonhosted.org/packages/51/be/803404e5abea2ef2c15042edca04bbb7f625044cca879e47f186b43887c2/crc32c-2.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1dc4da036126ac07b39dd9d03e93e585ec615a2ad28ff12757aef7de175295a8", size = 61229, upload-time = "2025-10-17T06:20:10.236Z" }, + { url = "https://files.pythonhosted.org/packages/fc/3a/00cc578cd27ed0b22c9be25cef2c24539d92df9fa80ebd67a3fc5419724c/crc32c-2.8-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:15905fa78344654e241371c47e6ed2411f9eeb2b8095311c68c88eccf541e8b4", size = 64108, upload-time = "2025-10-17T06:20:11.072Z" }, + { url = "https://files.pythonhosted.org/packages/6b/bc/0587ef99a1c7629f95dd0c9d4f3d894de383a0df85831eb16c48a6afdae4/crc32c-2.8-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c596f918688821f796434e89b431b1698396c38bf0b56de873621528fe3ecb1e", size = 64815, upload-time = "2025-10-17T06:20:11.919Z" }, + { url = "https://files.pythonhosted.org/packages/73/42/94f2b8b92eae9064fcfb8deef2b971514065bd606231f8857ff8ae02bebd/crc32c-2.8-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8d23c4fe01b3844cb6e091044bc1cebdef7d16472e058ce12d9fadf10d2614af", size = 66659, upload-time = "2025-10-17T06:20:12.766Z" }, +] + +[[package]] +name = "crcmod" +version = "1.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/b0/e595ce2a2527e169c3bcd6c33d2473c1918e0b7f6826a043ca1245dd4e5b/crcmod-1.7.tar.gz", hash = "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e", size = 89670, upload-time = "2010-06-27T14:35:29.538Z" } + [[package]] name = "cryptography" version = "45.0.7" @@ -733,6 +1052,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/3e/de39e18e14d07882fcff028227c2dbe7fa202f09413127d4de32b03e0884/dapr-1.16.0-py3-none-any.whl", hash = "sha256:076dd559a0b450eae24b1c2ae779c9299ed3e06a05c1f72719a6613af8d19ced", size = 166710, upload-time = "2025-09-17T10:59:55.473Z" }, ] +[[package]] +name = "darabonba-core" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "alibabacloud-tea" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/d9/076bfec3ef28b39d04a9c5e48ef56f20f1a62ed9f684f857a7c503b362b6/darabonba_core-1.0.7.tar.gz", hash = "sha256:c2de2ee260682b4c08c9ec67793de66a2bdf316363b9165f152b9acaa16b4dc3", size = 21705, upload-time = "2026-06-22T12:41:36.562Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/01/34073c5e1742db3399d5837b44ac237e1001f092224a1a2addf30c5f9aaa/darabonba_core-1.0.7-py3-none-any.whl", hash = "sha256:d83ca9253f4bfb0ee8ac1075e23a3480fbd1133f5f96ec08f0e25d1612e299bd", size = 24852, upload-time = "2026-06-22T12:41:35.629Z" }, +] + [[package]] name = "daytona" version = "0.155.0" @@ -1072,6 +1405,14 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, ] +[[package]] +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" }, +] + [[package]] name = "frozenlist" version = "1.7.0" @@ -1175,6 +1516,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2f/e0/014d5d9d7a4564cf1c40b5039bc882db69fd881111e03ab3657ac0b218e2/fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21", size = 199597, upload-time = "2025-07-15T16:05:19.529Z" }, ] +[[package]] +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" } +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" }, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -1440,6 +1790,11 @@ 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" }, ] +[package.optional-dependencies] +http2 = [ + { name = "h2" }, +] + [[package]] name = "httpx-sse" version = "0.4.1" @@ -1609,11 +1964,11 @@ wheels = [ [[package]] name = "jmespath" -version = "1.1.0" +version = "0.10.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/3c/56/3f325b1eef9791759784aa5046a8f6a1aff8f7c898a2e34506771d3b99d8/jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9", size = 21607, upload-time = "2020-05-12T22:03:47.267Z" } 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/07/cb/5f001272b6faeb23c1c9e0acc04d48eaaf5c862c17709d20e3469c6e0139/jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f", size = 24489, upload-time = "2020-05-12T22:03:45.643Z" }, ] [[package]] @@ -2142,6 +2497,35 @@ 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" }, ] +[[package]] +name = "mysql-connector-python" +version = "9.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/5e/55b265cb95938e271208e5692d7e615c53f2aeea894ab72a9f14ab198e9a/mysql-connector-python-9.3.0.tar.gz", hash = "sha256:8b16d51447e3603f18478fb5a19b333bfb73fb58f872eb055a105635f53d2345", size = 942579, upload-time = "2025-05-07T18:50:34.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/f8/b36f551601a4b942e2014f80a0bfa5f2f0da30ef2710182cc96d875a5852/mysql_connector_python-9.3.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:f979e712187796ad57cd0bef76666dd48ed4887104775833c9489ea837144ad8", size = 15148231, upload-time = "2025-04-15T11:21:33.974Z" }, + { url = "https://files.pythonhosted.org/packages/41/ae/abd18c61277ec9e00c36de6a4f53f84003ae9fc34ca6077241a19e2c440f/mysql_connector_python-9.3.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:ee1a901c287471013570e29cdf5ca7159898af31cf3a582180eadd41c96b42c9", size = 15964353, upload-time = "2025-04-15T11:21:38.211Z" }, + { url = "https://files.pythonhosted.org/packages/0a/98/ce72b24c53327dbe0a2520f8a0828a18726bcb8e4f2012b274a4507bbed3/mysql_connector_python-9.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:5508ff6b79d8d46b15791401784a1b5abd10c8e05aec2684c4a50e92c5893cd2", size = 33449033, upload-time = "2025-04-15T11:21:42.704Z" }, + { url = "https://files.pythonhosted.org/packages/a2/5f/10a89734281ac9d74c7e3bc44f42dbf2105709435ea1bebfbc71e214af18/mysql_connector_python-9.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:d47a0d5b2b9b02f06647d5d7bbb19e237f234d6be91d0e0c935629faacf0797f", size = 33847325, upload-time = "2025-04-15T11:21:48.13Z" }, + { url = "https://files.pythonhosted.org/packages/58/53/a04fc2186f90fdd2a52d02856f15f2c3c894215799bdaeb313899e75a27b/mysql_connector_python-9.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:e24be22a5d96f3535afa5dd331166b02bf72655ea6ed6a2a0eb548c313548788", size = 16359157, upload-time = "2025-04-15T11:21:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/65/59/fa9bef2d9a7eafdc5629b82916e4e1e29446c9bbb0b33706988bbf541b18/mysql_connector_python-9.3.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e8b0131006608e533b8eab20078f9e65486068c984ed3efd28413d350d241f44", size = 15148256, upload-time = "2025-04-15T11:21:55.05Z" }, + { url = "https://files.pythonhosted.org/packages/14/ae/4ac81d7dc2ce8dff22fd63fa16d4562b113ef0458b04bd958675da3adc74/mysql_connector_python-9.3.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb72fcda90b616f0b2d3dae257441e06e8896b2780c3dddc6a65275ec1408d9a", size = 15964339, upload-time = "2025-04-15T11:21:58.275Z" }, + { url = "https://files.pythonhosted.org/packages/88/f4/088022373f0b71aae6f3190278423fce1fe0c31ecbddf33eb5c0cbf87c4d/mysql_connector_python-9.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9cc8d3c2f45d16b064b0063db857f8a7187b8659253dd32e3f19df1bf1d55ea0", size = 33456359, upload-time = "2025-04-15T11:22:02.594Z" }, + { url = "https://files.pythonhosted.org/packages/b9/38/96a602ad402fb71175d83bed3178bd8c16e04251d279e314e0bc53e0b861/mysql_connector_python-9.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:9c898c5f3e34314ed825f2ffdd52d674e03d59c45d02ac8083a8ec5173c1e0f8", size = 33852738, upload-time = "2025-04-15T11:22:07.685Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/63567fa4082aa22bad5cecaf16fe3604f026aea40b06d0bf2a9fd75212ff/mysql_connector_python-9.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:f10fe89397e8da81026d8143e17fc5c12ae5e66e51753a0f49e1db179c4f7113", size = 16358431, upload-time = "2025-04-15T11:22:12.263Z" }, + { url = "https://files.pythonhosted.org/packages/bf/73/b42061ea4c0500edad4f92834ed7d75b1a740d11970e531c5be4dc1af5cd/mysql_connector_python-9.3.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2589af070babdff9c920ee37f929218d80afa704f4e2a99f1ddcb13d19de4450", size = 15151288, upload-time = "2025-04-15T18:43:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/27/87/9cd7e803c762c5098683c83837d2258c2f83cf82d33fabd1d0eaadae06ee/mysql_connector_python-9.3.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:1916256ecd039f4673715550d28138416bac5962335e06d36f7434c47feb5232", size = 15967397, upload-time = "2025-04-15T18:43:20.799Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5d/cd63f31bf5d0536ee1e4216fb2f3f57175ca1e0dd37e1e8139083d2156e8/mysql_connector_python-9.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d33e2f88e1d4b15844cfed2bb6e90612525ba2c1af2fb10b4a25b2c89a1fe49a", size = 33457025, upload-time = "2025-04-15T18:43:24.09Z" }, + { url = "https://files.pythonhosted.org/packages/76/65/9609a96edc0d015d1017176974c42b955cf87ba92cd31765f99cba835715/mysql_connector_python-9.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0aedee809e1f8dbab6b2732f51ee1619b54a56d15b9070655bc31fb822c1a015", size = 33853427, upload-time = "2025-04-15T18:43:28.441Z" }, + { url = "https://files.pythonhosted.org/packages/c2/da/f81eeb5b63dea3ebe035fbbbdc036ae517155ad73f2e9640ee7c9eace09d/mysql_connector_python-9.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:3853799f4b719357ea25eba05f5f278a158a85a5c8209b3d058947a948bc9262", size = 16358560, upload-time = "2025-04-15T18:43:32.281Z" }, + { url = "https://files.pythonhosted.org/packages/6a/16/5762061505a0d0d3a333613b6f5d7b8eb3222a689aa32f71ed15f1532ad1/mysql_connector_python-9.3.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9516a4cdbaee3c9200f0e7d9aafb31057692f45c202cdcb43a3f9b37c94e7c84", size = 15151425, upload-time = "2025-04-15T18:43:35.573Z" }, + { url = "https://files.pythonhosted.org/packages/db/40/22de86e966e648ea0e3e438ad523c86d0cf4866b3841e248726fb4afded8/mysql_connector_python-9.3.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:495798dd34445d749991fb3a2aa87b4205100676939556d8d4aab5d5558e7a1f", size = 15967663, upload-time = "2025-04-15T18:43:38.248Z" }, + { url = "https://files.pythonhosted.org/packages/4c/19/36983937347b6a58af546950c88a9403cdce944893850e80ffb7f602a099/mysql_connector_python-9.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:be0ef15f6023ae2037347498f005a4471f694f8a6b8384c3194895e153120286", size = 33457288, upload-time = "2025-04-15T18:43:41.901Z" }, + { url = "https://files.pythonhosted.org/packages/18/12/7ccbc678a130df0f751596b37eddb98b2e40930d0ebc9ee41965ffbf0b92/mysql_connector_python-9.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4364d3a37c449f1c0bb9e52fd4eddc620126b9897b6b9f2fd1b3f33dacc16356", size = 33853838, upload-time = "2025-04-15T18:43:45.505Z" }, + { url = "https://files.pythonhosted.org/packages/c2/5e/c361caa024ce14ffc1f5b153d90f0febf5e9483a60c4b5c84e1e012363cc/mysql_connector_python-9.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:2a5de57814217077a8672063167b616b1034a37b614b93abcb602cc0b8c6fade", size = 16358561, upload-time = "2025-04-15T18:43:49.176Z" }, + { url = "https://files.pythonhosted.org/packages/23/1d/8c2c6672094b538f4881f7714e5332fdcddd05a7e196cbc9eb4a9b5e9a45/mysql_connector_python-9.3.0-py2.py3-none-any.whl", hash = "sha256:8ab7719d614cf5463521082fab86afc21ada504b538166090e00eeaa1ff729bc", size = 399302, upload-time = "2025-04-15T18:44:10.046Z" }, +] + [[package]] name = "nexus-rpc" version = "1.4.0" @@ -2234,7 +2618,8 @@ version = "2.3.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", - "python_full_version >= '3.12' and python_full_version < '3.14'", + "python_full_version == '3.13.*'", + "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } @@ -2435,6 +2820,9 @@ dependencies = [ ] [package.optional-dependencies] +aliyun = [ + { name = "agentrun-sdk" }, +] any-llm = [ { name = "any-llm-sdk", marker = "python_full_version >= '3.11'" }, ] @@ -2539,6 +2927,7 @@ dev = [ [package.metadata] requires-dist = [ + { name = "agentrun-sdk", marker = "extra == 'aliyun'", specifier = "==0.0.35" }, { name = "aiohttp", marker = "extra == 'blaxel'", specifier = ">=3.12,<4" }, { name = "aiohttp", marker = "extra == 'cloudflare'", specifier = ">=3.12,<4" }, { name = "any-llm-sdk", marker = "python_full_version >= '3.11' and extra == 'any-llm'", specifier = ">=1.11.0,<2" }, @@ -2573,7 +2962,7 @@ requires-dist = [ { name = "websockets", marker = "extra == 'realtime'", specifier = ">=15.0,<17" }, { name = "websockets", marker = "extra == 'voice'", specifier = ">=15.0,<17" }, ] -provides-extras = ["voice", "viz", "litellm", "any-llm", "realtime", "sqlalchemy", "encrypt", "redis", "dapr", "mongodb", "docker", "blaxel", "daytona", "cloudflare", "e2b", "modal", "runloop", "vercel", "s3", "temporal"] +provides-extras = ["voice", "viz", "litellm", "any-llm", "realtime", "sqlalchemy", "encrypt", "redis", "dapr", "mongodb", "docker", "aliyun", "blaxel", "daytona", "cloudflare", "e2b", "modal", "runloop", "vercel", "s3", "temporal"] [package.metadata.requires-dev] dev = [ @@ -2744,6 +3133,20 @@ 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" }, ] +[[package]] +name = "oss2" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aliyun-python-sdk-core" }, + { name = "aliyun-python-sdk-kms" }, + { name = "crcmod" }, + { name = "pycryptodome" }, + { name = "requests" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/b5/f2cb1950dda46ac2284d6c950489fdacd0e743c2d79a347924d3cc44b86f/oss2-2.19.1.tar.gz", hash = "sha256:a8ab9ee7eb99e88a7e1382edc6ea641d219d585a7e074e3776e9dec9473e59c1", size = 298845, upload-time = "2024-10-25T11:37:46.638Z" } + [[package]] name = "packaging" version = "25.0" @@ -2807,6 +3210,33 @@ 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" }, ] +[[package]] +name = "portalocker" +version = "3.2.0" +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" } +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" }, +] + +[[package]] +name = "posthog" +version = "7.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backoff" }, + { name = "distro" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/14/edebfb265f7141f4681b7f2d1cd8dea2621010a9ad7d3819831db2037f13/posthog-7.21.0.tar.gz", hash = "sha256:182ab7572748ae5199cb72c281a3549b70920cb8071e037ff6e07acbfae80876", size = 308788, upload-time = "2026-06-26T15:57:01.727Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/07/ca761fd0e3b23e9bfeaaeeffa5df4c235fd0c5086d89934553583e6902f3/posthog-7.21.0-py3-none-any.whl", hash = "sha256:11dca1e9772bedcb6721751fb0945fa8dcddbf9a4e72e2430483b53cdb8d1ec0", size = 370932, upload-time = "2026-06-26T15:56:59.844Z" }, +] + [[package]] name = "propcache" version = "0.3.2" @@ -2919,9 +3349,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, ] +[[package]] +name = "pycryptodome" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" }, + { url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" }, + { url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" }, + { url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" }, + { url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" }, + { url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" }, + { url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" }, + { url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" }, + { url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" }, + { url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" }, + { url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" }, + { url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" }, + { url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379", size = 1623886, upload-time = "2025-05-17T17:21:20.614Z" }, + { url = "https://files.pythonhosted.org/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4", size = 1672151, upload-time = "2025-05-17T17:21:22.666Z" }, + { url = "https://files.pythonhosted.org/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630", size = 1664461, upload-time = "2025-05-17T17:21:25.225Z" }, + { url = "https://files.pythonhosted.org/packages/d6/92/608fbdad566ebe499297a86aae5f2a5263818ceeecd16733006f1600403c/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353", size = 1702440, upload-time = "2025-05-17T17:21:27.991Z" }, + { url = "https://files.pythonhosted.org/packages/d1/92/2eadd1341abd2989cce2e2740b4423608ee2014acb8110438244ee97d7ff/pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5", size = 1803005, upload-time = "2025-05-17T17:21:31.37Z" }, +] + [[package]] name = "pydantic" -version = "2.12.3" +version = "2.13.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -2929,123 +3394,125 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, ] [[package]] name = "pydantic-core" -version = "2.41.4" +version = "2.46.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/18/d0944e8eaaa3efd0a91b0f1fc537d3be55ad35091b6a87638211ba691964/pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5", size = 457557, upload-time = "2025-10-14T10:23:47.909Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/3d/9b8ca77b0f76fcdbf8bc6b72474e264283f461284ca84ac3fde570c6c49a/pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e", size = 2111197, upload-time = "2025-10-14T10:19:43.303Z" }, - { url = "https://files.pythonhosted.org/packages/59/92/b7b0fe6ed4781642232755cb7e56a86e2041e1292f16d9ae410a0ccee5ac/pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b", size = 1917909, upload-time = "2025-10-14T10:19:45.194Z" }, - { url = "https://files.pythonhosted.org/packages/52/8c/3eb872009274ffa4fb6a9585114e161aa1a0915af2896e2d441642929fe4/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd", size = 1969905, upload-time = "2025-10-14T10:19:46.567Z" }, - { url = "https://files.pythonhosted.org/packages/f4/21/35adf4a753bcfaea22d925214a0c5b880792e3244731b3f3e6fec0d124f7/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945", size = 2051938, upload-time = "2025-10-14T10:19:48.237Z" }, - { url = "https://files.pythonhosted.org/packages/7d/d0/cdf7d126825e36d6e3f1eccf257da8954452934ede275a8f390eac775e89/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706", size = 2250710, upload-time = "2025-10-14T10:19:49.619Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1c/af1e6fd5ea596327308f9c8d1654e1285cc3d8de0d584a3c9d7705bf8a7c/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba", size = 2367445, upload-time = "2025-10-14T10:19:51.269Z" }, - { url = "https://files.pythonhosted.org/packages/d3/81/8cece29a6ef1b3a92f956ea6da6250d5b2d2e7e4d513dd3b4f0c7a83dfea/pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b", size = 2072875, upload-time = "2025-10-14T10:19:52.671Z" }, - { url = "https://files.pythonhosted.org/packages/e3/37/a6a579f5fc2cd4d5521284a0ab6a426cc6463a7b3897aeb95b12f1ba607b/pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d", size = 2191329, upload-time = "2025-10-14T10:19:54.214Z" }, - { url = "https://files.pythonhosted.org/packages/ae/03/505020dc5c54ec75ecba9f41119fd1e48f9e41e4629942494c4a8734ded1/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700", size = 2151658, upload-time = "2025-10-14T10:19:55.843Z" }, - { url = "https://files.pythonhosted.org/packages/cb/5d/2c0d09fb53aa03bbd2a214d89ebfa6304be7df9ed86ee3dc7770257f41ee/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6", size = 2316777, upload-time = "2025-10-14T10:19:57.607Z" }, - { url = "https://files.pythonhosted.org/packages/ea/4b/c2c9c8f5e1f9c864b57d08539d9d3db160e00491c9f5ee90e1bfd905e644/pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9", size = 2320705, upload-time = "2025-10-14T10:19:59.016Z" }, - { url = "https://files.pythonhosted.org/packages/28/c3/a74c1c37f49c0a02c89c7340fafc0ba816b29bd495d1a31ce1bdeacc6085/pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57", size = 1975464, upload-time = "2025-10-14T10:20:00.581Z" }, - { url = "https://files.pythonhosted.org/packages/d6/23/5dd5c1324ba80303368f7569e2e2e1a721c7d9eb16acb7eb7b7f85cb1be2/pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc", size = 2024497, upload-time = "2025-10-14T10:20:03.018Z" }, - { url = "https://files.pythonhosted.org/packages/62/4c/f6cbfa1e8efacd00b846764e8484fe173d25b8dab881e277a619177f3384/pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80", size = 2109062, upload-time = "2025-10-14T10:20:04.486Z" }, - { url = "https://files.pythonhosted.org/packages/21/f8/40b72d3868896bfcd410e1bd7e516e762d326201c48e5b4a06446f6cf9e8/pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae", size = 1916301, upload-time = "2025-10-14T10:20:06.857Z" }, - { url = "https://files.pythonhosted.org/packages/94/4d/d203dce8bee7faeca791671c88519969d98d3b4e8f225da5b96dad226fc8/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827", size = 1968728, upload-time = "2025-10-14T10:20:08.353Z" }, - { url = "https://files.pythonhosted.org/packages/65/f5/6a66187775df87c24d526985b3a5d78d861580ca466fbd9d4d0e792fcf6c/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f", size = 2050238, upload-time = "2025-10-14T10:20:09.766Z" }, - { url = "https://files.pythonhosted.org/packages/5e/b9/78336345de97298cf53236b2f271912ce11f32c1e59de25a374ce12f9cce/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def", size = 2249424, upload-time = "2025-10-14T10:20:11.732Z" }, - { url = "https://files.pythonhosted.org/packages/99/bb/a4584888b70ee594c3d374a71af5075a68654d6c780369df269118af7402/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2", size = 2366047, upload-time = "2025-10-14T10:20:13.647Z" }, - { url = "https://files.pythonhosted.org/packages/5f/8d/17fc5de9d6418e4d2ae8c675f905cdafdc59d3bf3bf9c946b7ab796a992a/pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8", size = 2071163, upload-time = "2025-10-14T10:20:15.307Z" }, - { url = "https://files.pythonhosted.org/packages/54/e7/03d2c5c0b8ed37a4617430db68ec5e7dbba66358b629cd69e11b4d564367/pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265", size = 2190585, upload-time = "2025-10-14T10:20:17.3Z" }, - { url = "https://files.pythonhosted.org/packages/be/fc/15d1c9fe5ad9266a5897d9b932b7f53d7e5cfc800573917a2c5d6eea56ec/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c", size = 2150109, upload-time = "2025-10-14T10:20:19.143Z" }, - { url = "https://files.pythonhosted.org/packages/26/ef/e735dd008808226c83ba56972566138665b71477ad580fa5a21f0851df48/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a", size = 2315078, upload-time = "2025-10-14T10:20:20.742Z" }, - { url = "https://files.pythonhosted.org/packages/90/00/806efdcf35ff2ac0f938362350cd9827b8afb116cc814b6b75cf23738c7c/pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e", size = 2318737, upload-time = "2025-10-14T10:20:22.306Z" }, - { url = "https://files.pythonhosted.org/packages/41/7e/6ac90673fe6cb36621a2283552897838c020db343fa86e513d3f563b196f/pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03", size = 1974160, upload-time = "2025-10-14T10:20:23.817Z" }, - { url = "https://files.pythonhosted.org/packages/e0/9d/7c5e24ee585c1f8b6356e1d11d40ab807ffde44d2db3b7dfd6d20b09720e/pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e", size = 2021883, upload-time = "2025-10-14T10:20:25.48Z" }, - { url = "https://files.pythonhosted.org/packages/33/90/5c172357460fc28b2871eb4a0fb3843b136b429c6fa827e4b588877bf115/pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db", size = 1968026, upload-time = "2025-10-14T10:20:27.039Z" }, - { url = "https://files.pythonhosted.org/packages/e9/81/d3b3e95929c4369d30b2a66a91db63c8ed0a98381ae55a45da2cd1cc1288/pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887", size = 2099043, upload-time = "2025-10-14T10:20:28.561Z" }, - { url = "https://files.pythonhosted.org/packages/58/da/46fdac49e6717e3a94fc9201403e08d9d61aa7a770fab6190b8740749047/pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2", size = 1910699, upload-time = "2025-10-14T10:20:30.217Z" }, - { url = "https://files.pythonhosted.org/packages/1e/63/4d948f1b9dd8e991a5a98b77dd66c74641f5f2e5225fee37994b2e07d391/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999", size = 1952121, upload-time = "2025-10-14T10:20:32.246Z" }, - { url = "https://files.pythonhosted.org/packages/b2/a7/e5fc60a6f781fc634ecaa9ecc3c20171d238794cef69ae0af79ac11b89d7/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4", size = 2041590, upload-time = "2025-10-14T10:20:34.332Z" }, - { url = "https://files.pythonhosted.org/packages/70/69/dce747b1d21d59e85af433428978a1893c6f8a7068fa2bb4a927fba7a5ff/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f", size = 2219869, upload-time = "2025-10-14T10:20:35.965Z" }, - { url = "https://files.pythonhosted.org/packages/83/6a/c070e30e295403bf29c4df1cb781317b6a9bac7cd07b8d3acc94d501a63c/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b", size = 2345169, upload-time = "2025-10-14T10:20:37.627Z" }, - { url = "https://files.pythonhosted.org/packages/f0/83/06d001f8043c336baea7fd202a9ac7ad71f87e1c55d8112c50b745c40324/pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47", size = 2070165, upload-time = "2025-10-14T10:20:39.246Z" }, - { url = "https://files.pythonhosted.org/packages/14/0a/e567c2883588dd12bcbc110232d892cf385356f7c8a9910311ac997ab715/pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970", size = 2189067, upload-time = "2025-10-14T10:20:41.015Z" }, - { url = "https://files.pythonhosted.org/packages/f4/1d/3d9fca34273ba03c9b1c5289f7618bc4bd09c3ad2289b5420481aa051a99/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed", size = 2132997, upload-time = "2025-10-14T10:20:43.106Z" }, - { url = "https://files.pythonhosted.org/packages/52/70/d702ef7a6cd41a8afc61f3554922b3ed8d19dd54c3bd4bdbfe332e610827/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8", size = 2307187, upload-time = "2025-10-14T10:20:44.849Z" }, - { url = "https://files.pythonhosted.org/packages/68/4c/c06be6e27545d08b802127914156f38d10ca287a9e8489342793de8aae3c/pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431", size = 2305204, upload-time = "2025-10-14T10:20:46.781Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e5/35ae4919bcd9f18603419e23c5eaf32750224a89d41a8df1a3704b69f77e/pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd", size = 1972536, upload-time = "2025-10-14T10:20:48.39Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c2/49c5bb6d2a49eb2ee3647a93e3dae7080c6409a8a7558b075027644e879c/pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff", size = 2031132, upload-time = "2025-10-14T10:20:50.421Z" }, - { url = "https://files.pythonhosted.org/packages/06/23/936343dbcba6eec93f73e95eb346810fc732f71ba27967b287b66f7b7097/pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8", size = 1969483, upload-time = "2025-10-14T10:20:52.35Z" }, - { url = "https://files.pythonhosted.org/packages/13/d0/c20adabd181a029a970738dfe23710b52a31f1258f591874fcdec7359845/pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746", size = 2105688, upload-time = "2025-10-14T10:20:54.448Z" }, - { url = "https://files.pythonhosted.org/packages/00/b6/0ce5c03cec5ae94cca220dfecddc453c077d71363b98a4bbdb3c0b22c783/pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced", size = 1910807, upload-time = "2025-10-14T10:20:56.115Z" }, - { url = "https://files.pythonhosted.org/packages/68/3e/800d3d02c8beb0b5c069c870cbb83799d085debf43499c897bb4b4aaff0d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a", size = 1956669, upload-time = "2025-10-14T10:20:57.874Z" }, - { url = "https://files.pythonhosted.org/packages/60/a4/24271cc71a17f64589be49ab8bd0751f6a0a03046c690df60989f2f95c2c/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02", size = 2051629, upload-time = "2025-10-14T10:21:00.006Z" }, - { url = "https://files.pythonhosted.org/packages/68/de/45af3ca2f175d91b96bfb62e1f2d2f1f9f3b14a734afe0bfeff079f78181/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1", size = 2224049, upload-time = "2025-10-14T10:21:01.801Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/ae4e1ff84672bf869d0a77af24fd78387850e9497753c432875066b5d622/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2", size = 2342409, upload-time = "2025-10-14T10:21:03.556Z" }, - { url = "https://files.pythonhosted.org/packages/18/62/273dd70b0026a085c7b74b000394e1ef95719ea579c76ea2f0cc8893736d/pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84", size = 2069635, upload-time = "2025-10-14T10:21:05.385Z" }, - { url = "https://files.pythonhosted.org/packages/30/03/cf485fff699b4cdaea469bc481719d3e49f023241b4abb656f8d422189fc/pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d", size = 2194284, upload-time = "2025-10-14T10:21:07.122Z" }, - { url = "https://files.pythonhosted.org/packages/f9/7e/c8e713db32405dfd97211f2fc0a15d6bf8adb7640f3d18544c1f39526619/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d", size = 2137566, upload-time = "2025-10-14T10:21:08.981Z" }, - { url = "https://files.pythonhosted.org/packages/04/f7/db71fd4cdccc8b75990f79ccafbbd66757e19f6d5ee724a6252414483fb4/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2", size = 2316809, upload-time = "2025-10-14T10:21:10.805Z" }, - { url = "https://files.pythonhosted.org/packages/76/63/a54973ddb945f1bca56742b48b144d85c9fc22f819ddeb9f861c249d5464/pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab", size = 2311119, upload-time = "2025-10-14T10:21:12.583Z" }, - { url = "https://files.pythonhosted.org/packages/f8/03/5d12891e93c19218af74843a27e32b94922195ded2386f7b55382f904d2f/pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c", size = 1981398, upload-time = "2025-10-14T10:21:14.584Z" }, - { url = "https://files.pythonhosted.org/packages/be/d8/fd0de71f39db91135b7a26996160de71c073d8635edfce8b3c3681be0d6d/pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4", size = 2030735, upload-time = "2025-10-14T10:21:16.432Z" }, - { url = "https://files.pythonhosted.org/packages/72/86/c99921c1cf6650023c08bfab6fe2d7057a5142628ef7ccfa9921f2dda1d5/pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564", size = 1973209, upload-time = "2025-10-14T10:21:18.213Z" }, - { url = "https://files.pythonhosted.org/packages/36/0d/b5706cacb70a8414396efdda3d72ae0542e050b591119e458e2490baf035/pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4", size = 1877324, upload-time = "2025-10-14T10:21:20.363Z" }, - { url = "https://files.pythonhosted.org/packages/de/2d/cba1fa02cfdea72dfb3a9babb067c83b9dff0bbcb198368e000a6b756ea7/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2", size = 1884515, upload-time = "2025-10-14T10:21:22.339Z" }, - { url = "https://files.pythonhosted.org/packages/07/ea/3df927c4384ed9b503c9cc2d076cf983b4f2adb0c754578dfb1245c51e46/pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf", size = 2042819, upload-time = "2025-10-14T10:21:26.683Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ee/df8e871f07074250270a3b1b82aad4cd0026b588acd5d7d3eb2fcb1471a3/pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2", size = 1995866, upload-time = "2025-10-14T10:21:28.951Z" }, - { url = "https://files.pythonhosted.org/packages/fc/de/b20f4ab954d6d399499c33ec4fafc46d9551e11dc1858fb7f5dca0748ceb/pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89", size = 1970034, upload-time = "2025-10-14T10:21:30.869Z" }, - { url = "https://files.pythonhosted.org/packages/54/28/d3325da57d413b9819365546eb9a6e8b7cbd9373d9380efd5f74326143e6/pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1", size = 2102022, upload-time = "2025-10-14T10:21:32.809Z" }, - { url = "https://files.pythonhosted.org/packages/9e/24/b58a1bc0d834bf1acc4361e61233ee217169a42efbdc15a60296e13ce438/pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac", size = 1905495, upload-time = "2025-10-14T10:21:34.812Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a4/71f759cc41b7043e8ecdaab81b985a9b6cad7cec077e0b92cff8b71ecf6b/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554", size = 1956131, upload-time = "2025-10-14T10:21:36.924Z" }, - { url = "https://files.pythonhosted.org/packages/b0/64/1e79ac7aa51f1eec7c4cda8cbe456d5d09f05fdd68b32776d72168d54275/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e", size = 2052236, upload-time = "2025-10-14T10:21:38.927Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e3/a3ffc363bd4287b80f1d43dc1c28ba64831f8dfc237d6fec8f2661138d48/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616", size = 2223573, upload-time = "2025-10-14T10:21:41.574Z" }, - { url = "https://files.pythonhosted.org/packages/28/27/78814089b4d2e684a9088ede3790763c64693c3d1408ddc0a248bc789126/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af", size = 2342467, upload-time = "2025-10-14T10:21:44.018Z" }, - { url = "https://files.pythonhosted.org/packages/92/97/4de0e2a1159cb85ad737e03306717637842c88c7fd6d97973172fb183149/pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12", size = 2063754, upload-time = "2025-10-14T10:21:46.466Z" }, - { url = "https://files.pythonhosted.org/packages/0f/50/8cb90ce4b9efcf7ae78130afeb99fd1c86125ccdf9906ef64b9d42f37c25/pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d", size = 2196754, upload-time = "2025-10-14T10:21:48.486Z" }, - { url = "https://files.pythonhosted.org/packages/34/3b/ccdc77af9cd5082723574a1cc1bcae7a6acacc829d7c0a06201f7886a109/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad", size = 2137115, upload-time = "2025-10-14T10:21:50.63Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ba/e7c7a02651a8f7c52dc2cff2b64a30c313e3b57c7d93703cecea76c09b71/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a", size = 2317400, upload-time = "2025-10-14T10:21:52.959Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ba/6c533a4ee8aec6b812c643c49bb3bd88d3f01e3cebe451bb85512d37f00f/pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025", size = 2312070, upload-time = "2025-10-14T10:21:55.419Z" }, - { url = "https://files.pythonhosted.org/packages/22/ae/f10524fcc0ab8d7f96cf9a74c880243576fd3e72bd8ce4f81e43d22bcab7/pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e", size = 1982277, upload-time = "2025-10-14T10:21:57.474Z" }, - { url = "https://files.pythonhosted.org/packages/b4/dc/e5aa27aea1ad4638f0c3fb41132f7eb583bd7420ee63204e2d4333a3bbf9/pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894", size = 2024608, upload-time = "2025-10-14T10:21:59.557Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/51d89cc2612bd147198e120a13f150afbf0bcb4615cddb049ab10b81b79e/pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d", size = 1967614, upload-time = "2025-10-14T10:22:01.847Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c2/472f2e31b95eff099961fa050c376ab7156a81da194f9edb9f710f68787b/pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da", size = 1876904, upload-time = "2025-10-14T10:22:04.062Z" }, - { url = "https://files.pythonhosted.org/packages/4a/07/ea8eeb91173807ecdae4f4a5f4b150a520085b35454350fc219ba79e66a3/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e", size = 1882538, upload-time = "2025-10-14T10:22:06.39Z" }, - { url = "https://files.pythonhosted.org/packages/1e/29/b53a9ca6cd366bfc928823679c6a76c7a4c69f8201c0ba7903ad18ebae2f/pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa", size = 2041183, upload-time = "2025-10-14T10:22:08.812Z" }, - { url = "https://files.pythonhosted.org/packages/c7/3d/f8c1a371ceebcaf94d6dd2d77c6cf4b1c078e13a5837aee83f760b4f7cfd/pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d", size = 1993542, upload-time = "2025-10-14T10:22:11.332Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ac/9fc61b4f9d079482a290afe8d206b8f490e9fd32d4fc03ed4fc698214e01/pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0", size = 1973897, upload-time = "2025-10-14T10:22:13.444Z" }, - { url = "https://files.pythonhosted.org/packages/b0/12/5ba58daa7f453454464f92b3ca7b9d7c657d8641c48e370c3ebc9a82dd78/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b", size = 2122139, upload-time = "2025-10-14T10:22:47.288Z" }, - { url = "https://files.pythonhosted.org/packages/21/fb/6860126a77725c3108baecd10fd3d75fec25191d6381b6eb2ac660228eac/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42", size = 1936674, upload-time = "2025-10-14T10:22:49.555Z" }, - { url = "https://files.pythonhosted.org/packages/de/be/57dcaa3ed595d81f8757e2b44a38240ac5d37628bce25fb20d02c7018776/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee", size = 1956398, upload-time = "2025-10-14T10:22:52.19Z" }, - { url = "https://files.pythonhosted.org/packages/2f/1d/679a344fadb9695f1a6a294d739fbd21d71fa023286daeea8c0ed49e7c2b/pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c", size = 2138674, upload-time = "2025-10-14T10:22:54.499Z" }, - { url = "https://files.pythonhosted.org/packages/c4/48/ae937e5a831b7c0dc646b2ef788c27cd003894882415300ed21927c21efa/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537", size = 2112087, upload-time = "2025-10-14T10:22:56.818Z" }, - { url = "https://files.pythonhosted.org/packages/5e/db/6db8073e3d32dae017da7e0d16a9ecb897d0a4d92e00634916e486097961/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94", size = 1920387, upload-time = "2025-10-14T10:22:59.342Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c1/dd3542d072fcc336030d66834872f0328727e3b8de289c662faa04aa270e/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c", size = 1951495, upload-time = "2025-10-14T10:23:02.089Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c6/db8d13a1f8ab3f1eb08c88bd00fd62d44311e3456d1e85c0e59e0a0376e7/pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335", size = 2139008, upload-time = "2025-10-14T10:23:04.539Z" }, - { url = "https://files.pythonhosted.org/packages/5d/d4/912e976a2dd0b49f31c98a060ca90b353f3b73ee3ea2fd0030412f6ac5ec/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00", size = 2106739, upload-time = "2025-10-14T10:23:06.934Z" }, - { url = "https://files.pythonhosted.org/packages/71/f0/66ec5a626c81eba326072d6ee2b127f8c139543f1bf609b4842978d37833/pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9", size = 1932549, upload-time = "2025-10-14T10:23:09.24Z" }, - { url = "https://files.pythonhosted.org/packages/c4/af/625626278ca801ea0a658c2dcf290dc9f21bb383098e99e7c6a029fccfc0/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2", size = 2135093, upload-time = "2025-10-14T10:23:11.626Z" }, - { url = "https://files.pythonhosted.org/packages/20/f6/2fba049f54e0f4975fef66be654c597a1d005320fa141863699180c7697d/pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258", size = 2187971, upload-time = "2025-10-14T10:23:14.437Z" }, - { url = "https://files.pythonhosted.org/packages/0e/80/65ab839a2dfcd3b949202f9d920c34f9de5a537c3646662bdf2f7d999680/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347", size = 2147939, upload-time = "2025-10-14T10:23:16.831Z" }, - { url = "https://files.pythonhosted.org/packages/44/58/627565d3d182ce6dfda18b8e1c841eede3629d59c9d7cbc1e12a03aeb328/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa", size = 2311400, upload-time = "2025-10-14T10:23:19.234Z" }, - { url = "https://files.pythonhosted.org/packages/24/06/8a84711162ad5a5f19a88cead37cca81b4b1f294f46260ef7334ae4f24d3/pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a", size = 2316840, upload-time = "2025-10-14T10:23:21.738Z" }, - { url = "https://files.pythonhosted.org/packages/aa/8b/b7bb512a4682a2f7fbfae152a755d37351743900226d29bd953aaf870eaa/pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d", size = 2149135, upload-time = "2025-10-14T10:23:24.379Z" }, - { url = "https://files.pythonhosted.org/packages/7e/7d/138e902ed6399b866f7cfe4435d22445e16fff888a1c00560d9dc79a780f/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5", size = 2104721, upload-time = "2025-10-14T10:23:26.906Z" }, - { url = "https://files.pythonhosted.org/packages/47/13/0525623cf94627f7b53b4c2034c81edc8491cbfc7c28d5447fa318791479/pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2", size = 1931608, upload-time = "2025-10-14T10:23:29.306Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f9/744bc98137d6ef0a233f808bfc9b18cf94624bf30836a18d3b05d08bf418/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd", size = 2132986, upload-time = "2025-10-14T10:23:32.057Z" }, - { url = "https://files.pythonhosted.org/packages/17/c8/629e88920171173f6049386cc71f893dff03209a9ef32b4d2f7e7c264bcf/pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c", size = 2187516, upload-time = "2025-10-14T10:23:34.871Z" }, - { url = "https://files.pythonhosted.org/packages/2e/0f/4f2734688d98488782218ca61bcc118329bf5de05bb7fe3adc7dd79b0b86/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405", size = 2146146, upload-time = "2025-10-14T10:23:37.342Z" }, - { url = "https://files.pythonhosted.org/packages/ed/f2/ab385dbd94a052c62224b99cf99002eee99dbec40e10006c78575aead256/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8", size = 2311296, upload-time = "2025-10-14T10:23:40.145Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8e/e4f12afe1beeb9823bba5375f8f258df0cc61b056b0195fb1cf9f62a1a58/pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308", size = 2315386, upload-time = "2025-10-14T10:23:42.624Z" }, - { url = "https://files.pythonhosted.org/packages/48/f7/925f65d930802e3ea2eb4d5afa4cb8730c8dc0d2cb89a59dc4ed2fcb2d74/pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f", size = 2147775, upload-time = "2025-10-14T10:23:45.406Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, + { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, + { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, + { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, ] [[package]] @@ -3062,6 +3529,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, ] +[[package]] +name = "pydash" +version = "8.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/c1/1c55272f49d761cec38ddb80be9817935b9c91ebd6a8988e10f532868d56/pydash-8.0.6.tar.gz", hash = "sha256:b2821547e9723f69cf3a986be4db64de41730be149b2641947ecd12e1e11025a", size = 164338, upload-time = "2026-01-17T16:42:56.576Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/b7/cc5e7974699db40014d58c7dd7c4ad4ffc244d36930dc9ec7d06ee67d7a9/pydash-8.0.6-py3-none-any.whl", hash = "sha256:ee70a81a5b292c007f28f03a4ee8e75c1f5d7576df5457b836ec7ab2839cc5d0", size = 101561, upload-time = "2026-01-17T16:42:55.448Z" }, +] + [[package]] name = "pyee" version = "12.1.1" @@ -3373,11 +3852,11 @@ wheels = [ [[package]] name = "python-dotenv" -version = "1.1.1" +version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +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" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, + { 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" }, ] [[package]] @@ -3401,6 +3880,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398", size = 182185, upload-time = "2022-12-25T18:52:58.662Z" }, ] +[[package]] +name = "pytz" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861, upload-time = "2026-05-04T01:35:29.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141, upload-time = "2026-05-04T01:35:27.408Z" }, +] + [[package]] name = "pywin32" version = "311" @@ -3479,6 +3967,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, ] +[[package]] +name = "qdrant-client" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio" }, + { name = "httpx", extra = ["http2"] }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "portalocker" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/45/5b1bdd15a3c7730eefb9c113600829e20d689b82b5a23f9e07d107094004/qdrant_client-1.18.0.tar.gz", hash = "sha256:52e8ece1a7d40519801bf0b70713bfa0f6b7ae28c7275bbe0b0286fbed7f6db4", size = 352580, upload-time = "2026-05-11T14:12:38.702Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/10/c437bd2ac41ef30d3019063e6ce537dc111e9214473b337ee88f7fa6359a/qdrant_client-1.18.0-py3-none-any.whl", hash = "sha256:093aa8cf8a420ee3ad2a68b007e1378d7992b2600e0b53c193fc172674f659cd", size = 398126, upload-time = "2026-05-11T14:12:36.998Z" }, +] + [[package]] name = "redis" version = "7.0.1" @@ -3926,6 +4433,53 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/44/4f6ba4e2c171847e6f9a460213b196bbf26edea43d0e66889c7ccc55d368/synchronicity-0.12.2-py3-none-any.whl", hash = "sha256:9dbaca81fb7f2b57c6dea326e514e1c80e9ccfd9c9618515e84fa6091026273b", size = 41312, upload-time = "2026-04-06T15:06:14.459Z" }, ] +[[package]] +name = "tablestore" +version = "6.4.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "certifi" }, + { name = "crc32c" }, + { name = "flatbuffers" }, + { name = "future" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "protobuf" }, + { name = "six" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/1b/03bee654a922a818a12df211bbf31b724773082db356d964579e4b004f5b/tablestore-6.4.6.tar.gz", hash = "sha256:b0f3fd864380b0448c0dfc648dc7bf28e41c35afab8ddf6ee75cf43e480a1abc", size = 5082221, upload-time = "2026-04-28T11:25:34.886Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/24/6cd95c15123a6b2a17fbc013e78a4f6119df987a2004f8996768f060a353/tablestore-6.4.6-py3-none-any.whl", hash = "sha256:060f24cff09c4e2912e01adaf1be3b9a69d609f3f82e83f7c979136357e7e8fd", size = 5123750, upload-time = "2026-04-28T11:25:30.716Z" }, +] + +[[package]] +name = "tablestore-agent-storage" +version = "1.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oss2" }, + { name = "tablestore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/42/e94edd2ab55adbe8f986906a7005e1944ce7fd891a66e5b23e0a9f67d6d4/tablestore_agent_storage-1.0.6.tar.gz", hash = "sha256:bbf5458ff8d249aa20845279846958605273a452c6eae88e15bcc86dda429a83", size = 20677, upload-time = "2026-06-09T12:14:29.22Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/51/8cc6b37837ee606bc71615bb95795815fbf5a6fed24acfabc65cb6d2dbda/tablestore_agent_storage-1.0.6-py3-none-any.whl", hash = "sha256:e8abf19679a6bde110fa77e01d37e25ee4aada3f64f7a50fcb3429cd0a79718b", size = 20054, upload-time = "2026-06-09T12:14:27.191Z" }, +] + +[[package]] +name = "tablestore-for-agent-memory" +version = "1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "tablestore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/7d/a92ba26d87fb2be64c26847bb34a9ff207d42b7546c05d5a7af79455fe62/tablestore_for_agent_memory-1.1.3.tar.gz", hash = "sha256:522841fed7fdd655f122c516220950fa65ce469e9bf80c05db68d04d2021dcf6", size = 22233, upload-time = "2026-03-25T12:47:07.351Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/40/ea6ec162dc91ac55b3fd8ef7bcd2e1f04a039c8bbfcfa3dd8f6c6cbf7ef8/tablestore_for_agent_memory-1.1.3-py3-none-any.whl", hash = "sha256:0384ba823f09a47786ea050be35bb2d0f8b7c6d95dba0b31c04fbbac6e30622f", size = 33719, upload-time = "2026-03-25T12:47:06.353Z" }, +] + [[package]] name = "temporalio" version = "1.26.0" @@ -4163,11 +4717,11 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.14.1" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } +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" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, + { 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" }, ] [[package]] @@ -4182,6 +4736,27 @@ 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" }, ] +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, +] + +[[package]] +name = "tzlocal" +version = "5.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/55/15e2340963d2bfedcc6042da3911438fd336f8ae96b65bdbe3a29766da0c/tzlocal-5.4.3.tar.gz", hash = "sha256:3a8c9bc18cf47e1dcde252ea0e6a72a6cde320a400b6ac6db1f1f8cccd553c00", size = 30873, upload-time = "2026-06-17T04:17:41.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/28/fc144409c71569e928585f8f3c629d80d1ca3ef40175e9222f01588f98c9/tzlocal-5.4.3-py3-none-any.whl", hash = "sha256:24ce97bb58e2a973f7640ec2553ab4e6f6d5a0d0d1aa9dc43bca21d89e1feb82", size = 18039, upload-time = "2026-06-17T04:17:40.027Z" }, +] + [[package]] name = "uc-micro-py" version = "1.0.3"