diff --git a/.github/actions/setup-python-env/action.yml b/.github/actions/setup-python-env/action.yml index 7e8f6b3..206aa99 100644 --- a/.github/actions/setup-python-env/action.yml +++ b/.github/actions/setup-python-env/action.yml @@ -22,9 +22,9 @@ runs: uses: astral-sh/setup-uv@v6 with: version: ${{ inputs.uv-version }} - enable-cache: 'true' + enable-cache: "true" cache-suffix: ${{ matrix.python-version }} - name: Install Python dependencies - run: uv sync --frozen + run: uv sync --all-extras --all-groups --frozen shell: bash diff --git a/AGENTS.md b/AGENTS.md index 2c04e75..fdc7f48 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,19 +1,35 @@ # Repository Guidelines ## Project Structure & Module Organization -The package code lives under `src/acp`, exposing the high-level Agent, transport helpers, and generated protocol schema. Generated artifacts such as `schema/` and `src/acp/schema.py` are refreshed via `scripts/gen_all.py` against the upstream ACP schema. Integration examples are in `examples/`, including `echo_agent.py` and the mini SWE bridge. Tests reside in `tests/` with async fixtures and doctests; documentation sources live in `docs/` and publish via MkDocs. Built distributions drop into `dist/` after builds. +- `src/acp/`: runtime package exposing agent/client abstractions, transports, and the generated `schema.py`. +- `schema/`: upstream JSON schema sources; regenerate Python bindings with `make gen-all`. +- `examples/`: runnable scripts (`echo_agent.py`, `client.py`, `gemini.py`, etc.) demonstrating stdio orchestration patterns. +- `tests/`: pytest suite, including opt-in Gemini smoke checks under `tests/test_gemini_example.py`. +- `docs/`: MkDocs content powering the hosted documentation. ## Build, Test, and Development Commands -Run `make install` to create a `uv` managed virtualenv and install pre-commit hooks. `make check` executes lock verification, Ruff linting, `ty` static checks, and deptry analysis. `make test` calls `uv run python -m pytest --doctest-modules`. For release prep use `make build` or `make build-and-publish`. `make gen-all` regenerates protocol models; export `ACP_SCHEMA_VERSION=` beforehand to fetch a specific upstream schema (defaults to the cached copy). `make docs` serves MkDocs locally; `make docs-test` ensures clean builds. +- `make install` — provision the `uv` virtualenv and install pre-commit hooks. +- `make check` — run Ruff linting/formatting, type analysis, dependency hygiene, and lock verification. +- `make test` — execute `pytest` (with doctests) inside the managed environment. +- `make gen-all` — refresh protocol artifacts when the ACP schema version advances (`ACP_SCHEMA_VERSION=` to pin an upstream tag). ## Coding Style & Naming Conventions -Target Python 3.10+ with type hints and 120-character lines enforced by Ruff (`pyproject.toml`). Prefer dataclasses/pydantic models from the schema modules rather than bare dicts. Tests may ignore security lint (see per-file ignores) but still follow snake_case names. Keep public API modules under `acp/*` lean; place utilities in internal `_`-prefixed modules when needed. +- Target Python 3.10+ with four-space indentation and type hints on public APIs. +- Ruff enforces formatting and lint rules (`uv run ruff check`, `uv run ruff format`); keep both clean before publishing. +- Prefer dataclasses or generated Pydantic models from `acp.schema` over ad-hoc dicts. Place shared utilities in `_`-prefixed internal modules. +- Prefer the builders in `acp.helpers` (for example `text_block`, `start_tool_call`) when constructing ACP payloads. The helpers instantiate the generated Pydantic models for you, keep literal discriminator fields out of call sites, and stay in lockstep with the schema thanks to the golden tests (`tests/test_golden.py`). ## Testing Guidelines -Pytest is the main framework with `pytest-asyncio` for coroutine tests and doctests activated on modules. Name test files `test_*.py` and co-locate fixtures under `tests/conftest.py`. Aim to cover new protocol surfaces with integration-style tests using the async agent stubs. Generate coverage reports via `tox -e py310` when assessing CI parity. +- Tests live in `tests/` and must be named `test_*.py`. Use `pytest.mark.asyncio` for coroutine coverage. +- Run `make test` (or `uv run python -m pytest`) prior to commits; include reproducing steps for any added fixtures. +- Gemini CLI coverage is disabled by default. Set `ACP_ENABLE_GEMINI_TESTS=1` (and `ACP_GEMINI_BIN=/path/to/gemini`) to exercise `tests/test_gemini_example.py`. ## Commit & Pull Request Guidelines -Commit history follows Conventional Commits (`feat:`, `fix:`, `docs:`). Scope commits narrowly and include context on affected protocol version or tooling. PRs should describe agent behaviors exercised, link related issues, and mention schema regeneration if applicable. Attach test output (`make check` or targeted pytest) and screenshots only when UI-adjacent docs change. Update docs/examples when altering the public agent API. +- Follow Conventional Commits (`feat:`, `fix:`, `docs:`, etc.) with succinct scopes, noting schema regenerations when applicable. +- PRs should describe exercised agent behaviours, link relevant issues, and include output from `make check` or focused pytest runs. +- Update documentation and examples whenever public APIs or transport behaviours change, and call out environment prerequisites for new integrations. ## Agent Integration Tips -Leverage `examples/mini_swe_agent/` as a template when bridging other command executors. Use `AgentSideConnection` with `stdio_streams()` for ACP-compliant clients; document any extra environment variables in README updates. +- Bootstrap agents from `examples/echo_agent.py` or `examples/agent.py`; pair with `examples/client.py` for round-trip validation. +- Use `spawn_agent_process` / `spawn_client_process` to embed ACP parties directly in Python applications. +- Validate new transports against `tests/test_rpc.py` and, when applicable, the Gemini example to ensure streaming updates and permission flows stay compliant. diff --git a/README.md b/README.md index 94408fc..0ab193b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ + + Agent Client Protocol + + # Agent Client Protocol (Python) Python SDK for the Agent Client Protocol (ACP). Build agents that speak ACP over stdio so tools like Zed can orchestrate them. @@ -6,9 +10,11 @@ Python SDK for the Agent Client Protocol (ACP). Build agents that speak ACP over **Highlights** -- Typed dataclasses generated from the upstream ACP schema (`acp.schema`) -- Async agent base class plus stdio transport helpers for quick bootstrapping -- Included examples that stream content updates and tool calls end-to-end +- Generated `pydantic` models that track the upstream ACP schema (`acp.schema`) +- Async base classes and JSON-RPC plumbing that keep stdio agents tiny +- Process helpers such as `spawn_agent_process` for embedding agents and clients directly in Python +- Batteries-included examples that exercise streaming updates, file I/O, and permission flows +- Optional Gemini CLI bridge (`examples/gemini.py`) for the `gemini --experimental-acp` integration ## Install @@ -29,29 +35,75 @@ uv add agent-client-protocol Prefer a step-by-step walkthrough? Read the [Quickstart guide](docs/quickstart.md) or the hosted docs: https://psiace.github.io/agent-client-protocol-python/. +### Launching from Python + +Embed the agent inside another Python process without spawning your own pipes: + +```python +import asyncio +import sys +from pathlib import Path + +from acp import spawn_agent_process, text_block +from acp.schema import InitializeRequest, NewSessionRequest, PromptRequest + + +async def main() -> None: + agent_script = Path("examples/echo_agent.py") + async with spawn_agent_process(lambda _agent: YourClient(), sys.executable, str(agent_script)) as (conn, _proc): + await conn.initialize(InitializeRequest(protocolVersion=1)) + session = await conn.newSession(NewSessionRequest(cwd=str(agent_script.parent), mcpServers=[])) + await conn.prompt( + PromptRequest( + sessionId=session.sessionId, + prompt=[text_block("Hello!")], + ) + ) + + +asyncio.run(main()) +``` + +`spawn_client_process` mirrors this pattern for the inverse direction. + ### Minimal agent sketch ```python import asyncio -from acp import Agent, AgentSideConnection, PromptRequest, PromptResponse, SessionNotification, stdio_streams -from acp.schema import AgentMessageChunk, TextContentBlock +from acp import ( + Agent, + AgentSideConnection, + InitializeRequest, + InitializeResponse, + NewSessionRequest, + NewSessionResponse, + PromptRequest, + PromptResponse, + session_notification, + stdio_streams, + text_block, + update_agent_message, +) class EchoAgent(Agent): def __init__(self, conn): self._conn = conn + async def initialize(self, params: InitializeRequest) -> InitializeResponse: + return InitializeResponse(protocolVersion=params.protocolVersion) + + async def newSession(self, params: NewSessionRequest) -> NewSessionResponse: + return NewSessionResponse(sessionId="sess-1") + async def prompt(self, params: PromptRequest) -> PromptResponse: for block in params.prompt: - text = getattr(block, "text", "") + text = block.get("text", "") if isinstance(block, dict) else getattr(block, "text", "") await self._conn.sessionUpdate( - SessionNotification( - sessionId=params.sessionId, - update=AgentMessageChunk( - sessionUpdate="agent_message_chunk", - content=TextContentBlock(type="text", text=text), - ), + session_notification( + params.sessionId, + update_agent_message(text_block(text)), ) ) return PromptResponse(stopReason="end_turn") @@ -71,15 +123,54 @@ Full example with streaming and lifecycle hooks lives in [examples/echo_agent.py ## Examples -- `examples/mini_swe_agent`: bridges mini-swe-agent into ACP, including a duet launcher and Textual TUI client -- Additional transport helpers are documented in the [Mini SWE guide](docs/mini-swe-agent.md) +- `examples/echo_agent.py`: the canonical streaming agent with lifecycle hooks +- `examples/client.py`: interactive console client that can launch any ACP agent via stdio +- `examples/agent.py`: richer agent showcasing initialization, authentication, and chunked updates +- `examples/duet.py`: launches both example agent and client using `spawn_agent_process` +- `examples/gemini.py`: connects to the Gemini CLI in `--experimental-acp` mode, with optional auto-approval and sandbox flags + +## Helper APIs + +Use `acp.helpers` to build protocol payloads without manually shaping dictionaries: + +```python +from acp import start_tool_call, text_block, tool_content, update_tool_call + +start = start_tool_call("call-1", "Inspect config", kind="read", status="pending") +update = update_tool_call( + "call-1", + status="completed", + content=[tool_content(text_block("Inspection finished."))], +) +``` + +Helpers cover content blocks (`text_block`, `resource_link_block`), embedded resources, tool calls (`start_edit_tool_call`, `update_tool_call`), and session updates (`update_agent_message_text`, `session_notification`). ## Documentation - Project docs (MkDocs): https://psiace.github.io/agent-client-protocol-python/ - Local sources: `docs/` - [Quickstart](docs/quickstart.md) - - [Mini SWE Agent bridge](docs/mini-swe-agent.md) + - [Releasing](docs/releasing.md) + +## Gemini CLI bridge + +Want to exercise the `gemini` CLI over ACP? The repository includes a Python replica of the Go SDK's REPL: + +```bash +python examples/gemini.py --yolo # auto-approve permissions +python examples/gemini.py --sandbox --model gemini-2.5-pro +``` + +Defaults assume the CLI is discoverable via `PATH`; override with `--gemini` or `ACP_GEMINI_BIN=/path/to/gemini`. + +The smoke test (`tests/test_gemini_example.py`) is opt-in to avoid false negatives when the CLI is unavailable or lacks credentials. Enable it locally with: + +```bash +ACP_ENABLE_GEMINI_TESTS=1 ACP_GEMINI_BIN=/path/to/gemini uv run python -m pytest tests/test_gemini_example.py +``` + +The test gracefully skips when authentication prompts (e.g. missing `GOOGLE_CLOUD_PROJECT`) block the interaction. ## Development workflow diff --git a/docs/index.md b/docs/index.md index 77f022e..5a16958 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,12 +1,18 @@ + + Agent Client Protocol + + # Agent Client Protocol SDK (Python) Welcome to the Python SDK for the Agent Client Protocol (ACP). The package ships ready-to-use transports, typed protocol models, and examples that stream messages to ACP-aware clients such as Zed. ## What you get -- Fully typed dataclasses generated from the upstream ACP schema (`acp.schema`) -- Async agent base class and stdio helpers to spin up an agent in a few lines -- Examples that demonstrate streaming updates and tool execution over ACP +- Pydantic models generated from the upstream ACP schema (`acp.schema`) +- Async agent/client wrappers with JSON-RPC task supervision built in +- Process helpers (`spawn_agent_process`, `spawn_client_process`) for embedding ACP nodes inside Python applications +- Helper APIs in `acp.helpers` that mirror the Go/TS SDK builders for content blocks, tool calls, and session updates. They instantiate the generated Pydantic types for you, so call sites stay concise without sacrificing validation. +- Examples that showcase streaming updates, file operations, permission flows, and even a Gemini CLI bridge (`examples/gemini.py`) ## Getting started @@ -20,11 +26,27 @@ Welcome to the Python SDK for the Agent Client Protocol (ACP). The package ships ``` 3. Point your ACP-capable client at the running process (for Zed, configure an Agent Server entry). The SDK takes care of JSON-RPC framing and lifecycle transitions. -Prefer a guided tour? Head to the [Quickstart](quickstart.md) for step-by-step instructions, including how to run the agent from an editor or terminal. +Prefer a guided tour? Head to the [Quickstart](quickstart.md) for terminal, editor, and programmatic launch walkthroughs. + +## Gemini CLI bridge + +If you have access to the Gemini CLI (`gemini --experimental-acp`), run: + +```bash +python examples/gemini.py --yolo +``` + +Flags mirror the Go SDK example: + +- `--gemini /path/to/cli` or `ACP_GEMINI_BIN` to override discovery +- `--model`, `--sandbox`, `--debug` forwarded verbatim +- `--yolo` auto-approves permission prompts with sensible defaults + +An opt-in smoke test lives at `tests/test_gemini_example.py`. Enable it with `ACP_ENABLE_GEMINI_TESTS=1` (and optionally `ACP_GEMINI_TEST_ARGS`) when the CLI is authenticated; otherwise the test stays skipped. ## Documentation map -- [Quickstart](quickstart.md): install, run, and extend the echo agent -- [Mini SWE Agent guide](mini-swe-agent.md): bridge mini-swe-agent over ACP, including duet launcher and Textual client +- [Quickstart](quickstart.md): install, run, and embed the echo agent, plus next steps for extending it +- [Releasing](releasing.md): schema upgrade workflow, version bumps, and publishing checklist Source code lives under `src/acp/`, while tests and additional examples are available in `tests/` and `examples/`. If you plan to contribute, see the repository README for the development workflow. diff --git a/docs/mini-swe-agent.md b/docs/mini-swe-agent.md deleted file mode 100644 index 70df79d..0000000 --- a/docs/mini-swe-agent.md +++ /dev/null @@ -1,54 +0,0 @@ -# Mini SWE Agent bridge - -This example wraps mini-swe-agent behind ACP so editors such as Zed can interact with it over stdio. A duet launcher is included to run a local Textual client beside the bridge for quick experimentation. - -## Overview - -- Accepts ACP prompts, concatenates text blocks, and forwards them to mini-swe-agent -- Streams language-model output via `session/update` → `agent_message_chunk` -- Emits `tool_call` / `tool_call_update` pairs for shell execution, including stdout and return codes -- Sends a final `agent_message_chunk` when mini-swe-agent prints `COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT` - -## Requirements - -- Python environment with `mini-swe-agent` installed (`pip install mini-swe-agent`) -- ACP-capable client (e.g. Zed) or the bundled Textual client -- Optional: `.env` file at the repo root for shared configuration when using the duet launcher - -If `mini-swe-agent` is missing, the bridge falls back to the reference copy at `reference/mini-swe-agent/src`. - -## Configure models and credentials - -Set environment variables before launching the bridge: - -- `MINI_SWE_MODEL`: model identifier such as `openrouter/openai/gpt-4o-mini` -- `OPENROUTER_API_KEY` for OpenRouter models, or `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` for native providers -- Optional `MINI_SWE_MODEL_KWARGS`: JSON blob of extra keyword arguments (OpenRouter defaults are injected automatically when omitted) - -The bridge selects the correct API key based on the chosen model and available variables. - -## Run inside Zed - -Add an Agent Server entry targeting `examples/mini_swe_agent/agent.py` and provide the environment variables there. Use Zed’s “Open ACP Logs” panel to observe streamed message chunks and tool call events in real time. - -## Run locally with the duet launcher - -To pair the bridge with the Textual TUI client, run: - -```bash -python examples/mini_swe_agent/duet.py -``` - -Both processes inherit settings from `.env` (thanks to `python-dotenv`) and communicate over dedicated pipes. - -**TUI shortcuts** -- `y`: YOLO -- `c`: Confirm -- `u`: Human (prompts for a shell command and streams it back as a tool call) -- `Enter`: Continue - -## Related files - -- Agent entrypoint: `examples/mini_swe_agent/agent.py` -- Duet launcher: `examples/mini_swe_agent/duet.py` -- Textual client: `examples/mini_swe_agent/client.py` diff --git a/docs/quickstart.md b/docs/quickstart.md index a840e37..03c2dee 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -15,17 +15,17 @@ pip install agent-client-protocol uv add agent-client-protocol ``` -## 2. Run the echo agent +## 2. Launch the Echo agent (terminal) -Launch the ready-made echo example, which streams text blocks back over ACP: +Start the ready-made echo example — it streams text blocks back to any ACP client: ```bash python examples/echo_agent.py ``` -Keep it running while you connect your client. +Leave this process running while you connect from an editor or another program. -## 3. Connect from your client +## 3. Connect from an editor ### Zed @@ -50,6 +50,43 @@ Open the Agents panel and start the session. Each message you send should be ech Any ACP client that communicates over stdio can spawn the same script; no additional transport configuration is required. +### Programmatic launch + +```python +import asyncio +import sys +from pathlib import Path + +from acp import spawn_agent_process, text_block +from acp.interfaces import Client +from acp.schema import InitializeRequest, NewSessionRequest, PromptRequest, SessionNotification + + +class SimpleClient(Client): + async def requestPermission(self, params): # pragma: no cover - minimal stub + return {"outcome": {"outcome": "cancelled"}} + + async def sessionUpdate(self, params: SessionNotification) -> None: + print("update:", params.sessionId, params.update) + + +async def main() -> None: + script = Path("examples/echo_agent.py") + async with spawn_agent_process(lambda _agent: SimpleClient(), sys.executable, str(script)) as (conn, _proc): + await conn.initialize(InitializeRequest(protocolVersion=1)) + session = await conn.newSession(NewSessionRequest(cwd=str(script.parent), mcpServers=[])) + await conn.prompt( + PromptRequest( + sessionId=session.sessionId, + prompt=[text_block("Hello from spawn!")], + ) + ) + +asyncio.run(main()) +``` + +`spawn_agent_process` manages the child process, wires its stdio into ACP framing, and closes everything when the block exits. The mirror helper `spawn_client_process` lets you drive an ACP client from Python as well. + ## 4. Extend the agent Create your own agent by subclassing `acp.Agent`. The pattern mirrors the echo example: @@ -64,15 +101,41 @@ class MyAgent(Agent): return PromptResponse(stopReason="end_turn") ``` -Hook it up with `AgentSideConnection` inside an async entrypoint and wire it to your client. Refer to [examples/echo_agent.py](https://github.com/psiace/agent-client-protocol-python/blob/main/examples/echo_agent.py) for the complete structure, including lifetime hooks (`initialize`, `newSession`) and streaming responses. +Hook it up with `AgentSideConnection` inside an async entrypoint and wire it to your client. Refer to: + +- [`examples/echo_agent.py`](https://github.com/psiace/agent-client-protocol-python/blob/main/examples/echo_agent.py) for the smallest streaming agent +- [`examples/agent.py`](https://github.com/psiace/agent-client-protocol-python/blob/main/examples/agent.py) for an implementation that negotiates capabilities and streams richer updates +- [`examples/duet.py`](https://github.com/psiace/agent-client-protocol-python/blob/main/examples/duet.py) to see `spawn_agent_process` in action alongside the interactive client +- [`examples/gemini.py`](https://github.com/psiace/agent-client-protocol-python/blob/main/examples/gemini.py) to drive the Gemini CLI (`--experimental-acp`) directly from Python + +Need builders for common payloads? `acp.helpers` mirrors the Go/TS helper APIs: + +```python +from acp import start_tool_call, update_tool_call, text_block, tool_content + +start_update = start_tool_call("call-42", "Open file", kind="read", status="pending") +finish_update = update_tool_call( + "call-42", + status="completed", + content=[tool_content(text_block("File opened."))], +) +``` + +Each helper wraps the generated Pydantic models in `acp.schema`, so the right discriminator fields (`type`, `sessionUpdate`, and friends) are always populated. That keeps examples readable while maintaining the same validation guarantees as constructing the models directly. Golden fixtures in `tests/test_golden.py` ensure the helpers stay in sync with future schema revisions. + +## 5. Optional: Talk to the Gemini CLI + +If you have the Gemini CLI installed and authenticated: + +```bash +python examples/gemini.py --yolo # auto-approve permission prompts +python examples/gemini.py --sandbox --model gemini-1.5-pro +``` -## Optional: Mini SWE Agent bridge +Environment helpers: -The repository also ships a bridge for [mini-swe-agent](https://github.com/groundx-ai/mini-swe-agent). To try it: +- `ACP_GEMINI_BIN` — override the CLI path (defaults to `PATH` lookup) +- `ACP_GEMINI_TEST_ARGS` — extra flags forwarded during the smoke test +- `ACP_ENABLE_GEMINI_TESTS=1` — opt-in toggle for `tests/test_gemini_example.py` -1. Install the dependency: - ```bash - pip install mini-swe-agent - ``` -2. Configure Zed to run `examples/mini_swe_agent/agent.py` and supply environment variables such as `MINI_SWE_MODEL` and `OPENROUTER_API_KEY`. -3. Review the [Mini SWE Agent guide](mini-swe-agent.md) for environment options, tool-call mapping, and a duet launcher that starts both the bridge and a Textual client (`python examples/mini_swe_agent/duet.py`). +Authentication hiccups (e.g. missing `GOOGLE_CLOUD_PROJECT`) are surfaced but treated as skips during testing so the suite stays green on machines without credentials. diff --git a/docs/releasing.md b/docs/releasing.md new file mode 100644 index 0000000..3eac2de --- /dev/null +++ b/docs/releasing.md @@ -0,0 +1,57 @@ +# Releasing + +This project tracks the ACP schema tags published by +[`agentclientprotocol/agent-client-protocol`](https://github.com/agentclientprotocol/agent-client-protocol). +Every release should line up with one of those tags so that the generated `acp.schema` module, examples, and package +version remain consistent. + +## Preparation + +Pick the target schema tag (for example `v0.4.5`) and regenerate the protocol bindings: + +```bash +ACP_SCHEMA_VERSION=v0.4.5 make gen-all +``` + +This downloads the upstream schema package and rewrites `schema/` plus the generated `src/acp/schema.py`. + +Bump the project version in `pyproject.toml`, updating `uv.lock` if dependencies changed. + +Run the standard checks: + +```bash +make check +make test +``` + +- `make check` covers Ruff formatting/linting, static analysis, and dependency hygiene. +- `make test` executes pytest (including doctests). + +Refresh documentation and examples (for instance the Gemini walkthrough) so they match the new schema behaviour. + +## Commit & Merge + +1. Make sure the diff only includes the expected artifacts: regenerated schema sources, `src/acp/schema.py`, version bumps, and doc updates. +2. Commit with a Conventional Commit message (for example `release: v0.4.5`) and note in the PR: + - The ACP schema tag you targeted + - Results from `make check` / `make test` + - Any behavioural or API changes worth highlighting +3. Merge once the review is approved. + +## Publish via GitHub Release + +Publishing is automated through `on-release-main.yml`. After the release PR merges to `main`: + +1. Draft a GitHub Release for the new tag (e.g. `v0.4.5`). If the tag is missing, the release UI will create it. +2. Once published, the workflow will: + - Write the tag back into `pyproject.toml` to keep the package version aligned + - Build and publish to PyPI via `uv publish` (using the `PYPI_TOKEN` secret) + - Deploy updated documentation with `mkdocs gh-deploy` + +No local `uv build`/`uv publish` runs are required—focus on providing a complete release summary (highlights, compatibility notes, etc.). + +## Additional Notes + +- Breaking schema updates often require refreshing golden fixtures (`tests/test_golden.py`), end-to-end cases such as `tests/test_rpc.py`, and any affected examples. +- Use `make clean` to remove generated artifacts if you need a fresh baseline before re-running `make gen-all`. +- Run optional checks like the Gemini smoke test (`ACP_ENABLE_GEMINI_TESTS=1`) whenever the environment is available to catch regressions before publishing. diff --git a/examples/agent.py b/examples/agent.py index c398cca..1356c37 100644 --- a/examples/agent.py +++ b/examples/agent.py @@ -18,17 +18,13 @@ PromptResponse, SetSessionModeRequest, SetSessionModeResponse, + session_notification, stdio_streams, + text_block, + update_agent_message, PROTOCOL_VERSION, ) -from acp.schema import ( - AgentCapabilities, - AgentMessageChunk, - McpCapabilities, - PromptCapabilities, - SessionNotification, - TextContentBlock, -) +from acp.schema import AgentCapabilities, McpCapabilities, PromptCapabilities class ExampleAgent(Agent): @@ -38,24 +34,24 @@ def __init__(self, conn: AgentSideConnection) -> None: async def _send_chunk(self, session_id: str, content: Any) -> None: await self._conn.sessionUpdate( - SessionNotification( - sessionId=session_id, - update=AgentMessageChunk( - sessionUpdate="agent_message_chunk", - content=content, - ), + session_notification( + session_id, + update_agent_message(content), ) ) async def initialize(self, params: InitializeRequest) -> InitializeResponse: # noqa: ARG002 logging.info("Received initialize request") + mcp_caps: McpCapabilities = McpCapabilities(http=False, sse=False) + prompt_caps: PromptCapabilities = PromptCapabilities(audio=False, embeddedContext=False, image=False) + agent_caps: AgentCapabilities = AgentCapabilities( + loadSession=False, + mcpCapabilities=mcp_caps, + promptCapabilities=prompt_caps, + ) return InitializeResponse( protocolVersion=PROTOCOL_VERSION, - agentCapabilities=AgentCapabilities( - loadSession=False, - mcpCapabilities=McpCapabilities(http=False, sse=False), - promptCapabilities=PromptCapabilities(audio=False, embeddedContext=False, image=False), - ), + agentCapabilities=agent_caps, ) async def authenticate(self, params: AuthenticateRequest) -> AuthenticateResponse | None: # noqa: ARG002 @@ -82,7 +78,7 @@ async def prompt(self, params: PromptRequest) -> PromptResponse: # Notify the client what it just sent and then echo each content block back. await self._send_chunk( params.sessionId, - TextContentBlock(type="text", text="Client sent:"), + text_block("Client sent:"), ) for block in params.prompt: await self._send_chunk(params.sessionId, block) diff --git a/examples/client.py b/examples/client.py index 6cde6a8..bdb2ae9 100644 --- a/examples/client.py +++ b/examples/client.py @@ -1,4 +1,5 @@ import asyncio +import asyncio.subprocess as aio_subprocess import contextlib import logging import os @@ -13,9 +14,9 @@ PromptRequest, RequestError, SessionNotification, + text_block, PROTOCOL_VERSION, ) -from acp.schema import TextContentBlock class ExampleClient(Client): @@ -90,7 +91,7 @@ async def interactive_loop(conn: ClientSideConnection, session_id: str) -> None: await conn.prompt( PromptRequest( sessionId=session_id, - prompt=[TextContentBlock(type="text", text=line)], + prompt=[text_block(line)], ) ) except Exception as exc: # noqa: BLE001 @@ -118,8 +119,8 @@ async def main(argv: list[str]) -> int: proc = await asyncio.create_subprocess_exec( spawn_program, *spawn_args, - stdin=asyncio.subprocess.PIPE, - stdout=asyncio.subprocess.PIPE, + stdin=aio_subprocess.PIPE, + stdout=aio_subprocess.PIPE, ) if proc.stdin is None or proc.stdout is None: diff --git a/examples/duet.py b/examples/duet.py index 049f164..de8d9ca 100644 --- a/examples/duet.py +++ b/examples/duet.py @@ -1,27 +1,44 @@ import asyncio +import importlib.util import os import sys from pathlib import Path +def _load_client_module(path: Path): + spec = importlib.util.spec_from_file_location("examples_client", path) + if spec is None or spec.loader is None: + raise RuntimeError(f"Unable to load client module from {path}") + module = importlib.util.module_from_spec(spec) + sys.modules.setdefault("examples_client", module) + spec.loader.exec_module(module) + return module + + +from acp import PROTOCOL_VERSION, spawn_agent_process +from acp.schema import InitializeRequest, NewSessionRequest + + async def main() -> int: root = Path(__file__).resolve().parent - agent_path = str(root / "agent.py") - client_path = str(root / "client.py") + agent_path = root / "agent.py" - # Ensure PYTHONPATH includes project src for `from acp import ...` env = os.environ.copy() src_dir = str((root.parent / "src").resolve()) env["PYTHONPATH"] = src_dir + os.pathsep + env.get("PYTHONPATH", "") - # Run the client and let it spawn the agent, wiring stdio automatically. - proc = await asyncio.create_subprocess_exec( - sys.executable, - client_path, - agent_path, - env=env, - ) - return await proc.wait() + client_module = _load_client_module(root / "client.py") + client = client_module.ExampleClient() + + async with spawn_agent_process(lambda _agent: client, sys.executable, str(agent_path), env=env) as ( + conn, + process, + ): + await conn.initialize(InitializeRequest(protocolVersion=PROTOCOL_VERSION, clientCapabilities=None)) + session = await conn.newSession(NewSessionRequest(mcpServers=[], cwd=str(root))) + await client_module.interactive_loop(conn, session.sessionId) + + return process.returncode or 0 if __name__ == "__main__": diff --git a/examples/echo_agent.py b/examples/echo_agent.py index 3a7f1c9..1bf04ff 100644 --- a/examples/echo_agent.py +++ b/examples/echo_agent.py @@ -1,4 +1,5 @@ import asyncio +from uuid import uuid4 from acp import ( Agent, @@ -9,10 +10,11 @@ NewSessionResponse, PromptRequest, PromptResponse, - SessionNotification, + session_notification, stdio_streams, + text_block, + update_agent_message, ) -from acp.schema import TextContentBlock, AgentMessageChunk class EchoAgent(Agent): @@ -23,18 +25,15 @@ async def initialize(self, params: InitializeRequest) -> InitializeResponse: return InitializeResponse(protocolVersion=params.protocolVersion) async def newSession(self, params: NewSessionRequest) -> NewSessionResponse: - return NewSessionResponse(sessionId="sess-1") + return NewSessionResponse(sessionId=uuid4().hex) async def prompt(self, params: PromptRequest) -> PromptResponse: for block in params.prompt: - text = block.get("text", "") if isinstance(block, dict) else getattr(block, "text", "") + text = getattr(block, "text", "") await self._conn.sessionUpdate( - SessionNotification( - sessionId=params.sessionId, - update=AgentMessageChunk( - sessionUpdate="agent_message_chunk", - content=TextContentBlock(type="text", text=text), - ), + session_notification( + params.sessionId, + update_agent_message(text_block(text)), ) ) return PromptResponse(stopReason="end_turn") diff --git a/examples/gemini.py b/examples/gemini.py new file mode 100644 index 0000000..f1fe9a9 --- /dev/null +++ b/examples/gemini.py @@ -0,0 +1,400 @@ +from __future__ import annotations + +import argparse +import asyncio +import asyncio.subprocess +import contextlib +import json +import os +import shutil +import sys +from pathlib import Path +from typing import Iterable + +from acp import ( + Client, + ClientSideConnection, + PROTOCOL_VERSION, + RequestError, + text_block, +) +from acp.schema import ( + AgentMessageChunk, + AgentPlanUpdate, + AgentThoughtChunk, + AllowedOutcome, + CancelNotification, + ClientCapabilities, + FileEditToolCallContent, + FileSystemCapability, + CreateTerminalRequest, + CreateTerminalResponse, + DeniedOutcome, + EmbeddedResourceContentBlock, + KillTerminalCommandRequest, + KillTerminalCommandResponse, + InitializeRequest, + NewSessionRequest, + PermissionOption, + PromptRequest, + ReadTextFileRequest, + ReadTextFileResponse, + RequestPermissionRequest, + RequestPermissionResponse, + ResourceContentBlock, + ReleaseTerminalRequest, + ReleaseTerminalResponse, + SessionNotification, + TerminalToolCallContent, + TerminalOutputRequest, + TerminalOutputResponse, + TextContentBlock, + ToolCallProgress, + ToolCallStart, + UserMessageChunk, + WaitForTerminalExitRequest, + WaitForTerminalExitResponse, + WriteTextFileRequest, + WriteTextFileResponse, +) + + +class GeminiClient(Client): + """Minimal client implementation that can drive the Gemini CLI over ACP.""" + + def __init__(self, auto_approve: bool) -> None: + self._auto_approve = auto_approve + + async def requestPermission( + self, + params: RequestPermissionRequest, + ) -> RequestPermissionResponse: # type: ignore[override] + if self._auto_approve: + option = _pick_preferred_option(params.options) + if option is None: + return RequestPermissionResponse(outcome=DeniedOutcome(outcome="cancelled")) + return RequestPermissionResponse(outcome=AllowedOutcome(optionId=option.optionId, outcome="selected")) + + title = params.toolCall.title or "" + if not params.options: + print(f"\n🔐 Permission requested: {title} (no options, cancelling)") + return RequestPermissionResponse(outcome=DeniedOutcome(outcome="cancelled")) + print(f"\n🔐 Permission requested: {title}") + for idx, opt in enumerate(params.options, start=1): + print(f" {idx}. {opt.name} ({opt.kind})") + + loop = asyncio.get_running_loop() + while True: + choice = await loop.run_in_executor(None, lambda: input("Select option: ").strip()) + if not choice: + continue + if choice.isdigit(): + idx = int(choice) - 1 + if 0 <= idx < len(params.options): + opt = params.options[idx] + return RequestPermissionResponse(outcome=AllowedOutcome(optionId=opt.optionId, outcome="selected")) + print("Invalid selection, try again.") + + async def writeTextFile( + self, + params: WriteTextFileRequest, + ) -> WriteTextFileResponse: # type: ignore[override] + path = Path(params.path) + if not path.is_absolute(): + raise RequestError.invalid_params({"path": params.path, "reason": "path must be absolute"}) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(params.content) + print(f"[Client] Wrote {path} ({len(params.content)} bytes)") + return WriteTextFileResponse() + + async def readTextFile( + self, + params: ReadTextFileRequest, + ) -> ReadTextFileResponse: # type: ignore[override] + path = Path(params.path) + if not path.is_absolute(): + raise RequestError.invalid_params({"path": params.path, "reason": "path must be absolute"}) + text = path.read_text() + print(f"[Client] Read {path} ({len(text)} bytes)") + if params.line is not None or params.limit is not None: + text = _slice_text(text, params.line, params.limit) + return ReadTextFileResponse(content=text) + + async def sessionUpdate( + self, + params: SessionNotification, + ) -> None: # type: ignore[override] + update = params.update + if isinstance(update, AgentMessageChunk): + _print_text_content(update.content) + elif isinstance(update, AgentThoughtChunk): + print("\n[agent_thought]") + _print_text_content(update.content) + elif isinstance(update, UserMessageChunk): + print("\n[user_message]") + _print_text_content(update.content) + elif isinstance(update, AgentPlanUpdate): + print("\n[plan]") + for entry in update.entries: + print(f" - {entry.status.upper():<10} {entry.content}") + elif isinstance(update, ToolCallStart): + print(f"\n🔧 {update.title} ({update.status or 'pending'})") + elif isinstance(update, ToolCallProgress): + status = update.status or "in_progress" + print(f"\n🔧 Tool call `{update.toolCallId}` → {status}") + if update.content: + for item in update.content: + if isinstance(item, FileEditToolCallContent): + print(f" diff: {item.path}") + elif isinstance(item, TerminalToolCallContent): + print(f" terminal: {item.terminalId}") + elif isinstance(item, dict): + print(f" content: {json.dumps(item, indent=2)}") + else: + print(f"\n[session update] {update}") + + # Optional / terminal-related methods --------------------------------- + async def createTerminal( + self, + params: CreateTerminalRequest, + ) -> CreateTerminalResponse: # type: ignore[override] + print(f"[Client] createTerminal: {params}") + return CreateTerminalResponse(terminalId="term-1") + + async def terminalOutput( + self, + params: TerminalOutputRequest, + ) -> TerminalOutputResponse: # type: ignore[override] + print(f"[Client] terminalOutput: {params}") + return TerminalOutputResponse(output="", truncated=False) + + async def releaseTerminal( + self, + params: ReleaseTerminalRequest, + ) -> ReleaseTerminalResponse: # type: ignore[override] + print(f"[Client] releaseTerminal: {params}") + return ReleaseTerminalResponse() + + async def waitForTerminalExit( + self, + params: WaitForTerminalExitRequest, + ) -> WaitForTerminalExitResponse: # type: ignore[override] + print(f"[Client] waitForTerminalExit: {params}") + return WaitForTerminalExitResponse() + + async def killTerminal( + self, + params: KillTerminalCommandRequest, + ) -> KillTerminalCommandResponse: # type: ignore[override] + print(f"[Client] killTerminal: {params}") + return KillTerminalCommandResponse() + + +def _pick_preferred_option(options: Iterable[PermissionOption]) -> PermissionOption | None: + best: PermissionOption | None = None + for option in options: + if option.kind in {"allow_once", "allow_always"}: + return option + best = best or option + return best + + +def _slice_text(content: str, line: int | None, limit: int | None) -> str: + lines = content.splitlines() + start = 0 + if line: + start = max(line - 1, 0) + end = len(lines) + if limit: + end = min(start + limit, end) + return "\n".join(lines[start:end]) + + +def _print_text_content(content: object) -> None: + if isinstance(content, TextContentBlock): + print(content.text) + elif isinstance(content, ResourceContentBlock): + print(f"{content.name or content.uri}") + elif isinstance(content, EmbeddedResourceContentBlock): + resource = content.resource + text = getattr(resource, "text", None) + if text: + print(text) + else: + blob = getattr(resource, "blob", None) + print(blob if blob else "") + elif isinstance(content, dict): + text = content.get("text") # type: ignore[union-attr] + if text: + print(text) + + +async def interactive_loop(conn: ClientSideConnection, session_id: str) -> None: + print("Type a message and press Enter to send.") + print("Commands: :cancel, :exit") + + loop = asyncio.get_running_loop() + while True: + try: + line = await loop.run_in_executor(None, lambda: input("\n> ").strip()) + except (EOFError, KeyboardInterrupt): + print("\nExiting.") + break + + if not line: + continue + if line in {":exit", ":quit"}: + break + if line == ":cancel": + await conn.cancel(CancelNotification(sessionId=session_id)) + continue + + try: + await conn.prompt( + PromptRequest( + sessionId=session_id, + prompt=[text_block(line)], + ) + ) + except RequestError as err: + _print_request_error("prompt", err) + except Exception as exc: # noqa: BLE001 + print(f"Prompt failed: {exc}", file=sys.stderr) + + +def _resolve_gemini_cli(binary: str | None) -> str: + if binary: + return binary + env_value = os.environ.get("ACP_GEMINI_BIN") + if env_value: + return env_value + resolved = shutil.which("gemini") + if resolved: + return resolved + raise FileNotFoundError("Unable to locate `gemini` CLI, provide --gemini path") + + +async def run(argv: list[str]) -> int: + parser = argparse.ArgumentParser(description="Interact with the Gemini CLI over ACP.") + parser.add_argument("--gemini", help="Path to the Gemini CLI binary") + parser.add_argument("--model", help="Model identifier to pass to Gemini") + parser.add_argument("--sandbox", action="store_true", help="Enable Gemini sandbox mode") + parser.add_argument("--debug", action="store_true", help="Pass --debug to Gemini") + parser.add_argument("--yolo", action="store_true", help="Auto-approve permission prompts") + args = parser.parse_args(argv[1:]) + + try: + gemini_path = _resolve_gemini_cli(args.gemini) + except FileNotFoundError as exc: + print(exc, file=sys.stderr) + return 1 + + cmd = [gemini_path, "--experimental-acp"] + if args.model: + cmd += ["--model", args.model] + if args.sandbox: + cmd.append("--sandbox") + if args.debug: + cmd.append("--debug") + + try: + proc = await asyncio.create_subprocess_exec( + *cmd, + stdin=asyncio.subprocess.PIPE, + stdout=asyncio.subprocess.PIPE, + stderr=None, + ) + except FileNotFoundError as exc: + print(f"Failed to start Gemini CLI: {exc}", file=sys.stderr) + return 1 + + if proc.stdin is None or proc.stdout is None: + print("Gemini process did not expose stdio pipes.", file=sys.stderr) + proc.terminate() + with contextlib.suppress(ProcessLookupError): + await proc.wait() + return 1 + + client_impl = GeminiClient(auto_approve=args.yolo) + conn = ClientSideConnection(lambda _agent: client_impl, proc.stdin, proc.stdout) + + try: + init_resp = await conn.initialize( + InitializeRequest( + protocolVersion=PROTOCOL_VERSION, + clientCapabilities=ClientCapabilities( + fs=FileSystemCapability(readTextFile=True, writeTextFile=True), + terminal=True, + ), + ) + ) + except RequestError as err: + _print_request_error("initialize", err) + await _shutdown(proc, conn) + return 1 + except Exception as exc: # noqa: BLE001 + print(f"initialize error: {exc}", file=sys.stderr) + await _shutdown(proc, conn) + return 1 + + print(f"✅ Connected to Gemini (protocol v{init_resp.protocolVersion})") + + try: + session = await conn.newSession( + NewSessionRequest( + cwd=os.getcwd(), + mcpServers=[], + ) + ) + except RequestError as err: + _print_request_error("new_session", err) + await _shutdown(proc, conn) + return 1 + except Exception as exc: # noqa: BLE001 + print(f"new_session error: {exc}", file=sys.stderr) + await _shutdown(proc, conn) + return 1 + + print(f"📝 Created session: {session.sessionId}") + + try: + await interactive_loop(conn, session.sessionId) + finally: + await _shutdown(proc, conn) + + return 0 + + +def _print_request_error(stage: str, err: RequestError) -> None: + payload = err.to_error_obj() + message = payload.get("message", "") + code = payload.get("code") + print(f"{stage} error ({code}): {message}", file=sys.stderr) + data = payload.get("data") + if data is not None: + try: + formatted = json.dumps(data, indent=2) + except TypeError: + formatted = str(data) + print(formatted, file=sys.stderr) + + +async def _shutdown(proc: asyncio.subprocess.Process, conn: ClientSideConnection) -> None: + with contextlib.suppress(Exception): + await conn.close() + if proc.returncode is None: + proc.terminate() + try: + await asyncio.wait_for(proc.wait(), timeout=5) + except asyncio.TimeoutError: + proc.kill() + await proc.wait() + + +def main(argv: list[str] | None = None) -> int: + args = sys.argv if argv is None else argv + return asyncio.run(run(list(args))) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/examples/mini_swe_agent/README.md b/examples/mini_swe_agent/README.md deleted file mode 100644 index 64b445e..0000000 --- a/examples/mini_swe_agent/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# Mini SWE Agent (Python) — ACP Bridge - -> Just a show of the bridge in action. Not a best-effort or absolutely-correct implementation of the agent. - -A minimal Agent Client Protocol (ACP) bridge that wraps mini-swe-agent so it can be run by Zed as an external agent over stdio, and also provides a local Textual UI client. - -## Configure in Zed (recommended for editor integration) - -Add an `agent_servers` entry to Zed’s `settings.json`. Point `command` to the Python interpreter that has both `agent-client-protocol` and `mini-swe-agent` installed, and `args` to this example script: - -```json -{ - "agent_servers": { - "Mini SWE Agent (Python)": { - "command": "/abs/path/to/python", - "args": [ - "/abs/path/to/agent-client-protocol-python/examples/mini_swe_agent/agent.py" - ], - "env": { - "MINI_SWE_MODEL": "openrouter/openai/gpt-4o-mini", - "MINI_SWE_MODEL_KWARGS": "{\"api_base\":\"https://openrouter.ai/api/v1\"}", - "OPENROUTER_API_KEY": "sk-or-..." - } - } - } -} -``` - -Notes -- If you install `agent-client-protocol` from PyPI, you do not need to set `PYTHONPATH`. -- Using OpenRouter: - - Set `MINI_SWE_MODEL` to a model supported by OpenRouter (e.g. `openrouter/openai/gpt-4o-mini`, `openrouter/anthropic/claude-3.5-sonnet`). - - Set `MINI_SWE_MODEL_KWARGS` to a JSON containing `api_base`: `{ "api_base": "https://openrouter.ai/api/v1" }`. - - Set `OPENROUTER_API_KEY` to your API key. -- Alternatively, you can use native OpenAI/Anthropic APIs. Set `MINI_SWE_MODEL` accordingly and provide the vendor-specific API key; `MINI_SWE_MODEL_KWARGS` is optional. - -## Run locally with a TUI (Textual) - -Use the duet launcher to run both the ACP agent and the local Textual client connected over dedicated pipes. The client keeps your terminal stdio; ACP messages flow over separate FDs. - -```bash -# From repo root -python examples/mini_swe_agent/duet.py -``` - -Environment -- The launcher loads `.env` from the repo root using python-dotenv (override=True) so both child processes inherit the same environment. -- Minimum for OpenRouter: - - `MINI_SWE_MODEL="openrouter/openai/gpt-4o-mini"` - - `OPENROUTER_API_KEY="sk-or-..."` - - Optional: `MINI_SWE_MODEL_KWARGS='{"api_base":"https://openrouter.ai/api/v1"}'` (auto-injected if missing) - -Quit behavior -- Quit from the TUI cleanly ends the background loop; duet will terminate both processes gracefully and force-kill after a short timeout if needed. - -## Behavior overview - -- User prompt handling - - Text blocks are concatenated into a task and passed to mini-swe-agent. -- Streaming updates - - The agent sends `session/update` with `agent_message_chunk` for incremental messages. -- Command execution visualization - - Each bash execution is reported with a `tool_call` (start) and a `tool_call_update` (complete) including command and output (`returncode` in rawOutput). -- Final result - - A final `agent_message_chunk` is sent at the end of the turn with the submitted output. - -Use Zed’s “open acp logs” command to inspect ACP traffic if needed. diff --git a/examples/mini_swe_agent/agent.py b/examples/mini_swe_agent/agent.py deleted file mode 100644 index 4b24838..0000000 --- a/examples/mini_swe_agent/agent.py +++ /dev/null @@ -1,550 +0,0 @@ -import asyncio -import os -import re -import sys -import uuid -from dataclasses import dataclass, field -from pathlib import Path -from typing import Any, Dict, Literal - -from acp import ( - Agent, - AgentSideConnection, - AuthenticateRequest, - CancelNotification, - Client, - InitializeRequest, - InitializeResponse, - NewSessionRequest, - NewSessionResponse, - PromptRequest, - PromptResponse, - SessionNotification, - SetSessionModeRequest, - SetSessionModeResponse, - stdio_streams, - PROTOCOL_VERSION, -) -from acp.schema import ( - AgentMessageChunk, - AgentThoughtChunk, - AllowedOutcome, - ContentToolCallContent, - PermissionOption, - RequestPermissionRequest, - RequestPermissionResponse, - TextContentBlock, - ToolCallStart, - ToolCallProgress, - ToolCallUpdate, - UserMessageChunk, -) - - -# Lazily import mini-swe-agent to avoid hard dependency for users who don't need this example - - -@dataclass -class ACPAgentConfig: # Extra controls layered on top of mini-swe-agent defaults - mode: Literal["confirm", "yolo", "human"] = "confirm" - whitelist_actions: list[str] = field(default_factory=list) - confirm_exit: bool = True - - -def _create_streaming_mini_agent( - *, - client: Client, - session_id: str, - cwd: str, - model_name: str, - model_kwargs: dict[str, Any], - loop: asyncio.AbstractEventLoop, - ext_config: ACPAgentConfig, -): - """Create a DefaultAgent that emits ACP session/update events during execution. - - Returns (agent, error_message_if_any). - """ - try: - try: - from minisweagent.agents.default import ( - DefaultAgent, - NonTerminatingException, - Submitted, - LimitsExceeded, - AgentConfig as _BaseCfg, - ) # type: ignore - from minisweagent.environments.local import LocalEnvironment # type: ignore - from minisweagent.models.litellm_model import LitellmModel # type: ignore - except Exception: - # Fallback to vendored reference copy if available - REF_SRC = Path(__file__).resolve().parents[2] / "reference" / "mini-swe-agent" / "src" - if REF_SRC.is_dir(): - if str(REF_SRC) not in sys.path: - sys.path.insert(0, str(REF_SRC)) - from minisweagent.agents.default import ( - DefaultAgent, - NonTerminatingException, - Submitted, - LimitsExceeded, - AgentConfig as _BaseCfg, - ) # type: ignore - from minisweagent.environments.local import LocalEnvironment # type: ignore - from minisweagent.models.litellm_model import LitellmModel # type: ignore - else: - raise - - class _StreamingMiniAgent(DefaultAgent): # type: ignore[misc] - def __init__(self) -> None: - self._acp_client = client - self._session_id = session_id - self._tool_seq = 0 - self._loop = loop - # expose mini-swe-agent exception types for outer loop - self._Submitted = Submitted - self._NonTerminatingException = NonTerminatingException - self._LimitsExceeded = LimitsExceeded - model = LitellmModel(model_name=model_name, model_kwargs=model_kwargs) - env = LocalEnvironment(cwd=cwd) - super().__init__(model=model, env=env, config_class=_BaseCfg) - # extra config - self.acp_config = ext_config - # During initial seeding (system/user templates), suppress updates - self._emit_updates = False - - # --- ACP streaming helpers --- - - def _schedule(self, coro): - import asyncio as _asyncio - - return _asyncio.run_coroutine_threadsafe(coro, self._loop) - - async def _send(self, update_model) -> None: - await self._acp_client.sessionUpdate( - SessionNotification(sessionId=self._session_id, update=update_model) - ) - - def _send_cost_hint(self) -> None: - try: - cost = float(getattr(self.model, "cost", 0.0)) - except Exception: - cost = 0.0 - hint = AgentThoughtChunk( - sessionUpdate="agent_thought_chunk", - content=TextContentBlock(type="text", text=f"__COST__:{cost:.2f}"), - ) - try: - loop = asyncio.get_running_loop() - loop.create_task(self._send(hint)) - except RuntimeError: - self._schedule(self._send(hint)) - - async def on_tool_start(self, title: str, command: str, tool_call_id: str) -> None: - """Send a tool_call start notification for a bash command.""" - update = ToolCallStart( - sessionUpdate="tool_call", - toolCallId=tool_call_id, - title=title, - kind="execute", - status="pending", - content=[ - ContentToolCallContent( - type="content", content=TextContentBlock(type="text", text=f"```bash\n{command}\n```") - ) - ], - rawInput={"command": command}, - ) - await self._send(update) - - async def on_tool_complete( - self, - tool_call_id: str, - output: str, - returncode: int, - *, - status: str = "completed", - ) -> None: - """Send a tool_call_update with the final output and return code.""" - update = ToolCallProgress( - sessionUpdate="tool_call_update", - toolCallId=tool_call_id, - status=status, - content=[ - ContentToolCallContent( - type="content", content=TextContentBlock(type="text", text=f"```ansi\n{output}\n```") - ) - ], - rawOutput={"output": output, "returncode": returncode}, - ) - await self._send(update) - - def add_message(self, role: str, content: str, **kwargs): - super().add_message(role, content, **kwargs) - # Only stream LM output as agent_message_chunk; tool output is handled via tool_call_update. - if not getattr(self, "_emit_updates", True) or role != "assistant": - return - text = str(content) - block = TextContentBlock(type="text", text=text) - update = AgentMessageChunk(sessionUpdate="agent_message_chunk", content=block) - try: - loop = asyncio.get_running_loop() - loop.create_task(self._send(update)) - except RuntimeError: - self._schedule(self._send(update)) - # Fire-and-forget - - def _confirm_action_sync(self, tool_call_id: str, command: str) -> bool: - # Build request and block until client responds - req = RequestPermissionRequest( - sessionId=self._session_id, - options=[ - PermissionOption(optionId="allow-once", name="Allow once", kind="allow_once"), - PermissionOption(optionId="reject-once", name="Reject", kind="reject_once"), - ], - toolCall=ToolCallUpdate( - toolCallId=tool_call_id, - title="bash", - kind="execute", - status="pending", - content=[ - ContentToolCallContent( - type="content", - content=TextContentBlock(type="text", text=f"```bash\n{command}\n```"), - ) - ], - rawInput={"command": command}, - ), - ) - fut = self._schedule(self._acp_client.requestPermission(req)) - try: - resp: RequestPermissionResponse = fut.result() # type: ignore[assignment] - except Exception: - return False - out = resp.outcome - if isinstance(out, AllowedOutcome) and out.optionId in ("allow-once", "allow-always"): - return True - return False - - def execute_action(self, action: dict) -> dict: # type: ignore[override] - self._tool_seq += 1 - tool_id = f"mini-bash-{self._tool_seq}-{uuid.uuid4().hex[:8]}" - command = action.get("action", "") - - # Always create tool_call first (pending) - self._schedule(self.on_tool_start("bash", command, tool_id)) - - # Request permission unless whitelisted - if command.strip() and not any(re.match(r, command) for r in self.acp_config.whitelist_actions): - allowed = self._confirm_action_sync(tool_id, command) - if not allowed: - # Mark as cancelled/failed accordingly and abort this step - self._schedule( - self.on_tool_complete( - tool_id, - "Permission denied by user", - 0, - status="cancelled", - ) - ) - raise self._NonTerminatingException("Command not executed: denied by user") - - try: - # Mark in progress - self._schedule( - self._send( - ToolCallProgress( - sessionUpdate="tool_call_update", - toolCallId=tool_id, - status="in_progress", - ) - ) - ) - result = super().execute_action(action) - output = result.get("output", "") - returncode = int(result.get("returncode", 0) or 0) - self._schedule(self.on_tool_complete(tool_id, output, returncode, status="completed")) - return result - except self._Submitted as e: # type: ignore[misc] - final_text = str(e) - self._schedule(self.on_tool_complete(tool_id, final_text, 0, status="completed")) - raise - except self._NonTerminatingException as e: # type: ignore[misc] - msg = str(e) - status = ( - "cancelled" - if any( - key in msg - for key in ( - "Command not executed", - "Switching to human mode", - "switched to manual mode", - "Interrupted by user", - ) - ) - else "failed" - ) - self._schedule( - self.on_tool_complete(tool_id, msg, 124 if status != "cancelled" else 0, status=status) - ) - raise - except Exception as e: # include other failures - msg = str(e) or "execution failed" - self._schedule(self.on_tool_complete(tool_id, msg, 124, status="failed")) - raise - - return _StreamingMiniAgent(), None - except Exception as e: - return None, f"Failed to load mini-swe-agent: {e}" - - -class MiniSweACPAgent(Agent): - def __init__(self, client: Client) -> None: - self._client = client - self._sessions: Dict[str, Dict[str, Any]] = {} - - async def initialize(self, _params: InitializeRequest) -> InitializeResponse: - from acp.schema import AgentCapabilities, PromptCapabilities - - return InitializeResponse( - protocolVersion=PROTOCOL_VERSION, - agentCapabilities=AgentCapabilities( - loadSession=True, - promptCapabilities=PromptCapabilities(audio=False, image=False, embeddedContext=True), - ), - authMethods=[], - ) - - async def newSession(self, params: NewSessionRequest) -> NewSessionResponse: - session_id = f"sess-{uuid.uuid4().hex[:12]}" - # load config from env for whitelist & confirm_exit - cfg = ACPAgentConfig() - try: - import json as _json - - wl = os.getenv("MINI_SWE_WHITELIST", "[]") - cfg.whitelist_actions = list(_json.loads(wl)) if wl else [] # type: ignore[assignment] - except Exception: - pass - ce = os.getenv("MINI_SWE_CONFIRM_EXIT") - if ce is not None: - cfg.confirm_exit = ce.lower() not in ("0", "false", "no") - self._sessions[session_id] = { - "cwd": params.cwd, - "agent": None, - "task": None, - "config": cfg, - } - return NewSessionResponse(sessionId=session_id) - - async def loadSession(self, params) -> None: # type: ignore[override] - try: - session_id = params.sessionId # type: ignore[attr-defined] - cwd = params.cwd # type: ignore[attr-defined] - except Exception: - session_id = getattr(params, "sessionId", "sess-unknown") - cwd = getattr(params, "cwd", os.getcwd()) - if session_id not in self._sessions: - cfg = ACPAgentConfig() - try: - import json as _json - - wl = os.getenv("MINI_SWE_WHITELIST", "[]") - cfg.whitelist_actions = list(_json.loads(wl)) if wl else [] # type: ignore[assignment] - except Exception: - pass - ce = os.getenv("MINI_SWE_CONFIRM_EXIT") - if ce is not None: - cfg.confirm_exit = ce.lower() not in ("0", "false", "no") - self._sessions[session_id] = {"cwd": cwd, "agent": None, "task": None, "config": cfg} - return None - - async def authenticate(self, _params: AuthenticateRequest) -> None: - return None - - async def setSessionMode(self, params: SetSessionModeRequest) -> SetSessionModeResponse | None: # type: ignore[override] - sess = self._sessions.get(params.sessionId) - if not sess: - return SetSessionModeResponse() - mode = params.modeId.lower() - if mode in ("confirm", "yolo", "human"): - sess["config"].mode = mode # type: ignore[attr-defined] - return SetSessionModeResponse() - - def _extract_mode_from_blocks(self, blocks) -> Literal["confirm", "yolo", "human"] | None: - for b in blocks: - if getattr(b, "type", None) == "text": - t = getattr(b, "text", "") or "" - m = re.search(r"\[\[MODE:([a-zA-Z]+)\]\]", t) - if m: - mode = m.group(1).lower() - if mode in ("confirm", "yolo", "human"): - return mode # type: ignore[return-value] - return None - - def _extract_code_from_blocks(self, blocks) -> str | None: - for b in blocks: - if getattr(b, "type", None) == "text": - t = getattr(b, "text", "") or "" - actions = re.findall(r"```bash\n(.*?)\n```", t, re.DOTALL) - if actions: - return actions[0].strip() - return None - - async def prompt(self, params: PromptRequest) -> PromptResponse: - sess = self._sessions.get(params.sessionId) - if not sess: - self._sessions[params.sessionId] = { - "cwd": os.getcwd(), - "agent": None, - "task": None, - "config": ACPAgentConfig(), - } - sess = self._sessions[params.sessionId] - - # Init or reuse agent - agent = sess.get("agent") - if agent is None: - model_name = os.getenv("MINI_SWE_MODEL", os.getenv("OPENAI_MODEL", "gpt-4o-mini")) - try: - import json - - model_kwargs = json.loads(os.getenv("MINI_SWE_MODEL_KWARGS", "{}")) - if not isinstance(model_kwargs, dict): - model_kwargs = {} - except Exception: - model_kwargs = {} - loop = asyncio.get_running_loop() - agent, err = _create_streaming_mini_agent( - client=self._client, - session_id=params.sessionId, - cwd=sess.get("cwd") or os.getcwd(), - model_name=model_name, - model_kwargs=model_kwargs, - loop=loop, - ext_config=sess["config"], - ) - if err: - await self._client.sessionUpdate( - SessionNotification( - sessionId=params.sessionId, - update=AgentMessageChunk( - sessionUpdate="agent_message_chunk", - content=TextContentBlock( - type="text", - text=( - "mini-swe-agent load error: " - + err - + "\nPlease install mini-swe-agent or its dependencies in the configured venv." - ), - ), - ), - ) - ) - return PromptResponse(stopReason="end_turn") - sess["agent"] = agent - - # Mode is controlled entirely client-side via requestPermission behavior; no control blocks are parsed. - - # Initialize conversation on first task - if not sess.get("task"): - # Build task - task_parts: list[str] = [] - for block in params.prompt: - btype = getattr(block, "type", None) - if btype == "text": - text = getattr(block, "text", "") - if text and not text.strip().startswith("[[MODE:"): - task_parts.append(str(text)) - task = "\n".join(task_parts).strip() or "Help me with the current repository." - sess["task"] = task - agent.extra_template_vars |= {"task": task} - agent.messages = [] - # Seed templates without emitting updates - agent._emit_updates = False # type: ignore[attr-defined] - agent.add_message("system", agent.render_template(agent.config.system_template)) - agent.add_message("user", agent.render_template(agent.config.instance_template)) - agent._emit_updates = True # type: ignore[attr-defined] - - # Decide the source of the next action - try: - if sess["config"].mode == "human": - # Expect a bash command from the client - cmd = self._extract_code_from_blocks(params.prompt) - if not cmd: - # Ask user to provide a command and return - await self._client.sessionUpdate( - SessionNotification( - sessionId=params.sessionId, - update=AgentMessageChunk( - sessionUpdate="agent_message_chunk", - content=TextContentBlock(type="text", text="Human mode: please submit a bash command."), - ), - ) - ) - return PromptResponse(stopReason="end_turn") - # Fabricate assistant message with the command - msg_content = f"\n```bash\n{cmd}\n```" - agent.add_message("assistant", msg_content) - response = {"content": msg_content} - else: - # Query the model in a worker thread to keep the event loop free - response = await asyncio.to_thread(agent.query) - # Send cost hint after each model call - try: - agent._send_cost_hint() # type: ignore[attr-defined] - except Exception: - pass - - # Execute and record observation in worker thread - await asyncio.to_thread(agent.get_observation, response) - except getattr(agent, "_NonTerminatingException") as e: # type: ignore[misc] - agent.add_message("user", str(e)) - except getattr(agent, "_Submitted") as e: # type: ignore[misc] - final_message = str(e) - agent.add_message("user", final_message) - # Ask for confirmation / new task if configured - if sess["config"].confirm_exit: - await self._client.sessionUpdate( - SessionNotification( - sessionId=params.sessionId, - update=AgentMessageChunk( - sessionUpdate="agent_message_chunk", - content=TextContentBlock( - type="text", - text=( - "Agent finished. Type a new task in the next message to continue, or do nothing to end." - ), - ), - ), - ) - ) - # Reset task so that next prompt can set a new one - sess["task"] = None - except getattr(agent, "_LimitsExceeded") as e: # type: ignore[misc] - agent.add_message("user", f"Limits exceeded: {e}") - except Exception as e: - # Surface unexpected errors to the client to avoid silent waits - await self._client.sessionUpdate( - SessionNotification( - sessionId=params.sessionId, - update=AgentMessageChunk( - sessionUpdate="agent_message_chunk", - content=TextContentBlock(type="text", text=f"Error while processing: {e}"), - ), - ) - ) - - return PromptResponse(stopReason="end_turn") - - async def cancel(self, _params: CancelNotification) -> None: - return None - - -async def main() -> None: - reader, writer = await stdio_streams() - AgentSideConnection(lambda client: MiniSweACPAgent(client), writer, reader) - await asyncio.Event().wait() - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/examples/mini_swe_agent/client.py b/examples/mini_swe_agent/client.py deleted file mode 100644 index 33d6380..0000000 --- a/examples/mini_swe_agent/client.py +++ /dev/null @@ -1,650 +0,0 @@ -import asyncio -import os -import queue -import re -import threading -import time -from dataclasses import dataclass -from pathlib import Path -from typing import Iterable, Literal, Optional - - -from rich.spinner import Spinner -from rich.text import Text -from textual.app import App, ComposeResult, SystemCommand -from textual.binding import Binding -from textual.containers import Container, Vertical, VerticalScroll -from textual.css.query import NoMatches -from textual.events import Key -from textual.screen import Screen -from textual.widgets import Footer, Header, Input, Static, TextArea - -from acp import ( - Client, - PROTOCOL_VERSION, - ClientSideConnection, - InitializeRequest, - NewSessionRequest, - PromptRequest, - RequestPermissionRequest, - RequestPermissionResponse, - SessionNotification, - SetSessionModeRequest, -) -from acp.schema import ( - AgentMessageChunk, - AgentThoughtChunk, - AllowedOutcome, - ContentToolCallContent, - PermissionOption, - TextContentBlock, - ToolCallStart, - ToolCallProgress, - UserMessageChunk, -) -from acp.stdio import _WritePipeProtocol - - -MODE = Literal["confirm", "yolo", "human"] - - -@dataclass -class UIMessage: - role: str # "assistant" or "user" - content: str - - -def _messages_to_steps(messages: list[UIMessage]) -> list[list[UIMessage]]: - steps: list[list[UIMessage]] = [] - current: list[UIMessage] = [] - for m in messages: - current.append(m) - if m.role == "user": - steps.append(current) - current = [] - if current: - steps.append(current) - return steps - - -class SmartInputContainer(Container): - def __init__(self, app: "TextualMiniSweClient"): - super().__init__(classes="smart-input-container") - self._app = app - self._multiline_mode = False - self.can_focus = True - self.display = False - - self.pending_prompt: Optional[str] = None - self._input_event = threading.Event() - self._input_result: Optional[str] = None - - self._header_display = Static(id="input-header-display", classes="message-header input-request-header") - self._hint_text = Static(classes="hint-text") - self._single_input = Input(placeholder="Type your input...") - self._multi_input = TextArea(show_line_numbers=False, classes="multi-input") - self._input_elements_container = Vertical( - self._header_display, - self._hint_text, - self._single_input, - self._multi_input, - classes="message-container", - ) - - def compose(self) -> ComposeResult: - yield self._input_elements_container - - def on_mount(self) -> None: - self._multi_input.display = False - self._update_mode_display() - - def on_focus(self) -> None: - if self._multiline_mode: - self._multi_input.focus() - else: - self._single_input.focus() - - def request_input(self, prompt: str) -> str: - self._input_event.clear() - self._input_result = None - self.pending_prompt = prompt - self._header_display.update(prompt) - self._update_mode_display() - # If we're already on the Textual thread, call directly; otherwise marshal. - if getattr(self._app, "_thread_id", None) == threading.get_ident(): - self._app.update_content() - else: - self._app.call_from_thread(self._app.update_content) - self._input_event.wait() - return self._input_result or "" - - def _complete_input(self, input_text: str): - self._input_result = input_text - self.pending_prompt = None - self.display = False - self._single_input.value = "" - self._multi_input.text = "" - self._multiline_mode = False - self._update_mode_display() - self._app.update_content() - # Reset scroll position to bottom - self._app._vscroll.scroll_y = 0 - self._input_event.set() - - def action_toggle_mode(self) -> None: - if self.pending_prompt is None or self._multiline_mode: - return - self._multiline_mode = True - self._update_mode_display() - self.on_focus() - - def _update_mode_display(self) -> None: - if self._multiline_mode: - self._multi_input.text = self._single_input.value - self._single_input.display = False - self._multi_input.display = True - self._hint_text.update( - "[reverse][bold][$accent] Ctrl+D [/][/][/] to submit, [reverse][bold][$accent] Tab [/][/][/] to switch focus with other controls" - ) - else: - self._hint_text.update( - "[reverse][bold][$accent] Enter [/][/][/] to submit, [reverse][bold][$accent] Ctrl+T [/][/][/] to switch to multi-line input, [reverse][bold][$accent] Tab [/][/][/] to switch focus with other controls", - ) - self._multi_input.display = False - self._single_input.display = True - - def on_input_submitted(self, event: Input.Submitted) -> None: - if not self._multiline_mode: - text = event.input.value.strip() - self._complete_input(text) - - def on_key(self, event: Key) -> None: - if event.key == "ctrl+t" and not self._multiline_mode: - event.prevent_default() - self.action_toggle_mode() - return - if self._multiline_mode and event.key == "ctrl+d": - event.prevent_default() - self._complete_input(self._multi_input.text.strip()) - return - if event.key == "escape": - event.prevent_default() - self.can_focus = False - self._app.set_focus(None) - return - - -class MiniSweClientImpl(Client): - def __init__(self, app: "TextualMiniSweClient") -> None: - self._app = app - - async def sessionUpdate(self, params: SessionNotification) -> None: - upd = params.update - - def _post(msg: UIMessage) -> None: - if getattr(self._app, "_thread_id", None) == threading.get_ident(): - self._app.enqueue_message(msg) - self._app.on_message_added() - else: - self._app.call_from_thread(lambda: (self._app.enqueue_message(msg), self._app.on_message_added())) - - if isinstance(upd, AgentMessageChunk): - # agent message - txt = _content_to_text(upd.content) - _post(UIMessage("assistant", txt)) - elif isinstance(upd, UserMessageChunk): - txt = _content_to_text(upd.content) - _post(UIMessage("user", txt)) - elif isinstance(upd, AgentThoughtChunk): - # agent thought chunk (informational) - txt = _content_to_text(upd.content) - _post(UIMessage("assistant", f"[thought]\n{txt}")) - elif isinstance(upd, ToolCallStart): - # tool call start → record structured state - self._app._update_tool_call( - upd.toolCallId, title=upd.title or "", status=upd.status or "pending", content=upd.content - ) - self._app.call_from_thread(self._app.update_content) - elif isinstance(upd, ToolCallProgress): - # tool call update → update structured state - self._app._update_tool_call(upd.toolCallId, status=upd.status, content=upd.content) - self._app.call_from_thread(self._app.update_content) - - async def requestPermission(self, params: RequestPermissionRequest) -> RequestPermissionResponse: - # Respect client-side mode shortcuts - mode = self._app.mode - if mode == "yolo": - return RequestPermissionResponse(outcome=AllowedOutcome(outcome="selected", optionId="allow-once")) - # Prompt user for decision - prompt = "Approve tool call? Press Enter to allow once, type 'n' to reject" - ans = self._app.input_container.request_input(prompt).strip().lower() - if ans in ("", "y", "yes"): - return RequestPermissionResponse(outcome=AllowedOutcome(outcome="selected", optionId="allow-once")) - return RequestPermissionResponse(outcome=AllowedOutcome(outcome="selected", optionId="reject-once")) - - # Optional features not used in this example - async def writeTextFile(self, params): - return None - - async def readTextFile(self, params): - return None - - -def _content_to_text(content) -> str: - if hasattr(content, "text"): - return str(content.text) - return str(content) - - -class TextualMiniSweClient(App): - BINDINGS = [ - Binding("right,l", "next_step", "Step++", tooltip="Show next step of the agent"), - Binding("left,h", "previous_step", "Step--", tooltip="Show previous step of the agent"), - Binding("0", "first_step", "Step=0", tooltip="Show first step of the agent", show=False), - Binding("$", "last_step", "Step=-1", tooltip="Show last step of the agent", show=False), - Binding("j,down", "scroll_down", "Scroll down", show=False), - Binding("k,up", "scroll_up", "Scroll up", show=False), - Binding("q,ctrl+q", "quit", "Quit", tooltip="Quit the agent"), - Binding("y,ctrl+y", "yolo", "YOLO mode", tooltip="Switch to YOLO Mode (LM actions will execute immediately)"), - Binding( - "c", - "confirm", - "CONFIRM mode", - tooltip="Switch to Confirm Mode (LM proposes commands and you confirm/reject them)", - ), - Binding("u,ctrl+u", "human", "HUMAN mode", tooltip="Switch to Human Mode (you can now type commands directly)"), - Binding("enter", "continue_step", "Next"), - Binding("f1,question_mark", "toggle_help_panel", "Help", tooltip="Show help"), - ] - - def __init__(self) -> None: - # Load CSS - css_path = os.environ.get( - "MSWEA_MINI_STYLE_PATH", - str( - Path(__file__).resolve().parents[2] - / "reference" - / "mini-swe-agent" - / "src" - / "minisweagent" - / "config" - / "mini.tcss" - ), - ) - try: - self.__class__.CSS = Path(css_path).read_text() - except Exception: - self.__class__.CSS = "" - super().__init__() - self.mode: MODE = "confirm" - self._vscroll = VerticalScroll() - self.input_container = SmartInputContainer(self) - self.messages: list[UIMessage] = [] - self._spinner = Spinner("dots") - self.agent_state: Literal["UNINITIALIZED", "RUNNING", "AWAITING_INPUT", "STOPPED"] = "UNINITIALIZED" - self._bg_loop: Optional[asyncio.AbstractEventLoop] = None - self._bg_thread: Optional[threading.Thread] = None - self._conn: Optional[ClientSideConnection] = None - self._session_id: Optional[str] = None - self._pending_human_command: Optional[str] = None - self._outbox: "queue.Queue[list[TextContentBlock]]" = queue.Queue() - # Pagination and metrics - self._i_step: int = 0 - self.n_steps: int = 1 - # Structured state for tool calls and plan - self._tool_calls: dict[str, dict] = {} - self._plan: list[dict] = [] - self._ask_new_task_pending = False - - # --- Textual lifecycle --- - - def compose(self) -> ComposeResult: - yield Header() - with Container(id="main"): - with self._vscroll: - with Vertical(id="content"): - pass - yield self.input_container - yield Footer() - - def on_mount(self) -> None: - self.agent_state = "RUNNING" - self.update_content() - self.set_interval(1 / 8, self._update_headers) - # Ask for initial task without blocking UI - threading.Thread(target=self._ask_initial_task, daemon=True).start() - - def _ask_initial_task(self) -> None: - task = self.input_container.request_input("Enter your task for mini-swe-agent:") - blocks = [TextContentBlock(type="text", text=task)] - self._outbox.put(blocks) - self._start_connection_thread() - - def on_unmount(self) -> None: - if self._bg_loop: - try: - self._bg_loop.call_soon_threadsafe(self._bg_loop.stop) - except Exception: - pass - - # --- Backend comms --- - - def _start_connection_thread(self) -> None: - """Start a background thread running the ACP connection event loop.""" - - def _runner() -> None: - loop = asyncio.new_event_loop() - self._bg_loop = loop - asyncio.set_event_loop(loop) - loop.run_until_complete(self._run_connection()) - - t = threading.Thread(target=_runner, daemon=True) - t.start() - self._bg_thread = t - - async def _open_acp_streams_from_env(self) -> tuple[Optional[asyncio.StreamReader], Optional[asyncio.StreamWriter]]: - """If launched via duet, open ACP streams from inherited FDs; else return (None, None).""" - read_fd_s = os.environ.get("MSWEA_READ_FD") - write_fd_s = os.environ.get("MSWEA_WRITE_FD") - if not read_fd_s or not write_fd_s: - return None, None - read_fd = int(read_fd_s) - write_fd = int(write_fd_s) - loop = asyncio.get_running_loop() - # Reader - reader = asyncio.StreamReader() - reader_proto = asyncio.StreamReaderProtocol(reader) - r_file = os.fdopen(read_fd, "rb", buffering=0) - await loop.connect_read_pipe(lambda: reader_proto, r_file) - # Writer - write_proto = _WritePipeProtocol() - w_file = os.fdopen(write_fd, "wb", buffering=0) - transport, _ = await loop.connect_write_pipe(lambda: write_proto, w_file) - writer = asyncio.StreamWriter(transport, write_proto, None, loop) - return reader, writer - - async def _run_connection(self) -> None: - """Run the ACP client connection using FDs provided by duet; do not fallback.""" - reader, writer = await self._open_acp_streams_from_env() - if reader is None or writer is None: # type: ignore[truthy-bool] - # Do not fallback; inform user and stop - self.call_from_thread( - lambda: ( - self.enqueue_message( - UIMessage( - "assistant", - "Communication endpoints not provided. Please launch via examples/mini_swe_agent/duet.py", - ) - ), - self.on_message_added(), - ) - ) - self.agent_state = "STOPPED" - return - - self._conn = ClientSideConnection(lambda _agent: MiniSweClientImpl(self), writer, reader) - try: - resp = await self._conn.initialize(InitializeRequest(protocolVersion=PROTOCOL_VERSION)) - self.call_from_thread( - lambda: ( - self.enqueue_message(UIMessage("assistant", f"Initialized v{resp.protocolVersion}")), - self.on_message_added(), - ) - ) - new_sess = await self._conn.newSession(NewSessionRequest(mcpServers=[], cwd=os.getcwd())) - self._session_id = new_sess.sessionId - self.call_from_thread( - lambda: ( - self.enqueue_message(UIMessage("assistant", f"Session {self._session_id} created")), - self.on_message_added(), - ) - ) - except Exception as e: - self.call_from_thread( - lambda: ( - self.enqueue_message(UIMessage("assistant", f"ACP connect error: {e}")), - self.on_message_added(), - ) - ) - self.agent_state = "STOPPED" - return - - # Autostep loop: take queued prompts and send; if none and mode != human, keep stepping - while self.agent_state != "STOPPED": - blocks: list[TextContentBlock] - try: - blocks = self._outbox.get_nowait() - except queue.Empty: - # Auto-advance a step when not in human mode and we're not awaiting input - if self.mode != "human" and self.input_container.pending_prompt is None: - blocks = [] - else: - await asyncio.sleep(0.05) - continue - # Send prompt turn - try: - result = await self._conn.prompt(PromptRequest(sessionId=self._session_id, prompt=blocks)) - # Minimal finish/new task UX: after each stopReason, if not human and idle, offer new task - if ( - self.mode != "human" - and not self._ask_new_task_pending - and self.input_container.pending_prompt is None - ): - self._ask_new_task_pending = True - - def _ask_new(): - task = self.input_container.request_input( - "Turn complete. Type a new task or press Enter to continue:" - ) - if task.strip(): - self._outbox.put([TextContentBlock(type="text", text=task)]) - else: - self._outbox.put([]) - self._ask_new_task_pending = False - - threading.Thread(target=_ask_new, daemon=True).start() - except Exception as e: - # Break on connection shutdowns to stop background thread cleanly - msg = str(e) - if isinstance(e, (BrokenPipeError, ConnectionResetError)) or "Broken pipe" in msg or "closed" in msg: - self.agent_state = "STOPPED" - break - self.call_from_thread(lambda: self.enqueue_message(UIMessage("assistant", f"prompt error: {e}"))) - # Tiny delay to avoid busy-looping - await asyncio.sleep(0.05) - - def send_human_command(self, cmd: str) -> None: - if not cmd.strip(): - return - code = f"```bash\n{cmd.strip()}\n```" - self._outbox.put([TextContentBlock(type="text", text=code)]) - - # --- UI updates --- - - def enqueue_message(self, msg: UIMessage) -> None: - self.messages.append(msg) - - def on_message_added(self) -> None: - auto_follow = self._vscroll.scroll_y <= 1 and self._i_step == self.n_steps - 1 - # recompute step pages - items = _messages_to_steps(self.messages) - self.n_steps = max(1, len(items)) - self.update_content() - if auto_follow: - self.action_last_step() - - # --- Structured state helpers --- - - def _update_tool_call( - self, tool_id: str, *, title: Optional[str] = None, status: Optional[str] = None, content=None - ) -> None: - tc = self._tool_calls.get(tool_id, {"toolCallId": tool_id, "title": "", "status": "pending", "content": []}) - if title is not None: - tc["title"] = title - if status is not None: - tc["status"] = status - if content: - # Append any text content blocks - texts = [] - for c in content: - if isinstance(c, ContentToolCallContent) and getattr(c.content, "type", None) == "text": - texts.append(getattr(c.content, "text", "")) - if texts: - tc.setdefault("content", []).append("\n".join(texts)) - self._tool_calls[tool_id] = tc - - def update_content(self) -> None: - container = self.query_one("#content", Vertical) - container.remove_children() - if not self.messages: - container.mount(Static("Waiting for agent…")) - return - items = _messages_to_steps(self.messages) - page = items[self._i_step] if items else [] - for m in page[-400:]: - message_container = Vertical(classes="message-container") - container.mount(message_container) - role = m.role.replace("assistant", "mini-swe-agent").upper() - message_container.mount(Static(role, classes="message-header")) - message_container.mount(Static(Text(m.content, no_wrap=False), classes="message-content")) - # Render structured tool calls at the end of the page - if self._tool_calls: - tc_container = Vertical(classes="message-container") - container.mount(tc_container) - tc_container.mount(Static("TOOL CALLS", classes="message-header")) - for tcid, tc in self._tool_calls.items(): - block = Vertical(classes="message-content") - tc_container.mount(block) - status = tc.get("status", "") - title = tc.get("title", "") - block.mount(Static(Text(f"[TOOL] {title} — {status}", no_wrap=False))) - for chunk in tc.get("content", []) or []: - block.mount(Static(Text(chunk, no_wrap=False))) - if self.input_container.pending_prompt is not None: - self.agent_state = "AWAITING_INPUT" - self.input_container.display = ( - self.input_container.pending_prompt is not None and self._i_step == len(items) - 1 - ) - if self.input_container.display: - self.input_container.on_focus() - self._update_headers() - self.refresh() - - def _update_headers(self) -> None: - status_text = self.agent_state - if self.agent_state == "RUNNING": - spinner_frame = str(self._spinner.render(time.time())).strip() - status_text = f"{self.agent_state} {spinner_frame}" - self.title = f"Step {self._i_step + 1}/{self.n_steps} - {status_text}" - try: - self.query_one("Header").set_class(self.agent_state == "RUNNING", "running") - except NoMatches: - pass - - # --- Actions --- - - # --- Pagination helpers --- - - @property - def i_step(self) -> int: - return self._i_step - - @i_step.setter - def i_step(self, value: int) -> None: - if value != self._i_step: - self._i_step = max(0, min(value, self.n_steps - 1)) - self._vscroll.scroll_to(y=0, animate=False) - self.update_content() - - # --- Actions --- - - def action_next_step(self) -> None: - self.i_step += 1 - - def action_previous_step(self) -> None: - self.i_step -= 1 - - def action_first_step(self) -> None: - self.i_step = 0 - - def action_last_step(self) -> None: - self.i_step = self.n_steps - 1 - - def action_scroll_down(self) -> None: - self._vscroll.scroll_to(y=self._vscroll.scroll_target_y + 15) - - def action_scroll_up(self) -> None: - self._vscroll.scroll_to(y=self._vscroll.scroll_target_y - 15) - - def _set_agent_mode_async(self, mode_id: str) -> None: - if not self._conn or not self._session_id or not self._bg_loop: - return - - def _schedule() -> None: - try: - self._bg_loop.create_task( - self._conn.setSessionMode(SetSessionModeRequest(sessionId=self._session_id, modeId=mode_id)) - ) - except Exception: - pass - - try: - self._bg_loop.call_soon_threadsafe(_schedule) - except Exception: - pass - - def action_yolo(self): - self.mode = "yolo" - self._set_agent_mode_async("yolo") - if self.input_container.pending_prompt is not None: - self.input_container._complete_input("") - self.notify("YOLO mode enabled") - - def action_confirm(self): - self.mode = "confirm" - self._set_agent_mode_async("confirm") - if self.input_container.pending_prompt is not None: - self.input_container._complete_input("") - self.notify("Confirm mode enabled") - - def action_human(self): - self.mode = "human" - self._set_agent_mode_async("human") - - # Ask for a command asynchronously to avoid blocking UI - def _ask(): - cmd = self.input_container.request_input("Type a bash command to run:") - if cmd.strip(): - self.send_human_command(cmd) - - threading.Thread(target=_ask, daemon=True).start() - self.notify("Human mode: commands will be executed as you submit them") - - def action_continue_step(self): - # For non-human modes, enqueue an empty turn to advance one step. - if self.mode != "human": - self._outbox.put([]) - return - - # For human, prompt for next command. - def _ask(): - cmd = self.input_container.request_input("Type a bash command to run:") - if cmd.strip(): - self.send_human_command(cmd) - - threading.Thread(target=_ask, daemon=True).start() - - def action_toggle_help_panel(self) -> None: - if self.query("HelpPanel"): - self.action_hide_help_panel() - else: - self.action_show_help_panel() - - -def main() -> None: - app = TextualMiniSweClient() - app.run() - - -if __name__ == "__main__": - main() diff --git a/examples/mini_swe_agent/duet.py b/examples/mini_swe_agent/duet.py deleted file mode 100644 index a0e0487..0000000 --- a/examples/mini_swe_agent/duet.py +++ /dev/null @@ -1,91 +0,0 @@ -import asyncio -import contextlib -import os -import sys -from pathlib import Path - - -async def main() -> None: - # Launch agent and client, wiring a dedicated pipe pair for ACP protocol. - # Client keeps its own stdin/stdout for the Textual UI. - root = Path(__file__).resolve().parent - agent_path = str(root / "agent.py") - client_path = str(root / "client.py") - - # Load .env into process env so children inherit it (prefer python-dotenv if available) - try: - from dotenv import load_dotenv # type: ignore - - # Load .env from repo root: examples/mini_swe_agent -> examples -> REPO - load_dotenv(dotenv_path=str(root.parents[1] / ".env"), override=True) - except Exception: - pass - - base_env = os.environ.copy() - src_dir = str((root.parents[1] / "src").resolve()) - base_env["PYTHONPATH"] = src_dir + os.pathsep + base_env.get("PYTHONPATH", "") - - # Create two pipes: agent->client and client->agent - a2c_r, a2c_w = os.pipe() - c2a_r, c2a_w = os.pipe() - # Ensure the FDs we pass to children are inheritable - for fd in (a2c_r, a2c_w, c2a_r, c2a_w): - os.set_inheritable(fd, True) - - # Start agent: stdin <- client (c2a_r), stdout -> client (a2c_w) - agent = await asyncio.create_subprocess_exec( - sys.executable, - agent_path, - stdin=c2a_r, - stdout=a2c_w, - stderr=sys.stderr, - env=base_env, - close_fds=True, - ) - - # Start client with ACP FDs exported via environment; keep terminal IO for UI - client_env = base_env.copy() - client_env["MSWEA_READ_FD"] = str(a2c_r) # where client reads ACP messages - client_env["MSWEA_WRITE_FD"] = str(c2a_w) # where client writes ACP messages - - client = await asyncio.create_subprocess_exec( - sys.executable, - client_path, - env=client_env, - pass_fds=(a2c_r, c2a_w), # ensure client inherits these FDs - close_fds=True, - ) - - # Close parent's copies of the pipe ends to avoid leaks - for fd in (a2c_r, a2c_w, c2a_r, c2a_w): - with contextlib.suppress(OSError): - os.close(fd) - - # If either process exits, terminate the other gracefully - agent_task = asyncio.create_task(agent.wait()) - client_task = asyncio.create_task(client.wait()) - done, pending = await asyncio.wait({agent_task, client_task}, return_when=asyncio.FIRST_COMPLETED) - - # Terminate the peer process - if agent_task in done and client.returncode is None: - with contextlib.suppress(ProcessLookupError): - client.terminate() - if client_task in done and agent.returncode is None: - with contextlib.suppress(ProcessLookupError): - agent.terminate() - - # Wait a bit, then kill if still running - try: - await asyncio.wait_for(asyncio.gather(agent.wait(), client.wait()), timeout=3) - except asyncio.TimeoutError: - with contextlib.suppress(ProcessLookupError): - if agent.returncode is None: - agent.kill() - with contextlib.suppress(ProcessLookupError): - if client.returncode is None: - client.kill() - await asyncio.gather(agent.wait(), client.wait()) - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/mkdocs.yml b/mkdocs.yml index 8287852..8a027a8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -10,7 +10,7 @@ copyright: Maintained by psiace. nav: - Home: index.md - Quick Start: quickstart.md - - Mini SWE Agent: mini-swe-agent.md + - Releasing: releasing.md plugins: - search - mkdocstrings: diff --git a/pyproject.toml b/pyproject.toml index 9c3409f..107f8b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "agent-client-protocol" -version = "0.4.5" +version = "0.4.9" description = "A Python implement of Agent Client Protocol (ACP, by Zed Industries)" authors = [{ name = "Chojan Shang", email = "psiace@apache.org" }] readme = "README.md" @@ -39,10 +39,12 @@ dev = [ "mkdocs>=1.4.2", "mkdocs-material>=8.5.10", "mkdocstrings[python]>=0.26.1", - "mini-swe-agent>=1.10.0", "python-dotenv>=1.1.1", ] +[project.optional-dependencies] +logfire = ["logfire>=0.14", "opentelemetry-sdk>=1.28.0"] + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" diff --git a/schema/VERSION b/schema/VERSION index d0b8723..64ef92f 100644 --- a/schema/VERSION +++ b/schema/VERSION @@ -1 +1 @@ -refs/tags/v0.4.5 +refs/tags/v0.4.9 diff --git a/schema/schema.json b/schema/schema.json index 5ab4110..d726f49 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -35,9 +35,11 @@ "anyOf": [ { "$ref": "#/$defs/SessionNotification", + "description": "Handles session update notifications from the agent.\n\nThis is a notification endpoint (no response expected) that receives\nreal-time updates about session progress, including message chunks,\ntool calls, and execution plans.\n\nNote: Clients SHOULD continue accepting tool call updates even after\nsending a `session/cancel` notification, as the agent may send final\nupdates before responding with the cancelled stop reason.\n\nSee protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output)", "title": "SessionNotification" }, { + "description": "Handles extension notifications from the agent.\n\nAllows the Agent to send an arbitrary notification that is not part of the ACP spec.\nExtension notifications provide a way to send one-way messages for custom functionality\nwhile maintaining protocol compatibility.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", "title": "ExtNotification" } ], @@ -48,37 +50,46 @@ "anyOf": [ { "$ref": "#/$defs/WriteTextFileRequest", + "description": "Writes content to a text file in the client's file system.\n\nOnly available if the client advertises the `fs.writeTextFile` capability.\nAllows the agent to create or modify files within the client's environment.\n\nSee protocol docs: [Client](https://agentclientprotocol.com/protocol/overview#client)", "title": "WriteTextFileRequest" }, { "$ref": "#/$defs/ReadTextFileRequest", + "description": "Reads content from a text file in the client's file system.\n\nOnly available if the client advertises the `fs.readTextFile` capability.\nAllows the agent to access file contents within the client's environment.\n\nSee protocol docs: [Client](https://agentclientprotocol.com/protocol/overview#client)", "title": "ReadTextFileRequest" }, { "$ref": "#/$defs/RequestPermissionRequest", + "description": "Requests permission from the user for a tool call operation.\n\nCalled by the agent when it needs user authorization before executing\na potentially sensitive operation. The client should present the options\nto the user and return their decision.\n\nIf the client cancels the prompt turn via `session/cancel`, it MUST\nrespond to this request with `RequestPermissionOutcome::Cancelled`.\n\nSee protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission)", "title": "RequestPermissionRequest" }, { "$ref": "#/$defs/CreateTerminalRequest", + "description": "Executes a command in a new terminal\n\nOnly available if the `terminal` Client capability is set to `true`.\n\nReturns a `TerminalId` that can be used with other terminal methods\nto get the current output, wait for exit, and kill the command.\n\nThe `TerminalId` can also be used to embed the terminal in a tool call\nby using the `ToolCallContent::Terminal` variant.\n\nThe Agent is responsible for releasing the terminal by using the `terminal/release`\nmethod.\n\nSee protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)", "title": "CreateTerminalRequest" }, { "$ref": "#/$defs/TerminalOutputRequest", + "description": "Gets the terminal output and exit status\n\nReturns the current content in the terminal without waiting for the command to exit.\nIf the command has already exited, the exit status is included.\n\nSee protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)", "title": "TerminalOutputRequest" }, { "$ref": "#/$defs/ReleaseTerminalRequest", + "description": "Releases a terminal\n\nThe command is killed if it hasn't exited yet. Use `terminal/wait_for_exit`\nto wait for the command to exit before releasing the terminal.\n\nAfter release, the `TerminalId` can no longer be used with other `terminal/*` methods,\nbut tool calls that already contain it, continue to display its output.\n\nThe `terminal/kill` method can be used to terminate the command without releasing\nthe terminal, allowing the Agent to call `terminal/output` and other methods.\n\nSee protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)", "title": "ReleaseTerminalRequest" }, { "$ref": "#/$defs/WaitForTerminalExitRequest", + "description": "Waits for the terminal command to exit and return its exit status\n\nSee protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)", "title": "WaitForTerminalExitRequest" }, { "$ref": "#/$defs/KillTerminalCommandRequest", + "description": "Kills the terminal command without releasing the terminal\n\nWhile `terminal/release` will also kill the command, this method will keep\nthe `TerminalId` valid so it can be used with other methods.\n\nThis method can be helpful when implementing command timeouts which terminate\nthe command as soon as elapsed, and then get the final output so it can be sent\nto the model.\n\nNote: `terminal/release` when `TerminalId` is no longer needed.\n\nSee protocol docs: [Terminals](https://agentclientprotocol.com/protocol/terminals)", "title": "KillTerminalCommandRequest" }, { + "description": "Handles extension method requests from the agent.\n\nAllows the Agent to send an arbitrary request that is not part of the ACP spec.\nExtension methods provide a way to add custom functionality while maintaining\nprotocol compatibility.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", "title": "ExtMethodRequest" } ], @@ -363,9 +374,11 @@ "anyOf": [ { "$ref": "#/$defs/CancelNotification", + "description": "Cancels ongoing operations for a session.\n\nThis is a notification sent by the client to cancel an ongoing prompt turn.\n\nUpon receiving this notification, the Agent SHOULD:\n- Stop all language model requests as soon as possible\n- Abort all tool call invocations in progress\n- Send any pending `session/update` notifications\n- Respond to the original `session/prompt` request with `StopReason::Cancelled`\n\nSee protocol docs: [Cancellation](https://agentclientprotocol.com/protocol/prompt-turn#cancellation)", "title": "CancelNotification" }, { + "description": "Handles extension notifications from the client.\n\nExtension notifications provide a way to send one-way messages for custom functionality\nwhile maintaining protocol compatibility.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", "title": "ExtNotification" } ], @@ -376,33 +389,41 @@ "anyOf": [ { "$ref": "#/$defs/InitializeRequest", + "description": "Establishes the connection with a client and negotiates protocol capabilities.\n\nThis method is called once at the beginning of the connection to:\n- Negotiate the protocol version to use\n- Exchange capability information between client and agent\n- Determine available authentication methods\n\nThe agent should respond with its supported protocol version and capabilities.\n\nSee protocol docs: [Initialization](https://agentclientprotocol.com/protocol/initialization)", "title": "InitializeRequest" }, { "$ref": "#/$defs/AuthenticateRequest", + "description": "Authenticates the client using the specified authentication method.\n\nCalled when the agent requires authentication before allowing session creation.\nThe client provides the authentication method ID that was advertised during initialization.\n\nAfter successful authentication, the client can proceed to create sessions with\n`new_session` without receiving an `auth_required` error.\n\nSee protocol docs: [Initialization](https://agentclientprotocol.com/protocol/initialization)", "title": "AuthenticateRequest" }, { "$ref": "#/$defs/NewSessionRequest", + "description": "Creates a new conversation session with the agent.\n\nSessions represent independent conversation contexts with their own history and state.\n\nThe agent should:\n- Create a new session context\n- Connect to any specified MCP servers\n- Return a unique session ID for future requests\n\nMay return an `auth_required` error if the agent requires authentication.\n\nSee protocol docs: [Session Setup](https://agentclientprotocol.com/protocol/session-setup)", "title": "NewSessionRequest" }, { "$ref": "#/$defs/LoadSessionRequest", + "description": "Loads an existing session to resume a previous conversation.\n\nThis method is only available if the agent advertises the `loadSession` capability.\n\nThe agent should:\n- Restore the session context and conversation history\n- Connect to the specified MCP servers\n- Stream the entire conversation history back to the client via notifications\n\nSee protocol docs: [Loading Sessions](https://agentclientprotocol.com/protocol/session-setup#loading-sessions)", "title": "LoadSessionRequest" }, { "$ref": "#/$defs/SetSessionModeRequest", + "description": "Sets the current mode for a session.\n\nAllows switching between different agent modes (e.g., \"ask\", \"architect\", \"code\")\nthat affect system prompts, tool availability, and permission behaviors.\n\nThe mode must be one of the modes advertised in `availableModes` during session\ncreation or loading. Agents may also change modes autonomously and notify the\nclient via `current_mode_update` notifications.\n\nThis method can be called at any time during a session, whether the Agent is\nidle or actively generating a response.\n\nSee protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes)", "title": "SetSessionModeRequest" }, { "$ref": "#/$defs/PromptRequest", + "description": "Processes a user prompt within a session.\n\nThis method handles the whole lifecycle of a prompt:\n- Receives user messages with optional context (files, images, etc.)\n- Processes the prompt using language models\n- Reports language model content and tool calls to the Clients\n- Requests permission to run tools\n- Executes any requested tool calls\n- Returns when the turn is complete with a stop reason\n\nSee protocol docs: [Prompt Turn](https://agentclientprotocol.com/protocol/prompt-turn)", "title": "PromptRequest" }, { "$ref": "#/$defs/SetSessionModelRequest", + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nSelect a model for a given session.", "title": "SetSessionModelRequest" }, { + "description": "Handles extension method requests from the client.\n\nExtension methods provide a way to add custom functionality while maintaining\nprotocol compatibility.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)", "title": "ExtMethodRequest" } ], diff --git a/scripts/gen_schema.py b/scripts/gen_schema.py index 10d4366..4df6c19 100644 --- a/scripts/gen_schema.py +++ b/scripts/gen_schema.py @@ -2,10 +2,12 @@ from __future__ import annotations import argparse +import ast import re import shutil import subprocess import sys +from collections.abc import Callable from pathlib import Path ROOT = Path(__file__).resolve().parents[1] @@ -16,6 +18,13 @@ BACKCOMPAT_MARKER = "# Backwards compatibility aliases" +# Pattern caches used when post-processing generated schema. +FIELD_DECLARATION_PATTERN = re.compile(r"[A-Za-z_][A-Za-z0-9_]*\s*:") +DESCRIPTION_PATTERN = re.compile( + r"description\s*=\s*(?P[rRbBuU]*)?(?P'''|\"\"\"|'|\")(?P.*?)(?P=quote)", + re.DOTALL, +) + # Map of numbered classes produced by datamodel-code-generator to descriptive names. # Keep this in sync with the Rust/TypeScript SDK nomenclature. RENAME_MAP: dict[str, str] = { @@ -43,6 +52,56 @@ "ToolCallContent3": "TerminalToolCallContent", } +ENUM_LITERAL_MAP: dict[str, tuple[str, ...]] = { + "PermissionOptionKind": ( + "allow_once", + "allow_always", + "reject_once", + "reject_always", + ), + "PlanEntryPriority": ("high", "medium", "low"), + "PlanEntryStatus": ("pending", "in_progress", "completed"), + "StopReason": ("end_turn", "max_tokens", "max_turn_requests", "refusal", "cancelled"), + "ToolCallStatus": ("pending", "in_progress", "completed", "failed"), + "ToolKind": ("read", "edit", "delete", "move", "search", "execute", "think", "fetch", "switch_mode", "other"), +} + +FIELD_TYPE_OVERRIDES: tuple[tuple[str, str, str, bool], ...] = ( + ("PermissionOption", "kind", "PermissionOptionKind", False), + ("PlanEntry", "priority", "PlanEntryPriority", False), + ("PlanEntry", "status", "PlanEntryStatus", False), + ("PromptResponse", "stopReason", "StopReason", False), + ("ToolCallUpdate", "kind", "ToolKind", True), + ("ToolCallUpdate", "status", "ToolCallStatus", True), + ("ToolCallProgress", "kind", "ToolKind", True), + ("ToolCallProgress", "status", "ToolCallStatus", True), + ("ToolCallStart", "kind", "ToolKind", True), + ("ToolCallStart", "status", "ToolCallStatus", True), + ("ToolCall", "kind", "ToolKind", True), + ("ToolCall", "status", "ToolCallStatus", True), +) + +DEFAULT_VALUE_OVERRIDES: tuple[tuple[str, str, str], ...] = ( + ("AgentCapabilities", "mcpCapabilities", "McpCapabilities(http=False, sse=False)"), + ( + "AgentCapabilities", + "promptCapabilities", + "PromptCapabilities(audio=False, embeddedContext=False, image=False)", + ), + ("ClientCapabilities", "fs", "FileSystemCapability(readTextFile=False, writeTextFile=False)"), + ("ClientCapabilities", "terminal", "False"), + ( + "InitializeRequest", + "clientCapabilities", + "ClientCapabilities(fs=FileSystemCapability(readTextFile=False, writeTextFile=False), terminal=False)", + ), + ( + "InitializeResponse", + "agentCapabilities", + "AgentCapabilities(loadSession=False, mcpCapabilities=McpCapabilities(http=False, sse=False), promptCapabilities=PromptCapabilities(audio=False, embeddedContext=False, image=False))", + ), +) + def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Generate src/acp/schema.py from the ACP JSON schema.") @@ -131,9 +190,14 @@ def rename_types(output_path: Path) -> list[str]: leftover_classes = sorted(set(leftover_class_pattern.findall(content))) header_block = "\n".join(header_lines) + "\n\n" + content = _apply_field_overrides(content) + content = _apply_default_overrides(content) + content = _add_description_comments(content) + alias_lines = [f"{old} = {new}" for old, new in sorted(RENAME_MAP.items())] alias_block = BACKCOMPAT_MARKER + "\n" + "\n".join(alias_lines) + "\n" + content = _inject_enum_aliases(content) content = header_block + content.rstrip() + "\n\n" + alias_block if not content.endswith("\n"): content += "\n" @@ -150,6 +214,162 @@ def rename_types(output_path: Path) -> list[str]: return warnings +def _apply_field_overrides(content: str) -> str: + for class_name, field_name, new_type, optional in FIELD_TYPE_OVERRIDES: + if optional: + pattern = re.compile( + rf"(class {class_name}\(BaseModel\):.*?\n\s+{field_name}:\s+Annotated\[\s*)Optional\[str],", + re.DOTALL, + ) + content, count = pattern.subn(rf"\1Optional[{new_type}],", content) + else: + pattern = re.compile( + rf"(class {class_name}\(BaseModel\):.*?\n\s+{field_name}:\s+Annotated\[\s*)str,", + re.DOTALL, + ) + content, count = pattern.subn(rf"\1{new_type},", content) + if count == 0: + print( + f"Warning: failed to apply type override for {class_name}.{field_name} -> {new_type}", + file=sys.stderr, + ) + return content + + +def _apply_default_overrides(content: str) -> str: + for class_name, field_name, replacement in DEFAULT_VALUE_OVERRIDES: + class_pattern = re.compile( + rf"(class {class_name}\(BaseModel\):)(.*?)(?=\nclass |\Z)", + re.DOTALL, + ) + + def replace_block( + match: re.Match[str], + _field_name: str = field_name, + _replacement: str = replacement, + _class_name: str = class_name, + ) -> str: + header, block = match.group(1), match.group(2) + field_patterns: tuple[tuple[re.Pattern[str], Callable[[re.Match[str]], str]], ...] = ( + ( + re.compile( + rf"(\n\s+{_field_name}:.*?\]\s*=\s*)([\s\S]*?)(?=\n\s{{4}}[A-Za-z_]|$)", + re.DOTALL, + ), + lambda m, _rep=_replacement: m.group(1) + _rep, + ), + ( + re.compile( + rf"(\n\s+{_field_name}:[^\n]*=)\s*([^\n]+)", + re.MULTILINE, + ), + lambda m, _rep=_replacement: m.group(1) + " " + _rep, + ), + ) + for pattern, replacer in field_patterns: + new_block, count = pattern.subn(replacer, block, count=1) + if count: + return header + new_block + print( + f"Warning: failed to override default for {_class_name}.{_field_name}", + file=sys.stderr, + ) + return match.group(0) + + content, count = class_pattern.subn(replace_block, content, count=1) + if count == 0: + print( + f"Warning: class {class_name} not found for default override on {field_name}", + file=sys.stderr, + ) + return content + + +def _add_description_comments(content: str) -> str: + lines = content.splitlines() + new_lines: list[str] = [] + index = 0 + + while index < len(lines): + line = lines[index] + stripped = line.lstrip() + indent = len(line) - len(stripped) + + if indent == 4 and FIELD_DECLARATION_PATTERN.match(stripped or ""): + block_lines, next_index = _collect_field_block(lines, index, indent) + block_text = "\n".join(block_lines) + description = _extract_description(block_text) + + if description: + indent_str = " " * indent + comment_lines = [ + f"{indent_str}# {comment_line}" if comment_line else f"{indent_str}#" + for comment_line in description.splitlines() + ] + if comment_lines: + new_lines.extend(comment_lines) + + new_lines.extend(block_lines) + index = next_index + continue + + new_lines.append(line) + index += 1 + + return "\n".join(new_lines) + + +def _collect_field_block(lines: list[str], start: int, indent: int) -> tuple[list[str], int]: + block: list[str] = [] + index = start + + while index < len(lines): + current_line = lines[index] + current_indent = len(current_line) - len(current_line.lstrip()) + if index != start and current_line.strip() and current_indent <= indent: + break + + block.append(current_line) + index += 1 + + return block, index + + +def _extract_description(block_text: str) -> str | None: + match = DESCRIPTION_PATTERN.search(block_text) + if not match: + return None + + prefix = match.group("prefix") or "" + quote = match.group("quote") + value = match.group("value") + literal = f"{prefix}{quote}{value}{quote}" + + # datamodel-code-generator emits standard string literals, but fall back to raw text on parse errors. + try: + parsed = ast.literal_eval(literal) + except (SyntaxError, ValueError): + return value.replace("\\n", "\n") + + if isinstance(parsed, str): + return parsed + return str(parsed) + + +def _inject_enum_aliases(content: str) -> str: + enum_lines = [ + f"{name} = Literal[{', '.join(repr(value) for value in values)}]" for name, values in ENUM_LITERAL_MAP.items() + ] + if not enum_lines: + return content + block = "\n".join(enum_lines) + "\n\n" + class_index = content.find("\nclass ") + if class_index == -1: + return content + insertion_point = class_index + 1 # include leading newline + return content[:insertion_point] + block + content[insertion_point:] + + def format_with_ruff(file_path: Path) -> None: uv_executable = shutil.which("uv") if uv_executable is None: diff --git a/src/acp/__init__.py b/src/acp/__init__.py index 9e916de..3f5e72f 100644 --- a/src/acp/__init__.py +++ b/src/acp/__init__.py @@ -6,6 +6,31 @@ RequestError, TerminalHandle, ) +from .helpers import ( + audio_block, + embedded_blob_resource, + embedded_text_resource, + image_block, + plan_entry, + resource_block, + resource_link_block, + session_notification, + start_edit_tool_call, + start_read_tool_call, + start_tool_call, + text_block, + tool_content, + tool_diff_content, + tool_terminal_ref, + update_agent_message, + update_agent_message_text, + update_agent_thought, + update_agent_thought_text, + update_plan, + update_tool_call, + update_user_message, + update_user_message_text, +) from .meta import ( AGENT_METHODS, CLIENT_METHODS, @@ -45,7 +70,8 @@ WriteTextFileRequest, WriteTextFileResponse, ) -from .stdio import stdio_streams +from .stdio import spawn_agent_process, spawn_client_process, spawn_stdio_connection, stdio_streams +from .transports import default_environment, spawn_stdio_transport __all__ = [ # noqa: RUF022 # constants @@ -95,4 +121,33 @@ "TerminalHandle", # stdio helper "stdio_streams", + "spawn_stdio_connection", + "spawn_agent_process", + "spawn_client_process", + "default_environment", + "spawn_stdio_transport", + # helpers + "text_block", + "image_block", + "audio_block", + "resource_link_block", + "embedded_text_resource", + "embedded_blob_resource", + "resource_block", + "tool_content", + "tool_diff_content", + "tool_terminal_ref", + "plan_entry", + "update_plan", + "update_user_message", + "update_user_message_text", + "update_agent_message", + "update_agent_message_text", + "update_agent_thought", + "update_agent_thought_text", + "session_notification", + "start_tool_call", + "start_read_tool_call", + "start_edit_tool_call", + "update_tool_call", ] diff --git a/src/acp/agent/__init__.py b/src/acp/agent/__init__.py new file mode 100644 index 0000000..5513dc5 --- /dev/null +++ b/src/acp/agent/__init__.py @@ -0,0 +1,3 @@ +from .connection import AgentSideConnection + +__all__ = ["AgentSideConnection"] diff --git a/src/acp/agent/connection.py b/src/acp/agent/connection.py new file mode 100644 index 0000000..eab6766 --- /dev/null +++ b/src/acp/agent/connection.py @@ -0,0 +1,147 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Callable +from typing import Any + +from ..connection import Connection, MethodHandler +from ..interfaces import Agent +from ..meta import CLIENT_METHODS +from ..schema import ( + CreateTerminalRequest, + CreateTerminalResponse, + KillTerminalCommandRequest, + KillTerminalCommandResponse, + ReadTextFileRequest, + ReadTextFileResponse, + ReleaseTerminalRequest, + ReleaseTerminalResponse, + RequestPermissionRequest, + RequestPermissionResponse, + SessionNotification, + TerminalOutputRequest, + TerminalOutputResponse, + WaitForTerminalExitRequest, + WaitForTerminalExitResponse, + WriteTextFileRequest, + WriteTextFileResponse, +) +from ..terminal import TerminalHandle +from ..utils import notify_model, request_model, request_optional_model +from .router import build_agent_router + +__all__ = ["AgentSideConnection"] + +_AGENT_CONNECTION_ERROR = "AgentSideConnection requires asyncio StreamWriter/StreamReader" + + +class AgentSideConnection: + """Agent-side connection wrapper that dispatches JSON-RPC messages to a Client implementation.""" + + def __init__( + self, + to_agent: Callable[[AgentSideConnection], Agent], + input_stream: Any, + output_stream: Any, + **connection_kwargs: Any, + ) -> None: + agent = to_agent(self) + handler = self._create_handler(agent) + + if not isinstance(input_stream, asyncio.StreamWriter) or not isinstance(output_stream, asyncio.StreamReader): + raise TypeError(_AGENT_CONNECTION_ERROR) + self._conn = Connection(handler, input_stream, output_stream, **connection_kwargs) + + def _create_handler(self, agent: Agent) -> MethodHandler: + router = build_agent_router(agent) + + async def handler(method: str, params: Any | None, is_notification: bool) -> Any: + if is_notification: + await router.dispatch_notification(method, params) + return None + return await router.dispatch_request(method, params) + + return handler + + async def sessionUpdate(self, params: SessionNotification) -> None: + await notify_model(self._conn, CLIENT_METHODS["session_update"], params) + + async def requestPermission(self, params: RequestPermissionRequest) -> RequestPermissionResponse: + return await request_model( + self._conn, + CLIENT_METHODS["session_request_permission"], + params, + RequestPermissionResponse, + ) + + async def readTextFile(self, params: ReadTextFileRequest) -> ReadTextFileResponse: + return await request_model( + self._conn, + CLIENT_METHODS["fs_read_text_file"], + params, + ReadTextFileResponse, + ) + + async def writeTextFile(self, params: WriteTextFileRequest) -> WriteTextFileResponse | None: + return await request_optional_model( + self._conn, + CLIENT_METHODS["fs_write_text_file"], + params, + WriteTextFileResponse, + ) + + async def createTerminal(self, params: CreateTerminalRequest) -> TerminalHandle: + create_response = await request_model( + self._conn, + CLIENT_METHODS["terminal_create"], + params, + CreateTerminalResponse, + ) + return TerminalHandle(create_response.terminalId, params.sessionId, self._conn) + + async def terminalOutput(self, params: TerminalOutputRequest) -> TerminalOutputResponse: + return await request_model( + self._conn, + CLIENT_METHODS["terminal_output"], + params, + TerminalOutputResponse, + ) + + async def releaseTerminal(self, params: ReleaseTerminalRequest) -> ReleaseTerminalResponse | None: + return await request_optional_model( + self._conn, + CLIENT_METHODS["terminal_release"], + params, + ReleaseTerminalResponse, + ) + + async def waitForTerminalExit(self, params: WaitForTerminalExitRequest) -> WaitForTerminalExitResponse: + return await request_model( + self._conn, + CLIENT_METHODS["terminal_wait_for_exit"], + params, + WaitForTerminalExitResponse, + ) + + async def killTerminal(self, params: KillTerminalCommandRequest) -> KillTerminalCommandResponse | None: + return await request_optional_model( + self._conn, + CLIENT_METHODS["terminal_kill"], + params, + KillTerminalCommandResponse, + ) + + async def extMethod(self, method: str, params: dict[str, Any]) -> dict[str, Any]: + return await self._conn.send_request(f"_{method}", params) + + async def extNotification(self, method: str, params: dict[str, Any]) -> None: + await self._conn.send_notification(f"_{method}", params) + + async def close(self) -> None: + await self._conn.close() + + async def __aenter__(self) -> AgentSideConnection: + return self + + async def __aexit__(self, exc_type, exc, tb) -> None: + await self.close() diff --git a/src/acp/agent/router.py b/src/acp/agent/router.py new file mode 100644 index 0000000..515d804 --- /dev/null +++ b/src/acp/agent/router.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +from typing import Any + +from ..exceptions import RequestError +from ..interfaces import Agent +from ..meta import AGENT_METHODS +from ..router import MessageRouter, RouterBuilder +from ..schema import ( + AuthenticateRequest, + CancelNotification, + InitializeRequest, + LoadSessionRequest, + NewSessionRequest, + PromptRequest, + SetSessionModelRequest, + SetSessionModeRequest, +) +from ..utils import normalize_result + +__all__ = ["build_agent_router"] + + +def build_agent_router(agent: Agent) -> MessageRouter: + builder = RouterBuilder() + + builder.request_attr(AGENT_METHODS["initialize"], InitializeRequest, agent, "initialize") + builder.request_attr(AGENT_METHODS["session_new"], NewSessionRequest, agent, "newSession") + builder.request_attr( + AGENT_METHODS["session_load"], + LoadSessionRequest, + agent, + "loadSession", + adapt_result=normalize_result, + ) + builder.request_attr( + AGENT_METHODS["session_set_mode"], + SetSessionModeRequest, + agent, + "setSessionMode", + adapt_result=normalize_result, + ) + builder.request_attr(AGENT_METHODS["session_prompt"], PromptRequest, agent, "prompt") + builder.request_attr( + AGENT_METHODS["session_set_model"], + SetSessionModelRequest, + agent, + "setSessionModel", + adapt_result=normalize_result, + ) + builder.request_attr( + AGENT_METHODS["authenticate"], + AuthenticateRequest, + agent, + "authenticate", + adapt_result=normalize_result, + ) + + builder.notification_attr(AGENT_METHODS["session_cancel"], CancelNotification, agent, "cancel") + + async def handle_extension_request(name: str, payload: dict[str, Any]) -> Any: + ext = getattr(agent, "extMethod", None) + if ext is None: + raise RequestError.method_not_found(f"_{name}") + return await ext(name, payload) + + async def handle_extension_notification(name: str, payload: dict[str, Any]) -> None: + ext = getattr(agent, "extNotification", None) + if ext is None: + return + await ext(name, payload) + + return builder.build( + request_extensions=handle_extension_request, + notification_extensions=handle_extension_notification, + ) diff --git a/src/acp/client/__init__.py b/src/acp/client/__init__.py new file mode 100644 index 0000000..829c56f --- /dev/null +++ b/src/acp/client/__init__.py @@ -0,0 +1,3 @@ +from .connection import ClientSideConnection + +__all__ = ["ClientSideConnection"] diff --git a/src/acp/client/connection.py b/src/acp/client/connection.py new file mode 100644 index 0000000..f97ff25 --- /dev/null +++ b/src/acp/client/connection.py @@ -0,0 +1,139 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Callable +from typing import Any + +from ..connection import Connection, MethodHandler +from ..interfaces import Agent, Client +from ..meta import AGENT_METHODS +from ..schema import ( + AuthenticateRequest, + AuthenticateResponse, + CancelNotification, + InitializeRequest, + InitializeResponse, + LoadSessionRequest, + LoadSessionResponse, + NewSessionRequest, + NewSessionResponse, + PromptRequest, + PromptResponse, + SetSessionModelRequest, + SetSessionModelResponse, + SetSessionModeRequest, + SetSessionModeResponse, +) +from ..utils import ( + notify_model, + request_model, + request_model_from_dict, +) +from .router import build_client_router + +__all__ = ["ClientSideConnection"] + +_CLIENT_CONNECTION_ERROR = "ClientSideConnection requires asyncio StreamWriter/StreamReader" + + +class ClientSideConnection: + """Client-side connection wrapper that dispatches JSON-RPC messages to an Agent implementation.""" + + def __init__( + self, + to_client: Callable[[Agent], Client], + input_stream: Any, + output_stream: Any, + **connection_kwargs: Any, + ) -> None: + if not isinstance(input_stream, asyncio.StreamWriter) or not isinstance(output_stream, asyncio.StreamReader): + raise TypeError(_CLIENT_CONNECTION_ERROR) + + client = to_client(self) # type: ignore[arg-type] + handler = self._create_handler(client) + self._conn = Connection(handler, input_stream, output_stream, **connection_kwargs) + + def _create_handler(self, client: Client) -> MethodHandler: + router = build_client_router(client) + + async def handler(method: str, params: Any | None, is_notification: bool) -> Any: + if is_notification: + await router.dispatch_notification(method, params) + return None + return await router.dispatch_request(method, params) + + return handler + + async def initialize(self, params: InitializeRequest) -> InitializeResponse: + return await request_model( + self._conn, + AGENT_METHODS["initialize"], + params, + InitializeResponse, + ) + + async def newSession(self, params: NewSessionRequest) -> NewSessionResponse: + return await request_model( + self._conn, + AGENT_METHODS["session_new"], + params, + NewSessionResponse, + ) + + async def loadSession(self, params: LoadSessionRequest) -> LoadSessionResponse: + return await request_model_from_dict( + self._conn, + AGENT_METHODS["session_load"], + params, + LoadSessionResponse, + ) + + async def setSessionMode(self, params: SetSessionModeRequest) -> SetSessionModeResponse: + return await request_model_from_dict( + self._conn, + AGENT_METHODS["session_set_mode"], + params, + SetSessionModeResponse, + ) + + async def setSessionModel(self, params: SetSessionModelRequest) -> SetSessionModelResponse: + return await request_model_from_dict( + self._conn, + AGENT_METHODS["session_set_model"], + params, + SetSessionModelResponse, + ) + + async def authenticate(self, params: AuthenticateRequest) -> AuthenticateResponse: + return await request_model_from_dict( + self._conn, + AGENT_METHODS["authenticate"], + params, + AuthenticateResponse, + ) + + async def prompt(self, params: PromptRequest) -> PromptResponse: + return await request_model( + self._conn, + AGENT_METHODS["session_prompt"], + params, + PromptResponse, + ) + + async def cancel(self, params: CancelNotification) -> None: + await notify_model(self._conn, AGENT_METHODS["session_cancel"], params) + + async def extMethod(self, method: str, params: dict[str, Any]) -> dict[str, Any]: + return await self._conn.send_request(f"_{method}", params) + + async def extNotification(self, method: str, params: dict[str, Any]) -> None: + await self._conn.send_notification(f"_{method}", params) + + async def close(self) -> None: + await self._conn.close() + + async def __aenter__(self) -> ClientSideConnection: + return self + + async def __aexit__(self, exc_type, exc, tb) -> None: + await self.close() diff --git a/src/acp/client/router.py b/src/acp/client/router.py new file mode 100644 index 0000000..9f0b85f --- /dev/null +++ b/src/acp/client/router.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +from typing import Any + +from ..exceptions import RequestError +from ..interfaces import Client +from ..meta import CLIENT_METHODS +from ..router import MessageRouter, RouterBuilder +from ..schema import ( + CreateTerminalRequest, + KillTerminalCommandRequest, + ReadTextFileRequest, + ReleaseTerminalRequest, + RequestPermissionRequest, + SessionNotification, + TerminalOutputRequest, + WaitForTerminalExitRequest, + WriteTextFileRequest, +) +from ..utils import normalize_result + +__all__ = ["build_client_router"] + + +def build_client_router(client: Client) -> MessageRouter: + builder = RouterBuilder() + + builder.request_attr(CLIENT_METHODS["fs_write_text_file"], WriteTextFileRequest, client, "writeTextFile") + builder.request_attr(CLIENT_METHODS["fs_read_text_file"], ReadTextFileRequest, client, "readTextFile") + builder.request_attr( + CLIENT_METHODS["session_request_permission"], + RequestPermissionRequest, + client, + "requestPermission", + ) + builder.request_attr( + CLIENT_METHODS["terminal_create"], + CreateTerminalRequest, + client, + "createTerminal", + optional=True, + default_result=None, + ) + builder.request_attr( + CLIENT_METHODS["terminal_output"], + TerminalOutputRequest, + client, + "terminalOutput", + optional=True, + default_result=None, + ) + builder.request_attr( + CLIENT_METHODS["terminal_release"], + ReleaseTerminalRequest, + client, + "releaseTerminal", + optional=True, + default_result={}, + adapt_result=normalize_result, + ) + builder.request_attr( + CLIENT_METHODS["terminal_wait_for_exit"], + WaitForTerminalExitRequest, + client, + "waitForTerminalExit", + optional=True, + default_result=None, + ) + builder.request_attr( + CLIENT_METHODS["terminal_kill"], + KillTerminalCommandRequest, + client, + "killTerminal", + optional=True, + default_result={}, + adapt_result=normalize_result, + ) + + builder.notification_attr(CLIENT_METHODS["session_update"], SessionNotification, client, "sessionUpdate") + + async def handle_extension_request(name: str, payload: dict[str, Any]) -> Any: + ext = getattr(client, "extMethod", None) + if ext is None: + raise RequestError.method_not_found(f"_{name}") + return await ext(name, payload) + + async def handle_extension_notification(name: str, payload: dict[str, Any]) -> None: + ext = getattr(client, "extNotification", None) + if ext is None: + return + await ext(name, payload) + + return builder.build( + request_extensions=handle_extension_request, + notification_extensions=handle_extension_notification, + ) diff --git a/src/acp/connection.py b/src/acp/connection.py new file mode 100644 index 0000000..0b3230e --- /dev/null +++ b/src/acp/connection.py @@ -0,0 +1,270 @@ +from __future__ import annotations + +import asyncio +import contextlib +import copy +import inspect +import json +import logging +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from enum import Enum +from typing import Any + +from pydantic import BaseModel, ValidationError + +from .exceptions import RequestError +from .task import ( + DefaultMessageDispatcher, + InMemoryMessageQueue, + InMemoryMessageStateStore, + MessageDispatcher, + MessageQueue, + MessageSender, + MessageStateStore, + NotificationRunner, + RequestRunner, + RpcTask, + RpcTaskKind, + SenderFactory, + TaskSupervisor, +) +from .telemetry import span_context + +JsonValue = Any +MethodHandler = Callable[[str, JsonValue | None, bool], Awaitable[JsonValue | None]] + + +__all__ = ["Connection", "JsonValue", "MethodHandler", "StreamDirection", "StreamEvent"] + + +DispatcherFactory = Callable[ + [MessageQueue, TaskSupervisor, MessageStateStore, RequestRunner, NotificationRunner], + MessageDispatcher, +] + + +class StreamDirection(str, Enum): + INCOMING = "incoming" + OUTGOING = "outgoing" + + +@dataclass(frozen=True, slots=True) +class StreamEvent: + direction: StreamDirection + message: dict[str, Any] + + +StreamObserver = Callable[[StreamEvent], Awaitable[None] | None] + + +class Connection: + """Minimal JSON-RPC 2.0 connection over newline-delimited JSON frames.""" + + def __init__( + self, + handler: MethodHandler, + writer: asyncio.StreamWriter, + reader: asyncio.StreamReader, + *, + queue: MessageQueue | None = None, + state_store: MessageStateStore | None = None, + dispatcher_factory: DispatcherFactory | None = None, + sender_factory: SenderFactory | None = None, + observers: list[StreamObserver] | None = None, + ) -> None: + self._handler = handler + self._writer = writer + self._reader = reader + self._next_request_id = 0 + self._state = state_store or InMemoryMessageStateStore() + self._tasks = TaskSupervisor(source="acp.Connection") + self._tasks.add_error_handler(self._on_task_error) + self._queue = queue or InMemoryMessageQueue() + self._closed = False + self._sender = (sender_factory or self._default_sender_factory)(self._writer, self._tasks) + self._recv_task = self._tasks.create( + self._receive_loop(), + name="acp.Connection.receive", + on_error=self._on_receive_error, + ) + dispatcher_factory = dispatcher_factory or self._default_dispatcher_factory + self._dispatcher = dispatcher_factory( + self._queue, + self._tasks, + self._state, + self._run_request, + self._run_notification, + ) + self._dispatcher.start() + self._observers: list[StreamObserver] = list(observers or []) + + async def close(self) -> None: + """Stop the receive loop and cancel any in-flight handler tasks.""" + if self._closed: + return + self._closed = True + await self._dispatcher.stop() + await self._sender.close() + await self._tasks.shutdown() + self._state.reject_all_outgoing(ConnectionError("Connection closed")) + + async def __aenter__(self) -> Connection: + return self + + async def __aexit__(self, exc_type, exc, tb) -> None: + await self.close() + + def add_observer(self, observer: StreamObserver) -> None: + """Register a callback that receives every raw JSON-RPC message.""" + self._observers.append(observer) + + async def send_request(self, method: str, params: JsonValue | None = None) -> Any: + request_id = self._next_request_id + self._next_request_id += 1 + future = self._state.register_outgoing(request_id, method) + payload = {"jsonrpc": "2.0", "id": request_id, "method": method, "params": params} + await self._sender.send(payload) + self._notify_observers(StreamDirection.OUTGOING, payload) + return await future + + async def send_notification(self, method: str, params: JsonValue | None = None) -> None: + payload = {"jsonrpc": "2.0", "method": method, "params": params} + await self._sender.send(payload) + self._notify_observers(StreamDirection.OUTGOING, payload) + + async def _receive_loop(self) -> None: + try: + while True: + line = await self._reader.readline() + if not line: + break + try: + message: dict[str, Any] = json.loads(line) + except Exception: + logging.exception("Error parsing JSON-RPC message") + continue + self._notify_observers(StreamDirection.INCOMING, message) + await self._process_message(message) + except asyncio.CancelledError: + return + + async def _process_message(self, message: dict[str, Any]) -> None: + method = message.get("method") + has_id = "id" in message + if method is not None and has_id: + await self._queue.publish(RpcTask(RpcTaskKind.REQUEST, message)) + return + if method is not None and not has_id: + await self._queue.publish(RpcTask(RpcTaskKind.NOTIFICATION, message)) + return + if has_id: + await self._handle_response(message) + + def _notify_observers(self, direction: StreamDirection, message: dict[str, Any]) -> None: + if not self._observers: + return + snapshot = copy.deepcopy(message) + event = StreamEvent(direction, snapshot) + for observer in list(self._observers): + try: + result = observer(event) + except Exception: + logging.exception("Stream observer failed", exc_info=True) + continue + if inspect.isawaitable(result): + self._tasks.create( + result, + name=f"acp.Connection.observer.{direction.value}", + on_error=self._on_observer_error, + ) + + def _on_observer_error(self, task: asyncio.Task[Any], exc: BaseException) -> None: + logging.exception("Stream observer coroutine failed", exc_info=exc) + + async def _run_request(self, message: dict[str, Any]) -> Any: + payload: dict[str, Any] = {"jsonrpc": "2.0", "id": message["id"]} + method = message["method"] + with span_context( + "acp.request", + attributes={"method": method}, + ): + try: + result = await self._handler(method, message.get("params"), False) + if isinstance(result, BaseModel): + result = result.model_dump() + payload["result"] = result if result is not None else None + await self._sender.send(payload) + self._notify_observers(StreamDirection.OUTGOING, payload) + return payload.get("result") + except RequestError as exc: + payload["error"] = exc.to_error_obj() + await self._sender.send(payload) + self._notify_observers(StreamDirection.OUTGOING, payload) + raise + except ValidationError as exc: + err = RequestError.invalid_params({"errors": exc.errors()}) + payload["error"] = err.to_error_obj() + await self._sender.send(payload) + self._notify_observers(StreamDirection.OUTGOING, payload) + raise err from None + except Exception as exc: + try: + data = json.loads(str(exc)) + except Exception: + data = {"details": str(exc)} + err = RequestError.internal_error(data) + payload["error"] = err.to_error_obj() + await self._sender.send(payload) + self._notify_observers(StreamDirection.OUTGOING, payload) + raise err from None + + async def _run_notification(self, message: dict[str, Any]) -> None: + method = message["method"] + with span_context("acp.notification", attributes={"method": method}), contextlib.suppress(Exception): + await self._handler(method, message.get("params"), True) + + async def _handle_response(self, message: dict[str, Any]) -> None: + request_id = message["id"] + result = message.get("result") + if "result" in message: + self._state.resolve_outgoing(request_id, result) + return + if "error" in message: + error_obj = message.get("error") or {} + self._state.reject_outgoing( + request_id, + RequestError( + error_obj.get("code", -32603), + error_obj.get("message", "Error"), + error_obj.get("data"), + ), + ) + return + self._state.resolve_outgoing(request_id, None) + + def _on_receive_error(self, task: asyncio.Task[Any], exc: BaseException) -> None: + logging.exception("Receive loop failed", exc_info=exc) + self._state.reject_all_outgoing(exc) + + def _on_task_error(self, task: asyncio.Task[Any], exc: BaseException) -> None: + logging.exception("Background task failed", exc_info=exc) + + def _default_dispatcher_factory( + self, + queue: MessageQueue, + supervisor: TaskSupervisor, + state: MessageStateStore, + request_runner: RequestRunner, + notification_runner: NotificationRunner, + ) -> MessageDispatcher: + return DefaultMessageDispatcher( + queue=queue, + supervisor=supervisor, + store=state, + request_runner=request_runner, + notification_runner=notification_runner, + ) + + def _default_sender_factory(self, writer: asyncio.StreamWriter, supervisor: TaskSupervisor) -> MessageSender: + return MessageSender(writer, supervisor) diff --git a/src/acp/core.py b/src/acp/core.py index 46e13a3..8afa468 100644 --- a/src/acp/core.py +++ b/src/acp/core.py @@ -1,680 +1,27 @@ -from __future__ import annotations - -import asyncio -import contextlib -import json -import logging -from collections.abc import Awaitable, Callable -from dataclasses import dataclass -from typing import Any, Protocol - -from pydantic import BaseModel, ValidationError - -from .meta import AGENT_METHODS, CLIENT_METHODS, PROTOCOL_VERSION # noqa: F401 -from .schema import ( - AuthenticateRequest, - AuthenticateResponse, - CancelNotification, - CreateTerminalRequest, - CreateTerminalResponse, - InitializeRequest, - InitializeResponse, - KillTerminalCommandRequest, - KillTerminalCommandResponse, - LoadSessionRequest, - LoadSessionResponse, - NewSessionRequest, - NewSessionResponse, - PromptRequest, - PromptResponse, - ReadTextFileRequest, - ReadTextFileResponse, - ReleaseTerminalRequest, - ReleaseTerminalResponse, - RequestPermissionRequest, - RequestPermissionResponse, - SessionNotification, - SetSessionModelRequest, - SetSessionModelResponse, - SetSessionModeRequest, - SetSessionModeResponse, - TerminalOutputRequest, - TerminalOutputResponse, - WaitForTerminalExitRequest, - WaitForTerminalExitResponse, - WriteTextFileRequest, - WriteTextFileResponse, -) - -# --- JSON-RPC 2.0 error helpers ------------------------------------------------- - -_AGENT_CONNECTION_ERROR = "AgentSideConnection requires asyncio StreamWriter/StreamReader" -_CLIENT_CONNECTION_ERROR = "ClientSideConnection requires asyncio StreamWriter/StreamReader" - - -class RequestError(Exception): - def __init__(self, code: int, message: str, data: Any | None = None) -> None: - super().__init__(message) - self.code = code - self.data = data - - @staticmethod - def parse_error(data: dict | None = None) -> RequestError: - return RequestError(-32700, "Parse error", data) - - @staticmethod - def invalid_request(data: dict | None = None) -> RequestError: - return RequestError(-32600, "Invalid request", data) - - @staticmethod - def method_not_found(method: str) -> RequestError: - return RequestError(-32601, "Method not found", {"method": method}) - - @staticmethod - def invalid_params(data: dict | None = None) -> RequestError: - return RequestError(-32602, "Invalid params", data) - - @staticmethod - def internal_error(data: dict | None = None) -> RequestError: - return RequestError(-32603, "Internal error", data) - - @staticmethod - def auth_required(data: dict | None = None) -> RequestError: - return RequestError(-32000, "Authentication required", data) - - @staticmethod - def resource_not_found(uri: str | None = None) -> RequestError: - data = {"uri": uri} if uri is not None else None - return RequestError(-32002, "Resource not found", data) - - def to_error_obj(self) -> dict: - return {"code": self.code, "message": str(self), "data": self.data} - - -# --- Transport & Connection ------------------------------------------------------ - -JsonValue = Any -MethodHandler = Callable[[str, JsonValue | None, bool], Awaitable[JsonValue | None]] -_NO_MATCH = object() - - -@dataclass(slots=True) -class _Pending: - future: asyncio.Future[Any] - - -class Connection: - """ - Minimal JSON-RPC 2.0 connection over newline-delimited JSON frames using - asyncio streams. KISS: only supports StreamReader/StreamWriter. - - - Outgoing messages always include {"jsonrpc": "2.0"} - - Requests and notifications are dispatched to a single async handler - - Responses resolve pending futures by numeric id - """ - - def __init__( - self, - handler: MethodHandler, - writer: asyncio.StreamWriter, - reader: asyncio.StreamReader, - ) -> None: - self._handler = handler - self._writer = writer - self._reader = reader - self._next_request_id = 0 - self._pending: dict[int, _Pending] = {} - self._inflight: set[asyncio.Task[Any]] = set() - self._write_lock = asyncio.Lock() - self._recv_task = asyncio.create_task(self._receive_loop()) - - async def close(self) -> None: - if not self._recv_task.done(): - self._recv_task.cancel() - with contextlib.suppress(asyncio.CancelledError): - await self._recv_task - if self._inflight: - tasks = list(self._inflight) - for task in tasks: - task.cancel() - for task in tasks: - with contextlib.suppress(asyncio.CancelledError): - await task - # Do not close writer here; lifecycle owned by caller - - # --- IO loops ---------------------------------------------------------------- - - async def _receive_loop(self) -> None: - try: - while True: - line = await self._reader.readline() - if not line: - break - try: - message = json.loads(line) - except Exception: - # Align with Rust/TS: on parse error, do not send a response; just skip - logging.exception("Error parsing JSON-RPC message") - continue - - await self._process_message(message) - except asyncio.CancelledError: - return - - async def _process_message(self, message: dict) -> None: - method = message.get("method") - has_id = "id" in message - - if method is not None and has_id: - self._schedule(self._handle_request(message)) - return - if method is not None and not has_id: - await self._handle_notification(message) - return - if has_id: - await self._handle_response(message) - - def _schedule(self, coro: Awaitable[Any]) -> None: - task = asyncio.create_task(coro) - self._inflight.add(task) - task.add_done_callback(self._task_done) - - def _task_done(self, task: asyncio.Task[Any]) -> None: - self._inflight.discard(task) - if task.cancelled(): - return - try: - task.result() - except Exception: - logging.exception("Unhandled error in JSON-RPC request handler") - - async def _handle_request(self, message: dict) -> None: - """Handle JSON-RPC request.""" - payload = {"jsonrpc": "2.0", "id": message["id"]} - try: - result = await self._handler(message["method"], message.get("params"), False) - if isinstance(result, BaseModel): - result = result.model_dump() - payload["result"] = result if result is not None else None - except RequestError as re: - payload["error"] = re.to_error_obj() - except ValidationError as ve: - payload["error"] = RequestError.invalid_params({"errors": ve.errors()}).to_error_obj() - except Exception as err: - try: - data = json.loads(str(err)) - except Exception: - data = {"details": str(err)} - payload["error"] = RequestError.internal_error(data).to_error_obj() - await self._send_obj(payload) - - async def _handle_notification(self, message: dict) -> None: - """Handle JSON-RPC notification.""" - with contextlib.suppress(Exception): - # Best-effort; notifications do not produce responses - await self._handler(message["method"], message.get("params"), True) - - async def _handle_response(self, message: dict) -> None: - """Handle JSON-RPC response.""" - fut = self._pending.pop(message["id"], None) - if fut is None: - return - if "result" in message: - fut.future.set_result(message.get("result")) - elif "error" in message: - err = message.get("error") or {} - fut.future.set_exception( - RequestError(err.get("code", -32603), err.get("message", "Error"), err.get("data")) - ) - else: - fut.future.set_result(None) - - async def _send_obj(self, obj: dict) -> None: - data = (json.dumps(obj, separators=(",", ":")) + "\n").encode("utf-8") - async with self._write_lock: - self._writer.write(data) - with contextlib.suppress(ConnectionError, RuntimeError): - # Peer closed; let reader loop end naturally - await self._writer.drain() - - # --- Public API -------------------------------------------------------------- - - async def send_request(self, method: str, params: JsonValue | None = None) -> Any: - req_id = self._next_request_id - self._next_request_id += 1 - fut: asyncio.Future[Any] = asyncio.get_running_loop().create_future() - self._pending[req_id] = _Pending(fut) - await self._send_obj({"jsonrpc": "2.0", "id": req_id, "method": method, "params": params}) - return await fut - - async def send_notification(self, method: str, params: JsonValue | None = None) -> None: - await self._send_obj({"jsonrpc": "2.0", "method": method, "params": params}) - - -# --- High-level Agent/Client wrappers ------------------------------------------- - - -class Client(Protocol): - async def requestPermission(self, params: RequestPermissionRequest) -> RequestPermissionResponse: ... - - async def sessionUpdate(self, params: SessionNotification) -> None: ... - - async def writeTextFile(self, params: WriteTextFileRequest) -> WriteTextFileResponse | None: ... - - async def readTextFile(self, params: ReadTextFileRequest) -> ReadTextFileResponse: ... - - # Optional/unstable terminal methods - async def createTerminal(self, params: CreateTerminalRequest) -> CreateTerminalResponse: ... - - async def terminalOutput(self, params: TerminalOutputRequest) -> TerminalOutputResponse: ... - - async def releaseTerminal(self, params: ReleaseTerminalRequest) -> ReleaseTerminalResponse | None: ... - - async def waitForTerminalExit(self, params: WaitForTerminalExitRequest) -> WaitForTerminalExitResponse: ... - - async def killTerminal(self, params: KillTerminalCommandRequest) -> KillTerminalCommandResponse | None: ... - - # Extension hooks (optional) - async def extMethod(self, method: str, params: dict) -> dict: ... - - async def extNotification(self, method: str, params: dict) -> None: ... - - -class Agent(Protocol): - async def initialize(self, params: InitializeRequest) -> InitializeResponse: ... - - async def newSession(self, params: NewSessionRequest) -> NewSessionResponse: ... +"""Compatibility re-exports for historical imports. - async def loadSession(self, params: LoadSessionRequest) -> LoadSessionResponse | None: ... +The project now keeps implementation in dedicated modules mirroring the +agent-client-protocol Rust structure, but external callers may still import +from ``acp.core``. Keep the surface API stable by forwarding to the new homes. +""" - async def setSessionMode(self, params: SetSessionModeRequest) -> SetSessionModeResponse | None: ... - - async def setSessionModel(self, params: SetSessionModelRequest) -> SetSessionModelResponse | None: ... - - async def authenticate(self, params: AuthenticateRequest) -> AuthenticateResponse | None: ... - - async def prompt(self, params: PromptRequest) -> PromptResponse: ... - - async def cancel(self, params: CancelNotification) -> None: ... - - # Extension hooks (optional) - async def extMethod(self, method: str, params: dict) -> dict: ... - - async def extNotification(self, method: str, params: dict) -> None: ... - - -class AgentSideConnection: - """ - Agent-side connection. Use when you implement the Agent and need to talk to a Client. - - Parameters: - - to_agent: factory that receives this connection and returns your Agent implementation - - input: asyncio.StreamWriter (local -> peer) - - output: asyncio.StreamReader (peer -> local) - """ - - def __init__( - self, - to_agent: Callable[[AgentSideConnection], Agent], - input_stream: Any, - output_stream: Any, - ) -> None: - agent = to_agent(self) - - handler = self._create_agent_handler(agent) - - if not isinstance(input_stream, asyncio.StreamWriter) or not isinstance(output_stream, asyncio.StreamReader): - raise TypeError(_AGENT_CONNECTION_ERROR) - self._conn = Connection(handler, input_stream, output_stream) - - def _create_agent_handler(self, agent: Agent) -> MethodHandler: - async def handler(method: str, params: Any, is_notification: bool) -> Any: - return await self._handle_agent_method(agent, method, params, is_notification) - - return handler - - async def _handle_agent_method(self, agent: Agent, method: str, params: Any, is_notification: bool) -> Any: - # Init/new - result = await self._handle_agent_init_methods(agent, method, params) - if result is not _NO_MATCH: - return result - # Session-related - result = await self._handle_agent_session_methods(agent, method, params) - if result is not _NO_MATCH: - return result - # Auth - result = await self._handle_agent_auth_methods(agent, method, params) - if result is not _NO_MATCH: - return result - # Extensions - result = await self._handle_agent_ext_methods(agent, method, params, is_notification) - if result is not _NO_MATCH: - return result - raise RequestError.method_not_found(method) - - async def _handle_agent_init_methods(self, agent: Agent, method: str, params: Any) -> Any: - if method == AGENT_METHODS["initialize"]: - p = InitializeRequest.model_validate(params) - return await agent.initialize(p) - if method == AGENT_METHODS["session_new"]: - p = NewSessionRequest.model_validate(params) - return await agent.newSession(p) - return _NO_MATCH - - async def _handle_agent_session_methods(self, agent: Agent, method: str, params: Any) -> Any: - if method == AGENT_METHODS["session_load"]: - if not hasattr(agent, "loadSession"): - raise RequestError.method_not_found(method) - p = LoadSessionRequest.model_validate(params) - result = await agent.loadSession(p) - if isinstance(result, BaseModel): - return result.model_dump() - return result or {} - if method == AGENT_METHODS["session_set_mode"]: - if not hasattr(agent, "setSessionMode"): - raise RequestError.method_not_found(method) - p = SetSessionModeRequest.model_validate(params) - result = await agent.setSessionMode(p) - return result.model_dump() if isinstance(result, BaseModel) else (result or {}) - if method == AGENT_METHODS["session_prompt"]: - p = PromptRequest.model_validate(params) - return await agent.prompt(p) - if method == AGENT_METHODS["session_set_model"]: - if not hasattr(agent, "setSessionModel"): - raise RequestError.method_not_found(method) - p = SetSessionModelRequest.model_validate(params) - result = await agent.setSessionModel(p) - return result.model_dump() if isinstance(result, BaseModel) else (result or {}) - if method == AGENT_METHODS["session_cancel"]: - p = CancelNotification.model_validate(params) - return await agent.cancel(p) - return _NO_MATCH - - async def _handle_agent_auth_methods(self, agent: Agent, method: str, params: Any) -> Any: - if method == AGENT_METHODS["authenticate"]: - p = AuthenticateRequest.model_validate(params) - result = await agent.authenticate(p) - return result.model_dump() if isinstance(result, BaseModel) else (result or {}) - return _NO_MATCH - - async def _handle_agent_ext_methods(self, agent: Agent, method: str, params: Any, is_notification: bool) -> Any: - if isinstance(method, str) and method.startswith("_"): - ext_name = method[1:] - if is_notification: - if hasattr(agent, "extNotification"): - await agent.extNotification(ext_name, params or {}) # type: ignore[arg-type] - return None - return None - else: - if hasattr(agent, "extMethod"): - return await agent.extMethod(ext_name, params or {}) # type: ignore[arg-type] - return _NO_MATCH - return _NO_MATCH - - # client-bound methods (agent -> client) - async def sessionUpdate(self, params: SessionNotification) -> None: - await self._conn.send_notification( - CLIENT_METHODS["session_update"], - params.model_dump(exclude_none=True, exclude_defaults=True), - ) - - async def requestPermission(self, params: RequestPermissionRequest) -> RequestPermissionResponse: - resp = await self._conn.send_request( - CLIENT_METHODS["session_request_permission"], - params.model_dump(exclude_none=True, exclude_defaults=True), - ) - return RequestPermissionResponse.model_validate(resp) - - async def readTextFile(self, params: ReadTextFileRequest) -> ReadTextFileResponse: - resp = await self._conn.send_request( - CLIENT_METHODS["fs_read_text_file"], - params.model_dump(exclude_none=True, exclude_defaults=True), - ) - return ReadTextFileResponse.model_validate(resp) - - async def writeTextFile(self, params: WriteTextFileRequest) -> WriteTextFileResponse | None: - resp = await self._conn.send_request( - CLIENT_METHODS["fs_write_text_file"], - params.model_dump(exclude_none=True, exclude_defaults=True), - ) - # Response may be empty object - return WriteTextFileResponse.model_validate(resp) if isinstance(resp, dict) else None - - async def createTerminal(self, params: CreateTerminalRequest) -> TerminalHandle: - resp = await self._conn.send_request( - CLIENT_METHODS["terminal_create"], - params.model_dump(exclude_none=True, exclude_defaults=True), - ) - create_resp = CreateTerminalResponse.model_validate(resp) - return TerminalHandle(create_resp.terminalId, params.sessionId, self._conn) - - async def extMethod(self, method: str, params: dict) -> dict: - return await self._conn.send_request(f"_{method}", params) - - async def extNotification(self, method: str, params: dict) -> None: - await self._conn.send_notification(f"_{method}", params) - - -class ClientSideConnection: - """ - Client-side connection. Use when you implement the Client and need to talk to an Agent. - - Parameters: - - to_client: factory that receives this connection and returns your Client implementation - - input: asyncio.StreamWriter (local -> peer) - - output: asyncio.StreamReader (peer -> local) - """ - - def __init__( - self, - to_client: Callable[[Agent], Client], - input_stream: Any, - output_stream: Any, - ) -> None: - if not isinstance(input_stream, asyncio.StreamWriter) or not isinstance(output_stream, asyncio.StreamReader): - raise TypeError(_CLIENT_CONNECTION_ERROR) - - # Build client first so handler can delegate - client = to_client(self) # type: ignore[arg-type] - handler = self._create_handler(client) - self._conn = Connection(handler, input_stream, output_stream) - - def _create_handler(self, client: Client) -> MethodHandler: - """Create the method handler for client-side connection.""" - - async def handler(method: str, params: Any, is_notification: bool) -> Any: - return await self._handle_client_method(client, method, params, is_notification) - - return handler - - async def _handle_client_method(self, client: Client, method: str, params: Any, is_notification: bool) -> Any: - """Handle client method calls.""" - # Core session/file methods - result = await self._handle_client_core_methods(client, method, params) - if result is not _NO_MATCH: - return result - # Terminal methods - result = await self._handle_client_terminal_methods(client, method, params) - if result is not _NO_MATCH: - return result - # Extension methods/notifications - result = await self._handle_client_extension_methods(client, method, params, is_notification) - if result is not _NO_MATCH: - return result - raise RequestError.method_not_found(method) - - async def _handle_client_core_methods(self, client: Client, method: str, params: Any) -> Any: - if method == CLIENT_METHODS["fs_write_text_file"]: - p = WriteTextFileRequest.model_validate(params) - return await client.writeTextFile(p) - if method == CLIENT_METHODS["fs_read_text_file"]: - p = ReadTextFileRequest.model_validate(params) - return await client.readTextFile(p) - if method == CLIENT_METHODS["session_request_permission"]: - p = RequestPermissionRequest.model_validate(params) - return await client.requestPermission(p) - if method == CLIENT_METHODS["session_update"]: - p = SessionNotification.model_validate(params) - return await client.sessionUpdate(p) - return _NO_MATCH - - async def _handle_client_terminal_methods(self, client: Client, method: str, params: Any) -> Any: - result = await self._handle_client_terminal_basic(client, method, params) - if result is not _NO_MATCH: - return result - result = await self._handle_client_terminal_lifecycle(client, method, params) - if result is not _NO_MATCH: - return result - return _NO_MATCH - - async def _handle_client_terminal_basic(self, client: Client, method: str, params: Any) -> Any: - if method == CLIENT_METHODS["terminal_create"]: - if hasattr(client, "createTerminal"): - p = CreateTerminalRequest.model_validate(params) - return await client.createTerminal(p) - return None # TS returns null when optional method missing - if method == CLIENT_METHODS["terminal_output"]: - if hasattr(client, "terminalOutput"): - p = TerminalOutputRequest.model_validate(params) - return await client.terminalOutput(p) - return None - return _NO_MATCH - - async def _handle_client_terminal_lifecycle(self, client: Client, method: str, params: Any) -> Any: - if method == CLIENT_METHODS["terminal_release"]: - if hasattr(client, "releaseTerminal"): - p = ReleaseTerminalRequest.model_validate(params) - result = await client.releaseTerminal(p) - return result.model_dump() if isinstance(result, BaseModel) else (result or {}) - return {} # TS returns {} for void optional methods - if method == CLIENT_METHODS["terminal_wait_for_exit"]: - if hasattr(client, "waitForTerminalExit"): - p = WaitForTerminalExitRequest.model_validate(params) - return await client.waitForTerminalExit(p) - return None - if method == CLIENT_METHODS["terminal_kill"]: - if hasattr(client, "killTerminal"): - p = KillTerminalCommandRequest.model_validate(params) - result = await client.killTerminal(p) - return result.model_dump() if isinstance(result, BaseModel) else (result or {}) - return {} # TS returns {} for void optional methods - return _NO_MATCH - - async def _handle_client_extension_methods( - self, client: Client, method: str, params: Any, is_notification: bool - ) -> Any: - if isinstance(method, str) and method.startswith("_"): - ext_name = method[1:] - if is_notification: - if hasattr(client, "extNotification"): - await client.extNotification(ext_name, params or {}) # type: ignore[arg-type] - return None - return None - else: - if hasattr(client, "extMethod"): - return await client.extMethod(ext_name, params or {}) # type: ignore[arg-type] - return _NO_MATCH - return _NO_MATCH - - # agent-bound methods (client -> agent) - async def initialize(self, params: InitializeRequest) -> InitializeResponse: - resp = await self._conn.send_request( - AGENT_METHODS["initialize"], - params.model_dump(exclude_none=True, exclude_defaults=True), - ) - return InitializeResponse.model_validate(resp) - - async def newSession(self, params: NewSessionRequest) -> NewSessionResponse: - resp = await self._conn.send_request( - AGENT_METHODS["session_new"], - params.model_dump(exclude_none=True, exclude_defaults=True), - ) - return NewSessionResponse.model_validate(resp) - - async def loadSession(self, params: LoadSessionRequest) -> LoadSessionResponse: - resp = await self._conn.send_request( - AGENT_METHODS["session_load"], - params.model_dump(exclude_none=True, exclude_defaults=True), - ) - payload = resp if isinstance(resp, dict) else {} - return LoadSessionResponse.model_validate(payload) - - async def setSessionMode(self, params: SetSessionModeRequest) -> SetSessionModeResponse: - resp = await self._conn.send_request( - AGENT_METHODS["session_set_mode"], - params.model_dump(exclude_none=True, exclude_defaults=True), - ) - payload = resp if isinstance(resp, dict) else {} - return SetSessionModeResponse.model_validate(payload) - - async def setSessionModel(self, params: SetSessionModelRequest) -> SetSessionModelResponse: - resp = await self._conn.send_request( - AGENT_METHODS["session_set_model"], - params.model_dump(exclude_none=True, exclude_defaults=True), - ) - payload = resp if isinstance(resp, dict) else {} - return SetSessionModelResponse.model_validate(payload) - - async def authenticate(self, params: AuthenticateRequest) -> AuthenticateResponse: - resp = await self._conn.send_request( - AGENT_METHODS["authenticate"], - params.model_dump(exclude_none=True, exclude_defaults=True), - ) - payload = resp if isinstance(resp, dict) else {} - return AuthenticateResponse.model_validate(payload) - - async def prompt(self, params: PromptRequest) -> PromptResponse: - resp = await self._conn.send_request( - AGENT_METHODS["session_prompt"], - params.model_dump(exclude_none=True, exclude_defaults=True), - ) - return PromptResponse.model_validate(resp) - - async def cancel(self, params: CancelNotification) -> None: - await self._conn.send_notification( - AGENT_METHODS["session_cancel"], - params.model_dump(exclude_none=True, exclude_defaults=True), - ) - - async def extMethod(self, method: str, params: dict) -> dict: - return await self._conn.send_request(f"_{method}", params) - - async def extNotification(self, method: str, params: dict) -> None: - await self._conn.send_notification(f"_{method}", params) - - -class TerminalHandle: - def __init__(self, terminal_id: str, session_id: str, conn: Connection) -> None: - self.id = terminal_id - self._session_id = session_id - self._conn = conn - - async def current_output(self) -> TerminalOutputResponse: - resp = await self._conn.send_request( - CLIENT_METHODS["terminal_output"], - {"sessionId": self._session_id, "terminalId": self.id}, - ) - return TerminalOutputResponse.model_validate(resp) - - async def wait_for_exit(self) -> WaitForTerminalExitResponse: - resp = await self._conn.send_request( - CLIENT_METHODS["terminal_wait_for_exit"], - {"sessionId": self._session_id, "terminalId": self.id}, - ) - return WaitForTerminalExitResponse.model_validate(resp) - - async def kill(self) -> KillTerminalCommandResponse: - resp = await self._conn.send_request( - CLIENT_METHODS["terminal_kill"], - {"sessionId": self._session_id, "terminalId": self.id}, - ) - payload = resp if isinstance(resp, dict) else {} - return KillTerminalCommandResponse.model_validate(payload) +from __future__ import annotations - async def release(self) -> ReleaseTerminalResponse: - resp = await self._conn.send_request( - CLIENT_METHODS["terminal_release"], - {"sessionId": self._session_id, "terminalId": self.id}, - ) - payload = resp if isinstance(resp, dict) else {} - return ReleaseTerminalResponse.model_validate(payload) +from .agent.connection import AgentSideConnection +from .client.connection import ClientSideConnection +from .connection import Connection, JsonValue, MethodHandler +from .exceptions import RequestError +from .interfaces import Agent, Client +from .terminal import TerminalHandle + +__all__ = [ + "Agent", + "AgentSideConnection", + "Client", + "ClientSideConnection", + "Connection", + "JsonValue", + "MethodHandler", + "RequestError", + "TerminalHandle", +] diff --git a/src/acp/exceptions.py b/src/acp/exceptions.py new file mode 100644 index 0000000..c9a4dd9 --- /dev/null +++ b/src/acp/exceptions.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from typing import Any + +__all__ = ["RequestError"] + + +class RequestError(Exception): + """JSON-RPC 2.0 error helper.""" + + def __init__(self, code: int, message: str, data: Any | None = None) -> None: + super().__init__(message) + self.code = code + self.data = data + + @staticmethod + def parse_error(data: dict | None = None) -> RequestError: + return RequestError(-32700, "Parse error", data) + + @staticmethod + def invalid_request(data: dict | None = None) -> RequestError: + return RequestError(-32600, "Invalid request", data) + + @staticmethod + def method_not_found(method: str) -> RequestError: + return RequestError(-32601, "Method not found", {"method": method}) + + @staticmethod + def invalid_params(data: dict | None = None) -> RequestError: + return RequestError(-32602, "Invalid params", data) + + @staticmethod + def internal_error(data: dict | None = None) -> RequestError: + return RequestError(-32603, "Internal error", data) + + @staticmethod + def auth_required(data: dict | None = None) -> RequestError: + return RequestError(-32000, "Authentication required", data) + + @staticmethod + def resource_not_found(uri: str | None = None) -> RequestError: + data = {"uri": uri} if uri is not None else None + return RequestError(-32002, "Resource not found", data) + + def to_error_obj(self) -> dict[str, Any]: + return {"code": self.code, "message": str(self), "data": self.data} diff --git a/src/acp/helpers.py b/src/acp/helpers.py new file mode 100644 index 0000000..8514da2 --- /dev/null +++ b/src/acp/helpers.py @@ -0,0 +1,257 @@ +from __future__ import annotations + +from collections.abc import Iterable, Sequence +from typing import Any + +from .schema import ( + AgentMessageChunk, + AgentPlanUpdate, + AgentThoughtChunk, + AudioContentBlock, + BlobResourceContents, + ContentToolCallContent, + EmbeddedResource, + EmbeddedResourceContentBlock, + FileEditToolCallContent, + ImageContentBlock, + PlanEntry, + PlanEntryPriority, + PlanEntryStatus, + ResourceContentBlock, + SessionNotification, + TerminalToolCallContent, + TextContentBlock, + TextResourceContents, + ToolCallLocation, + ToolCallProgress, + ToolCallStart, + ToolCallStatus, + ToolKind, + UserMessageChunk, +) + +ContentBlock = ( + TextContentBlock | ImageContentBlock | AudioContentBlock | ResourceContentBlock | EmbeddedResourceContentBlock +) + +SessionUpdate = ( + AgentMessageChunk | AgentPlanUpdate | AgentThoughtChunk | UserMessageChunk | ToolCallStart | ToolCallProgress +) + +ToolCallContentVariant = ContentToolCallContent | FileEditToolCallContent | TerminalToolCallContent + +__all__ = [ + "audio_block", + "embedded_blob_resource", + "embedded_text_resource", + "image_block", + "plan_entry", + "resource_block", + "resource_link_block", + "session_notification", + "start_edit_tool_call", + "start_read_tool_call", + "start_tool_call", + "text_block", + "tool_content", + "tool_diff_content", + "tool_terminal_ref", + "update_agent_message", + "update_agent_message_text", + "update_agent_thought", + "update_agent_thought_text", + "update_plan", + "update_tool_call", + "update_user_message", + "update_user_message_text", +] + + +def text_block(text: str) -> TextContentBlock: + return TextContentBlock(type="text", text=text) + + +def image_block(data: str, mime_type: str, *, uri: str | None = None) -> ImageContentBlock: + return ImageContentBlock(type="image", data=data, mimeType=mime_type, uri=uri) + + +def audio_block(data: str, mime_type: str) -> AudioContentBlock: + return AudioContentBlock(type="audio", data=data, mimeType=mime_type) + + +def resource_link_block( + name: str, + uri: str, + *, + mime_type: str | None = None, + size: int | None = None, + description: str | None = None, + title: str | None = None, +) -> ResourceContentBlock: + return ResourceContentBlock( + type="resource_link", + name=name, + uri=uri, + mimeType=mime_type, + size=size, + description=description, + title=title, + ) + + +def embedded_text_resource(uri: str, text: str, *, mime_type: str | None = None) -> EmbeddedResource: + return EmbeddedResource(resource=TextResourceContents(uri=uri, text=text, mimeType=mime_type)) + + +def embedded_blob_resource(uri: str, blob: str, *, mime_type: str | None = None) -> EmbeddedResource: + return EmbeddedResource(resource=BlobResourceContents(uri=uri, blob=blob, mimeType=mime_type)) + + +def resource_block( + resource: EmbeddedResource | TextResourceContents | BlobResourceContents, +) -> EmbeddedResourceContentBlock: + resource_obj = resource.resource if isinstance(resource, EmbeddedResource) else resource + return EmbeddedResourceContentBlock(type="resource", resource=resource_obj) + + +def tool_content(block: ContentBlock) -> ContentToolCallContent: + return ContentToolCallContent(type="content", content=block) + + +def tool_diff_content(path: str, new_text: str, old_text: str | None = None) -> FileEditToolCallContent: + return FileEditToolCallContent(type="diff", path=path, newText=new_text, oldText=old_text) + + +def tool_terminal_ref(terminal_id: str) -> TerminalToolCallContent: + return TerminalToolCallContent(type="terminal", terminalId=terminal_id) + + +def plan_entry( + content: str, + *, + priority: PlanEntryPriority = "medium", + status: PlanEntryStatus = "pending", +) -> PlanEntry: + return PlanEntry(content=content, priority=priority, status=status) + + +def update_plan(entries: Iterable[PlanEntry]) -> AgentPlanUpdate: + return AgentPlanUpdate(sessionUpdate="plan", entries=list(entries)) + + +def update_user_message(content: ContentBlock) -> UserMessageChunk: + return UserMessageChunk(sessionUpdate="user_message_chunk", content=content) + + +def update_user_message_text(text: str) -> UserMessageChunk: + return update_user_message(text_block(text)) + + +def update_agent_message(content: ContentBlock) -> AgentMessageChunk: + return AgentMessageChunk(sessionUpdate="agent_message_chunk", content=content) + + +def update_agent_message_text(text: str) -> AgentMessageChunk: + return update_agent_message(text_block(text)) + + +def update_agent_thought(content: ContentBlock) -> AgentThoughtChunk: + return AgentThoughtChunk(sessionUpdate="agent_thought_chunk", content=content) + + +def update_agent_thought_text(text: str) -> AgentThoughtChunk: + return update_agent_thought(text_block(text)) + + +def session_notification(session_id: str, update: SessionUpdate) -> SessionNotification: + return SessionNotification(sessionId=session_id, update=update) + + +def start_tool_call( + tool_call_id: str, + title: str, + *, + kind: ToolKind | None = None, + status: ToolCallStatus | None = None, + content: Sequence[ToolCallContentVariant] | None = None, + locations: Sequence[ToolCallLocation] | None = None, + raw_input: Any | None = None, + raw_output: Any | None = None, +) -> ToolCallStart: + return ToolCallStart( + sessionUpdate="tool_call", + toolCallId=tool_call_id, + title=title, + kind=kind, + status=status, + content=list(content) if content is not None else None, + locations=list(locations) if locations is not None else None, + rawInput=raw_input, + rawOutput=raw_output, + ) + + +def start_read_tool_call( + tool_call_id: str, + title: str, + path: str, + *, + extra_options: Sequence[ToolCallContentVariant] | None = None, +) -> ToolCallStart: + content = list(extra_options) if extra_options is not None else None + locations = [ToolCallLocation(path=path)] + raw_input = {"path": path} + return start_tool_call( + tool_call_id, + title, + kind="read", + status="pending", + content=content, + locations=locations, + raw_input=raw_input, + ) + + +def start_edit_tool_call( + tool_call_id: str, + title: str, + path: str, + content: Any, + *, + extra_options: Sequence[ToolCallContentVariant] | None = None, +) -> ToolCallStart: + locations = [ToolCallLocation(path=path)] + raw_input = {"path": path, "content": content} + return start_tool_call( + tool_call_id, + title, + kind="edit", + status="pending", + content=list(extra_options) if extra_options is not None else None, + locations=locations, + raw_input=raw_input, + ) + + +def update_tool_call( + tool_call_id: str, + *, + title: str | None = None, + kind: ToolKind | None = None, + status: ToolCallStatus | None = None, + content: Sequence[ToolCallContentVariant] | None = None, + locations: Sequence[ToolCallLocation] | None = None, + raw_input: Any | None = None, + raw_output: Any | None = None, +) -> ToolCallProgress: + return ToolCallProgress( + sessionUpdate="tool_call_update", + toolCallId=tool_call_id, + title=title, + kind=kind, + status=status, + content=list(content) if content is not None else None, + locations=list(locations) if locations is not None else None, + rawInput=raw_input, + rawOutput=raw_output, + ) diff --git a/src/acp/interfaces.py b/src/acp/interfaces.py new file mode 100644 index 0000000..11d04d3 --- /dev/null +++ b/src/acp/interfaces.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +from typing import Any, Protocol + +from .schema import ( + AuthenticateRequest, + AuthenticateResponse, + CancelNotification, + CreateTerminalRequest, + CreateTerminalResponse, + InitializeRequest, + InitializeResponse, + KillTerminalCommandRequest, + KillTerminalCommandResponse, + LoadSessionRequest, + LoadSessionResponse, + NewSessionRequest, + NewSessionResponse, + PromptRequest, + PromptResponse, + ReadTextFileRequest, + ReadTextFileResponse, + ReleaseTerminalRequest, + ReleaseTerminalResponse, + RequestPermissionRequest, + RequestPermissionResponse, + SessionNotification, + SetSessionModelRequest, + SetSessionModelResponse, + SetSessionModeRequest, + SetSessionModeResponse, + TerminalOutputRequest, + TerminalOutputResponse, + WaitForTerminalExitRequest, + WaitForTerminalExitResponse, + WriteTextFileRequest, + WriteTextFileResponse, +) + +__all__ = ["Agent", "Client"] + + +class Client(Protocol): + async def requestPermission(self, params: RequestPermissionRequest) -> RequestPermissionResponse: ... + + async def sessionUpdate(self, params: SessionNotification) -> None: ... + + async def writeTextFile(self, params: WriteTextFileRequest) -> WriteTextFileResponse | None: ... + + async def readTextFile(self, params: ReadTextFileRequest) -> ReadTextFileResponse: ... + + async def createTerminal(self, params: CreateTerminalRequest) -> CreateTerminalResponse: ... + + async def terminalOutput(self, params: TerminalOutputRequest) -> TerminalOutputResponse: ... + + async def releaseTerminal(self, params: ReleaseTerminalRequest) -> ReleaseTerminalResponse | None: ... + + async def waitForTerminalExit(self, params: WaitForTerminalExitRequest) -> WaitForTerminalExitResponse: ... + + async def killTerminal(self, params: KillTerminalCommandRequest) -> KillTerminalCommandResponse | None: ... + + async def extMethod(self, method: str, params: dict[str, Any]) -> dict[str, Any]: ... + + async def extNotification(self, method: str, params: dict[str, Any]) -> None: ... + + +class Agent(Protocol): + async def initialize(self, params: InitializeRequest) -> InitializeResponse: ... + + async def newSession(self, params: NewSessionRequest) -> NewSessionResponse: ... + + async def loadSession(self, params: LoadSessionRequest) -> LoadSessionResponse | None: ... + + async def setSessionMode(self, params: SetSessionModeRequest) -> SetSessionModeResponse | None: ... + + async def setSessionModel(self, params: SetSessionModelRequest) -> SetSessionModelResponse | None: ... + + async def authenticate(self, params: AuthenticateRequest) -> AuthenticateResponse | None: ... + + async def prompt(self, params: PromptRequest) -> PromptResponse: ... + + async def cancel(self, params: CancelNotification) -> None: ... + + async def extMethod(self, method: str, params: dict[str, Any]) -> dict[str, Any]: ... + + async def extNotification(self, method: str, params: dict[str, Any]) -> None: ... diff --git a/src/acp/meta.py b/src/acp/meta.py index 95db6c0..f436615 100644 --- a/src/acp/meta.py +++ b/src/acp/meta.py @@ -1,5 +1,5 @@ # Generated from schema/meta.json. Do not edit by hand. -# Schema ref: refs/tags/v0.4.5 +# Schema ref: refs/tags/v0.4.9 AGENT_METHODS = {'authenticate': 'authenticate', 'initialize': 'initialize', 'session_cancel': 'session/cancel', 'session_load': 'session/load', 'session_new': 'session/new', 'session_prompt': 'session/prompt', 'session_set_mode': 'session/set_mode', 'session_set_model': 'session/set_model'} CLIENT_METHODS = {'fs_read_text_file': 'fs/read_text_file', 'fs_write_text_file': 'fs/write_text_file', 'session_request_permission': 'session/request_permission', 'session_update': 'session/update', 'terminal_create': 'terminal/create', 'terminal_kill': 'terminal/kill', 'terminal_output': 'terminal/output', 'terminal_release': 'terminal/release', 'terminal_wait_for_exit': 'terminal/wait_for_exit'} PROTOCOL_VERSION = 1 diff --git a/src/acp/router.py b/src/acp/router.py new file mode 100644 index 0000000..f50f9b7 --- /dev/null +++ b/src/acp/router.py @@ -0,0 +1,192 @@ +from __future__ import annotations + +from collections.abc import Awaitable, Callable, Mapping, Sequence +from dataclasses import dataclass +from typing import Any, Literal + +from pydantic import BaseModel + +from .exceptions import RequestError + +__all__ = [ + "MessageRouter", + "Route", + "RouterBuilder", + "attribute_handler", +] + + +AsyncHandler = Callable[[Any], Awaitable[Any | None]] + + +@dataclass(slots=True) +class Route: + method: str + model: type[BaseModel] + handle: Callable[[], AsyncHandler | None] + kind: Literal["request", "notification"] + optional: bool = False + default_result: Any = None + adapt_result: Callable[[Any | None], Any] | None = None + + +class MessageRouter: + def __init__( + self, + routes: Sequence[Route], + *, + request_extensions: Callable[[str, dict[str, Any]], Awaitable[Any]] | None = None, + notification_extensions: Callable[[str, dict[str, Any]], Awaitable[None]] | None = None, + ) -> None: + self._requests: Mapping[str, Route] = {route.method: route for route in routes if route.kind == "request"} + self._notifications: Mapping[str, Route] = { + route.method: route for route in routes if route.kind == "notification" + } + self._request_extensions = request_extensions + self._notification_extensions = notification_extensions + + async def dispatch_request(self, method: str, params: Any | None) -> Any: + if isinstance(method, str) and method.startswith("_"): + if self._request_extensions is None: + raise RequestError.method_not_found(method) + payload = params if isinstance(params, dict) else {} + return await self._request_extensions(method[1:], payload) + + route = self._requests.get(method) + if route is None: + raise RequestError.method_not_found(method) + model = route.model + parsed = model.model_validate(params) + + handler = route.handle() + if handler is None: + if route.optional: + return route.default_result + raise RequestError.method_not_found(method) + + result = await handler(parsed) + if route.adapt_result is not None: + return route.adapt_result(result) + return result + + async def dispatch_notification(self, method: str, params: Any | None) -> None: + if isinstance(method, str) and method.startswith("_"): + if self._notification_extensions is None: + return + payload = params if isinstance(params, dict) else {} + await self._notification_extensions(method[1:], payload) + return + + route = self._notifications.get(method) + if route is None: + raise RequestError.method_not_found(method) + model = route.model + parsed = model.model_validate(params) + + handler = route.handle() + if handler is None: + if route.optional: + return + raise RequestError.method_not_found(method) + await handler(parsed) + + +class RouterBuilder: + def __init__(self) -> None: + self._routes: list[Route] = [] + + def request( + self, + method: str, + model: type[BaseModel], + *, + optional: bool = False, + default_result: Any = None, + adapt_result: Callable[[Any | None], Any] | None = None, + ) -> Callable[[Callable[[], AsyncHandler | None]], Callable[[], AsyncHandler | None]]: + def decorator(factory: Callable[[], AsyncHandler | None]) -> Callable[[], AsyncHandler | None]: + self._routes.append( + Route( + method=method, + model=model, + handle=factory, + kind="request", + optional=optional, + default_result=default_result, + adapt_result=adapt_result, + ) + ) + return factory + + return decorator + + def notification( + self, + method: str, + model: type[BaseModel], + *, + optional: bool = False, + ) -> Callable[[Callable[[], AsyncHandler | None]], Callable[[], AsyncHandler | None]]: + def decorator(factory: Callable[[], AsyncHandler | None]) -> Callable[[], AsyncHandler | None]: + self._routes.append( + Route( + method=method, + model=model, + handle=factory, + kind="notification", + optional=optional, + ) + ) + return factory + + return decorator + + def build( + self, + *, + request_extensions: Callable[[str, dict[str, Any]], Awaitable[Any]] | None = None, + notification_extensions: Callable[[str, dict[str, Any]], Awaitable[None]] | None = None, + ) -> MessageRouter: + return MessageRouter( + routes=self._routes, + request_extensions=request_extensions, + notification_extensions=notification_extensions, + ) + + def request_attr( + self, + method: str, + model: type[BaseModel], + obj: Any, + attr: str, + *, + optional: bool = False, + default_result: Any = None, + adapt_result: Callable[[Any | None], Any] | None = None, + ) -> None: + self.request( + method, + model, + optional=optional, + default_result=default_result, + adapt_result=adapt_result, + )(attribute_handler(obj, attr)) + + def notification_attr( + self, + method: str, + model: type[BaseModel], + obj: Any, + attr: str, + *, + optional: bool = False, + ) -> None: + self.notification(method, model, optional=optional)(attribute_handler(obj, attr)) + + +def attribute_handler(obj: Any, attr: str) -> Callable[[], AsyncHandler | None]: + def factory() -> AsyncHandler | None: + func = getattr(obj, attr, None) + return func if callable(func) else None + + return factory diff --git a/src/acp/schema.py b/src/acp/schema.py index 13f1c66..3f5386e 100644 --- a/src/acp/schema.py +++ b/src/acp/schema.py @@ -1,5 +1,5 @@ # Generated from schema/schema.json. Do not edit by hand. -# Schema ref: refs/tags/v0.4.5 +# Schema ref: refs/tags/v0.4.9 from __future__ import annotations @@ -9,11 +9,22 @@ from pydantic import BaseModel, Field, RootModel +PermissionOptionKind = Literal["allow_once", "allow_always", "reject_once", "reject_always"] +PlanEntryPriority = Literal["high", "medium", "low"] +PlanEntryStatus = Literal["pending", "in_progress", "completed"] +StopReason = Literal["end_turn", "max_tokens", "max_turn_requests", "refusal", "cancelled"] +ToolCallStatus = Literal["pending", "in_progress", "completed", "failed"] +ToolKind = Literal["read", "edit", "delete", "move", "search", "execute", "think", "fetch", "switch_mode", "other"] + + class AuthenticateRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The ID of the authentication method to use. + # Must be one of the methods advertised in the initialize response. methodId: Annotated[ str, Field( @@ -23,6 +34,7 @@ class AuthenticateRequest(BaseModel): class AuthenticateResponse(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -30,6 +42,7 @@ class AuthenticateResponse(BaseModel): class CommandInputHint(BaseModel): + # A hint to display when the input hasn't been provided yet hint: Annotated[ str, Field(description="A hint to display when the input hasn't been provided yet"), @@ -37,6 +50,7 @@ class CommandInputHint(BaseModel): class AvailableCommandInput(RootModel[CommandInputHint]): + # The input specification for a command. root: Annotated[ CommandInputHint, Field(description="The input specification for a command."), @@ -44,6 +58,7 @@ class AvailableCommandInput(RootModel[CommandInputHint]): class BlobResourceContents(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -54,31 +69,39 @@ class BlobResourceContents(BaseModel): class CreateTerminalResponse(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The unique identifier for the created terminal. terminalId: Annotated[str, Field(description="The unique identifier for the created terminal.")] class EnvVariable(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The name of the environment variable. name: Annotated[str, Field(description="The name of the environment variable.")] + # The value to set for the environment variable. value: Annotated[str, Field(description="The value to set for the environment variable.")] class FileSystemCapability(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Whether the Client supports `fs/read_text_file` requests. readTextFile: Annotated[ Optional[bool], Field(description="Whether the Client supports `fs/read_text_file` requests."), ] = False + # Whether the Client supports `fs/write_text_file` requests. writeTextFile: Annotated[ Optional[bool], Field(description="Whether the Client supports `fs/write_text_file` requests."), @@ -86,15 +109,19 @@ class FileSystemCapability(BaseModel): class HttpHeader(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The name of the HTTP header. name: Annotated[str, Field(description="The name of the HTTP header.")] + # The value to set for the HTTP header. value: Annotated[str, Field(description="The value to set for the HTTP header.")] class KillTerminalCommandResponse(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -102,66 +129,86 @@ class KillTerminalCommandResponse(BaseModel): class McpCapabilities(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Agent supports [`McpServer::Http`]. http: Annotated[Optional[bool], Field(description="Agent supports [`McpServer::Http`].")] = False + # Agent supports [`McpServer::Sse`]. sse: Annotated[Optional[bool], Field(description="Agent supports [`McpServer::Sse`].")] = False class HttpMcpServer(BaseModel): + # HTTP headers to set when making requests to the MCP server. headers: Annotated[ List[HttpHeader], Field(description="HTTP headers to set when making requests to the MCP server."), ] + # Human-readable name identifying this MCP server. name: Annotated[str, Field(description="Human-readable name identifying this MCP server.")] type: Literal["http"] + # URL to the MCP server. url: Annotated[str, Field(description="URL to the MCP server.")] class SseMcpServer(BaseModel): + # HTTP headers to set when making requests to the MCP server. headers: Annotated[ List[HttpHeader], Field(description="HTTP headers to set when making requests to the MCP server."), ] + # Human-readable name identifying this MCP server. name: Annotated[str, Field(description="Human-readable name identifying this MCP server.")] type: Literal["sse"] + # URL to the MCP server. url: Annotated[str, Field(description="URL to the MCP server.")] class StdioMcpServer(BaseModel): + # Command-line arguments to pass to the MCP server. args: Annotated[ List[str], Field(description="Command-line arguments to pass to the MCP server."), ] + # Path to the MCP server executable. command: Annotated[str, Field(description="Path to the MCP server executable.")] + # Environment variables to set when launching the MCP server. env: Annotated[ List[EnvVariable], Field(description="Environment variables to set when launching the MCP server."), ] + # Human-readable name identifying this MCP server. name: Annotated[str, Field(description="Human-readable name identifying this MCP server.")] class ModelInfo(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Optional description of the model. description: Annotated[Optional[str], Field(description="Optional description of the model.")] = None + # Unique identifier for the model. modelId: Annotated[str, Field(description="Unique identifier for the model.")] + # Human-readable name of the model. name: Annotated[str, Field(description="Human-readable name of the model.")] class NewSessionRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The working directory for this session. Must be an absolute path. cwd: Annotated[ str, Field(description="The working directory for this session. Must be an absolute path."), ] + # List of MCP (Model Context Protocol) servers the agent should connect to. mcpServers: Annotated[ List[Union[HttpMcpServer, SseMcpServer, StdioMcpServer]], Field(description="List of MCP (Model Context Protocol) servers the agent should connect to."), @@ -169,21 +216,29 @@ class NewSessionRequest(BaseModel): class PromptCapabilities(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Agent supports [`ContentBlock::Audio`]. audio: Annotated[Optional[bool], Field(description="Agent supports [`ContentBlock::Audio`].")] = False + # Agent supports embedded context in `session/prompt` requests. + # + # When enabled, the Client is allowed to include [`ContentBlock::Resource`] + # in prompt requests for pieces of context that are referenced in the message. embeddedContext: Annotated[ Optional[bool], Field( description="Agent supports embedded context in `session/prompt` requests.\n\nWhen enabled, the Client is allowed to include [`ContentBlock::Resource`]\nin prompt requests for pieces of context that are referenced in the message." ), ] = False + # Agent supports [`ContentBlock::Image`]. image: Annotated[Optional[bool], Field(description="Agent supports [`ContentBlock::Image`].")] = False class ReadTextFileResponse(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -192,6 +247,7 @@ class ReadTextFileResponse(BaseModel): class ReleaseTerminalResponse(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -203,15 +259,18 @@ class DeniedOutcome(BaseModel): class AllowedOutcome(BaseModel): + # The ID of the option the user selected. optionId: Annotated[str, Field(description="The ID of the option the user selected.")] outcome: Literal["selected"] class RequestPermissionResponse(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The user's decision on the permission request. outcome: Annotated[ Union[DeniedOutcome, AllowedOutcome], Field(description="The user's decision on the permission request."), @@ -224,25 +283,32 @@ class Role(Enum): class SessionModelState(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The set of models that the Agent can use availableModels: Annotated[List[ModelInfo], Field(description="The set of models that the Agent can use")] + # The current model the Agent is in. currentModelId: Annotated[str, Field(description="The current model the Agent is in.")] class CurrentModeUpdate(BaseModel): + # Unique identifier for a Session Mode. currentModeId: Annotated[str, Field(description="Unique identifier for a Session Mode.")] sessionUpdate: Literal["current_mode_update"] class SetSessionModeRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The ID of the mode to set. modeId: Annotated[str, Field(description="The ID of the mode to set.")] + # The ID of the session to set the mode for. sessionId: Annotated[str, Field(description="The ID of the session to set the mode for.")] @@ -251,15 +317,19 @@ class SetSessionModeResponse(BaseModel): class SetSessionModelRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The ID of the model to set. modelId: Annotated[str, Field(description="The ID of the model to set.")] + # The ID of the session to set the model for. sessionId: Annotated[str, Field(description="The ID of the session to set the model for.")] class SetSessionModelResponse(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -267,10 +337,12 @@ class SetSessionModelResponse(BaseModel): class TerminalExitStatus(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The process exit code (may be null if terminated by signal). exitCode: Annotated[ Optional[int], Field( @@ -278,6 +350,7 @@ class TerminalExitStatus(BaseModel): ge=0, ), ] = None + # The signal that terminated the process (may be null if exited normally). signal: Annotated[ Optional[str], Field(description="The signal that terminated the process (may be null if exited normally)."), @@ -285,28 +358,36 @@ class TerminalExitStatus(BaseModel): class TerminalOutputRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The session ID for this request. sessionId: Annotated[str, Field(description="The session ID for this request.")] + # The ID of the terminal to get output from. terminalId: Annotated[str, Field(description="The ID of the terminal to get output from.")] class TerminalOutputResponse(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Exit status if the command has completed. exitStatus: Annotated[ Optional[TerminalExitStatus], Field(description="Exit status if the command has completed."), ] = None + # The terminal output captured so far. output: Annotated[str, Field(description="The terminal output captured so far.")] + # Whether the output was truncated due to byte limits. truncated: Annotated[bool, Field(description="Whether the output was truncated due to byte limits.")] class TextResourceContents(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -317,12 +398,16 @@ class TextResourceContents(BaseModel): class FileEditToolCallContent(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The new content after modification. newText: Annotated[str, Field(description="The new content after modification.")] + # The original content (None for new files). oldText: Annotated[Optional[str], Field(description="The original content (None for new files).")] = None + # The file path being modified. path: Annotated[str, Field(description="The file path being modified.")] type: Literal["diff"] @@ -333,28 +418,36 @@ class TerminalToolCallContent(BaseModel): class ToolCallLocation(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Optional line number within the file. line: Annotated[Optional[int], Field(description="Optional line number within the file.", ge=0)] = None + # The file path being accessed or modified. path: Annotated[str, Field(description="The file path being accessed or modified.")] class WaitForTerminalExitRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The session ID for this request. sessionId: Annotated[str, Field(description="The session ID for this request.")] + # The ID of the terminal to wait for. terminalId: Annotated[str, Field(description="The ID of the terminal to wait for.")] class WaitForTerminalExitResponse(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The process exit code (may be null if terminated by signal). exitCode: Annotated[ Optional[int], Field( @@ -362,6 +455,7 @@ class WaitForTerminalExitResponse(BaseModel): ge=0, ), ] = None + # The signal that terminated the process (may be null if exited normally). signal: Annotated[ Optional[str], Field(description="The signal that terminated the process (may be null if exited normally)."), @@ -369,16 +463,21 @@ class WaitForTerminalExitResponse(BaseModel): class WriteTextFileRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The text content to write to the file. content: Annotated[str, Field(description="The text content to write to the file.")] + # Absolute path to the file to write. path: Annotated[str, Field(description="Absolute path to the file to write.")] + # The session ID for this request. sessionId: Annotated[str, Field(description="The session ID for this request.")] class WriteTextFileResponse(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -386,22 +485,27 @@ class WriteTextFileResponse(BaseModel): class AgentCapabilities(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Whether the agent supports `session/load`. loadSession: Annotated[Optional[bool], Field(description="Whether the agent supports `session/load`.")] = False + # MCP capabilities supported by the agent. mcpCapabilities: Annotated[ Optional[McpCapabilities], Field(description="MCP capabilities supported by the agent."), - ] = {"http": False, "sse": False} + ] = McpCapabilities(http=False, sse=False) + # Prompt capabilities supported by the agent. promptCapabilities: Annotated[ Optional[PromptCapabilities], Field(description="Prompt capabilities supported by the agent."), - ] = {"audio": False, "embeddedContext": False, "image": False} + ] = PromptCapabilities(audio=False, embeddedContext=False, image=False) class Annotations(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -412,6 +516,7 @@ class Annotations(BaseModel): class AudioContent(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -422,28 +527,36 @@ class AudioContent(BaseModel): class AuthMethod(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Optional description providing more details about this authentication method. description: Annotated[ Optional[str], Field(description="Optional description providing more details about this authentication method."), ] = None + # Unique identifier for this authentication method. id: Annotated[str, Field(description="Unique identifier for this authentication method.")] + # Human-readable name of the authentication method. name: Annotated[str, Field(description="Human-readable name of the authentication method.")] class AvailableCommand(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Human-readable description of what the command does. description: Annotated[str, Field(description="Human-readable description of what the command does.")] + # Input for the command if required input: Annotated[ Optional[AvailableCommandInput], Field(description="Input for the command if required"), ] = None + # Command name (e.g., `create_plan`, `research_codebase`). name: Annotated[ str, Field(description="Command name (e.g., `create_plan`, `research_codebase`)."), @@ -451,24 +564,30 @@ class AvailableCommand(BaseModel): class CancelNotification(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The ID of the session to cancel operations for. sessionId: Annotated[str, Field(description="The ID of the session to cancel operations for.")] class ClientCapabilities(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # File system capabilities supported by the client. + # Determines which file operations the agent can request. fs: Annotated[ Optional[FileSystemCapability], Field( description="File system capabilities supported by the client.\nDetermines which file operations the agent can request." ), - ] = {"readTextFile": False, "writeTextFile": False} + ] = FileSystemCapability(readTextFile=False, writeTextFile=False) + # Whether the Client support all `terminal/*` methods. terminal: Annotated[ Optional[bool], Field(description="Whether the Client support all `terminal/*` methods."), @@ -476,6 +595,7 @@ class ClientCapabilities(BaseModel): class TextContentBlock(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -486,6 +606,7 @@ class TextContentBlock(BaseModel): class ImageContentBlock(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -498,6 +619,7 @@ class ImageContentBlock(BaseModel): class AudioContentBlock(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -509,6 +631,7 @@ class AudioContentBlock(BaseModel): class ResourceContentBlock(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -524,20 +647,33 @@ class ResourceContentBlock(BaseModel): class CreateTerminalRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Array of command arguments. args: Annotated[Optional[List[str]], Field(description="Array of command arguments.")] = None + # The command to execute. command: Annotated[str, Field(description="The command to execute.")] + # Working directory for the command (absolute path). cwd: Annotated[ Optional[str], Field(description="Working directory for the command (absolute path)."), ] = None + # Environment variables for the command. env: Annotated[ Optional[List[EnvVariable]], Field(description="Environment variables for the command."), ] = None + # Maximum number of output bytes to retain. + # + # When the limit is exceeded, the Client truncates from the beginning of the output + # to stay within the limit. + # + # The Client MUST ensure truncation happens at a character boundary to maintain valid + # string output, even if this means the retained output is slightly less than the + # specified limit. outputByteLimit: Annotated[ Optional[int], Field( @@ -545,10 +681,12 @@ class CreateTerminalRequest(BaseModel): ge=0, ), ] = None + # The session ID for this request. sessionId: Annotated[str, Field(description="The session ID for this request.")] class ImageContent(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -560,14 +698,17 @@ class ImageContent(BaseModel): class InitializeRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Capabilities supported by the client. clientCapabilities: Annotated[ Optional[ClientCapabilities], Field(description="Capabilities supported by the client."), - ] = {"fs": {"readTextFile": False, "writeTextFile": False}, "terminal": False} + ] = ClientCapabilities(fs=FileSystemCapability(readTextFile=False, writeTextFile=False), terminal=False) + # The latest protocol version supported by the client. protocolVersion: Annotated[ int, Field( @@ -579,26 +720,29 @@ class InitializeRequest(BaseModel): class InitializeResponse(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Capabilities supported by the agent. agentCapabilities: Annotated[ Optional[AgentCapabilities], Field(description="Capabilities supported by the agent."), - ] = { - "loadSession": False, - "mcpCapabilities": {"http": False, "sse": False}, - "promptCapabilities": { - "audio": False, - "embeddedContext": False, - "image": False, - }, - } + ] = AgentCapabilities( + loadSession=False, + mcpCapabilities=McpCapabilities(http=False, sse=False), + promptCapabilities=PromptCapabilities(audio=False, embeddedContext=False, image=False), + ) + # Authentication methods supported by the agent. authMethods: Annotated[ Optional[List[AuthMethod]], Field(description="Authentication methods supported by the agent."), ] = [] + # The protocol version the client specified if supported by the agent, + # or the latest protocol version supported by the agent. + # + # The client should disconnect, if it doesn't support this version. protocolVersion: Annotated[ int, Field( @@ -610,87 +754,114 @@ class InitializeResponse(BaseModel): class KillTerminalCommandRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The session ID for this request. sessionId: Annotated[str, Field(description="The session ID for this request.")] + # The ID of the terminal to kill. terminalId: Annotated[str, Field(description="The ID of the terminal to kill.")] class LoadSessionRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The working directory for this session. cwd: Annotated[str, Field(description="The working directory for this session.")] + # List of MCP servers to connect to for this session. mcpServers: Annotated[ List[Union[HttpMcpServer, SseMcpServer, StdioMcpServer]], Field(description="List of MCP servers to connect to for this session."), ] + # The ID of the session to load. sessionId: Annotated[str, Field(description="The ID of the session to load.")] class PermissionOption(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None - kind: Annotated[str, Field(description="Hint about the nature of this permission option.")] + # Hint about the nature of this permission option. + kind: Annotated[PermissionOptionKind, Field(description="Hint about the nature of this permission option.")] + # Human-readable label to display to the user. name: Annotated[str, Field(description="Human-readable label to display to the user.")] + # Unique identifier for this permission option. optionId: Annotated[str, Field(description="Unique identifier for this permission option.")] class PlanEntry(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Human-readable description of what this task aims to accomplish. content: Annotated[ str, Field(description="Human-readable description of what this task aims to accomplish."), ] + # The relative importance of this task. + # Used to indicate which tasks are most critical to the overall goal. priority: Annotated[ - str, + PlanEntryPriority, Field( description="The relative importance of this task.\nUsed to indicate which tasks are most critical to the overall goal." ), ] - status: Annotated[str, Field(description="Current execution status of this task.")] + # Current execution status of this task. + status: Annotated[PlanEntryStatus, Field(description="Current execution status of this task.")] class PromptResponse(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None - stopReason: Annotated[str, Field(description="Indicates why the agent stopped processing the turn.")] + # Indicates why the agent stopped processing the turn. + stopReason: Annotated[StopReason, Field(description="Indicates why the agent stopped processing the turn.")] class ReadTextFileRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Maximum number of lines to read. limit: Annotated[Optional[int], Field(description="Maximum number of lines to read.", ge=0)] = None + # Line number to start reading from (1-based). line: Annotated[ Optional[int], Field(description="Line number to start reading from (1-based).", ge=0), ] = None + # Absolute path to the file to read. path: Annotated[str, Field(description="Absolute path to the file to read.")] + # The session ID for this request. sessionId: Annotated[str, Field(description="The session ID for this request.")] class ReleaseTerminalRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The session ID for this request. sessionId: Annotated[str, Field(description="The session ID for this request.")] + # The ID of the terminal to release. terminalId: Annotated[str, Field(description="The ID of the terminal to release.")] class ResourceLink(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -705,32 +876,42 @@ class ResourceLink(BaseModel): class SessionMode(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None description: Optional[str] = None + # Unique identifier for a Session Mode. id: Annotated[str, Field(description="Unique identifier for a Session Mode.")] name: str class SessionModeState(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The set of modes that the Agent can operate in availableModes: Annotated[ List[SessionMode], Field(description="The set of modes that the Agent can operate in"), ] + # The current mode the Agent is in. currentModeId: Annotated[str, Field(description="The current mode the Agent is in.")] class AgentPlanUpdate(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The list of tasks to be accomplished. + # + # When updating a plan, the agent must send a complete list of all entries + # with their current status. The client replaces the entire plan with each update. entries: Annotated[ List[PlanEntry], Field( @@ -746,6 +927,7 @@ class AvailableCommandsUpdate(BaseModel): class TextContent(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), @@ -755,11 +937,13 @@ class TextContent(BaseModel): class EmbeddedResourceContentBlock(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None annotations: Optional[Annotations] = None + # Resource content that can be embedded in a message. resource: Annotated[ Union[TextResourceContents, BlobResourceContents], Field(description="Resource content that can be embedded in a message."), @@ -768,11 +952,13 @@ class EmbeddedResourceContentBlock(BaseModel): class EmbeddedResource(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None annotations: Optional[Annotations] = None + # Resource content that can be embedded in a message. resource: Annotated[ Union[TextResourceContents, BlobResourceContents], Field(description="Resource content that can be embedded in a message."), @@ -780,16 +966,25 @@ class EmbeddedResource(BaseModel): class LoadSessionResponse(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # **UNSTABLE** + # + # This capability is not part of the spec yet, and may be removed or changed at any point. + # + # Initial model state if supported by the Agent models: Annotated[ Optional[SessionModelState], Field( description="**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nInitial model state if supported by the Agent" ), ] = None + # Initial mode state if supported by the Agent + # + # See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) modes: Annotated[ Optional[SessionModeState], Field( @@ -799,22 +994,34 @@ class LoadSessionResponse(BaseModel): class NewSessionResponse(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # **UNSTABLE** + # + # This capability is not part of the spec yet, and may be removed or changed at any point. + # + # Initial model state if supported by the Agent models: Annotated[ Optional[SessionModelState], Field( description="**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nInitial model state if supported by the Agent" ), ] = None + # Initial mode state if supported by the Agent + # + # See protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes) modes: Annotated[ Optional[SessionModeState], Field( description="Initial mode state if supported by the Agent\n\nSee protocol docs: [Session Modes](https://agentclientprotocol.com/protocol/session-modes)" ), ] = None + # Unique identifier for the created session. + # + # Used in all subsequent requests for this conversation. sessionId: Annotated[ str, Field( @@ -824,10 +1031,15 @@ class NewSessionResponse(BaseModel): class Plan(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The list of tasks to be accomplished. + # + # When updating a plan, the agent must send a complete list of all entries + # with their current status. The client replaces the entire plan with each update. entries: Annotated[ List[PlanEntry], Field( @@ -837,10 +1049,24 @@ class Plan(BaseModel): class PromptRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The blocks of content that compose the user's message. + # + # As a baseline, the Agent MUST support [`ContentBlock::Text`] and [`ContentBlock::ResourceLink`], + # while other variants are optionally enabled via [`PromptCapabilities`]. + # + # The Client MUST adapt its interface according to [`PromptCapabilities`]. + # + # The client MAY include referenced pieces of context as either + # [`ContentBlock::Resource`] or [`ContentBlock::ResourceLink`]. + # + # When available, [`ContentBlock::Resource`] is preferred + # as it avoids extra round-trips and allows the message to include + # pieces of context from sources the agent may not have access to. prompt: Annotated[ List[ Union[ @@ -855,10 +1081,25 @@ class PromptRequest(BaseModel): description="The blocks of content that compose the user's message.\n\nAs a baseline, the Agent MUST support [`ContentBlock::Text`] and [`ContentBlock::ResourceLink`],\nwhile other variants are optionally enabled via [`PromptCapabilities`].\n\nThe Client MUST adapt its interface according to [`PromptCapabilities`].\n\nThe client MAY include referenced pieces of context as either\n[`ContentBlock::Resource`] or [`ContentBlock::ResourceLink`].\n\nWhen available, [`ContentBlock::Resource`] is preferred\nas it avoids extra round-trips and allows the message to include\npieces of context from sources the agent may not have access to." ), ] + # The ID of the session to send this user message to sessionId: Annotated[str, Field(description="The ID of the session to send this user message to")] class UserMessageChunk(BaseModel): + # Content blocks represent displayable information in the Agent Client Protocol. + # + # They provide a structured way to handle various types of user-facing content—whether + # it's text from language models, images for analysis, or embedded resources for context. + # + # Content blocks appear in: + # - User prompts sent via `session/prompt` + # - Language model output streamed through `session/update` notifications + # - Progress updates and results from tool calls + # + # This structure is compatible with the Model Context Protocol (MCP), enabling + # agents to seamlessly forward content from MCP tool outputs without transformation. + # + # See protocol docs: [Content](https://agentclientprotocol.com/protocol/content) content: Annotated[ Union[ TextContentBlock, ImageContentBlock, AudioContentBlock, ResourceContentBlock, EmbeddedResourceContentBlock @@ -871,6 +1112,20 @@ class UserMessageChunk(BaseModel): class AgentMessageChunk(BaseModel): + # Content blocks represent displayable information in the Agent Client Protocol. + # + # They provide a structured way to handle various types of user-facing content—whether + # it's text from language models, images for analysis, or embedded resources for context. + # + # Content blocks appear in: + # - User prompts sent via `session/prompt` + # - Language model output streamed through `session/update` notifications + # - Progress updates and results from tool calls + # + # This structure is compatible with the Model Context Protocol (MCP), enabling + # agents to seamlessly forward content from MCP tool outputs without transformation. + # + # See protocol docs: [Content](https://agentclientprotocol.com/protocol/content) content: Annotated[ Union[ TextContentBlock, ImageContentBlock, AudioContentBlock, ResourceContentBlock, EmbeddedResourceContentBlock @@ -883,6 +1138,20 @@ class AgentMessageChunk(BaseModel): class AgentThoughtChunk(BaseModel): + # Content blocks represent displayable information in the Agent Client Protocol. + # + # They provide a structured way to handle various types of user-facing content—whether + # it's text from language models, images for analysis, or embedded resources for context. + # + # Content blocks appear in: + # - User prompts sent via `session/prompt` + # - Language model output streamed through `session/update` notifications + # - Progress updates and results from tool calls + # + # This structure is compatible with the Model Context Protocol (MCP), enabling + # agents to seamlessly forward content from MCP tool outputs without transformation. + # + # See protocol docs: [Content](https://agentclientprotocol.com/protocol/content) content: Annotated[ Union[ TextContentBlock, ImageContentBlock, AudioContentBlock, ResourceContentBlock, EmbeddedResourceContentBlock @@ -895,6 +1164,7 @@ class AgentThoughtChunk(BaseModel): class ContentToolCallContent(BaseModel): + # The actual content block. content: Annotated[ Union[ TextContentBlock, ImageContentBlock, AudioContentBlock, ResourceContentBlock, EmbeddedResourceContentBlock @@ -905,36 +1175,49 @@ class ContentToolCallContent(BaseModel): class ToolCallUpdate(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Replace the content collection. content: Annotated[ Optional[List[Union[ContentToolCallContent, FileEditToolCallContent, TerminalToolCallContent]]], Field(description="Replace the content collection."), ] = None - kind: Annotated[Optional[str], Field(description="Update the tool kind.")] = None + # Update the tool kind. + kind: Annotated[Optional[ToolKind], Field(description="Update the tool kind.")] = None + # Replace the locations collection. locations: Annotated[ Optional[List[ToolCallLocation]], Field(description="Replace the locations collection."), ] = None + # Update the raw input. rawInput: Annotated[Optional[Any], Field(description="Update the raw input.")] = None + # Update the raw output. rawOutput: Annotated[Optional[Any], Field(description="Update the raw output.")] = None - status: Annotated[Optional[str], Field(description="Update the execution status.")] = None + # Update the execution status. + status: Annotated[Optional[ToolCallStatus], Field(description="Update the execution status.")] = None + # Update the human-readable title. title: Annotated[Optional[str], Field(description="Update the human-readable title.")] = None + # The ID of the tool call being updated. toolCallId: Annotated[str, Field(description="The ID of the tool call being updated.")] class RequestPermissionRequest(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Available permission options for the user to choose from. options: Annotated[ List[PermissionOption], Field(description="Available permission options for the user to choose from."), ] + # The session ID for this request. sessionId: Annotated[str, Field(description="The session ID for this request.")] + # Details about the tool call requiring permission. toolCall: Annotated[ ToolCallUpdate, Field(description="Details about the tool call requiring permission."), @@ -942,32 +1225,43 @@ class RequestPermissionRequest(BaseModel): class ToolCallStart(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Content produced by the tool call. content: Annotated[ Optional[List[Union[ContentToolCallContent, FileEditToolCallContent, TerminalToolCallContent]]], Field(description="Content produced by the tool call."), ] = None + # The category of tool being invoked. + # Helps clients choose appropriate icons and UI treatment. kind: Annotated[ - Optional[str], + Optional[ToolKind], Field( description="The category of tool being invoked.\nHelps clients choose appropriate icons and UI treatment." ), ] = None + # File locations affected by this tool call. + # Enables "follow-along" features in clients. locations: Annotated[ Optional[List[ToolCallLocation]], Field(description='File locations affected by this tool call.\nEnables "follow-along" features in clients.'), ] = None + # Raw input parameters sent to the tool. rawInput: Annotated[Optional[Any], Field(description="Raw input parameters sent to the tool.")] = None + # Raw output returned by the tool. rawOutput: Annotated[Optional[Any], Field(description="Raw output returned by the tool.")] = None sessionUpdate: Literal["tool_call"] - status: Annotated[Optional[str], Field(description="Current execution status of the tool call.")] = None + # Current execution status of the tool call. + status: Annotated[Optional[ToolCallStatus], Field(description="Current execution status of the tool call.")] = None + # Human-readable title describing what the tool is doing. title: Annotated[ str, Field(description="Human-readable title describing what the tool is doing."), ] + # Unique identifier for this tool call within the session. toolCallId: Annotated[ str, Field(description="Unique identifier for this tool call within the session."), @@ -975,53 +1269,73 @@ class ToolCallStart(BaseModel): class ToolCallProgress(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Replace the content collection. content: Annotated[ Optional[List[Union[ContentToolCallContent, FileEditToolCallContent, TerminalToolCallContent]]], Field(description="Replace the content collection."), ] = None - kind: Annotated[Optional[str], Field(description="Update the tool kind.")] = None + # Update the tool kind. + kind: Annotated[Optional[ToolKind], Field(description="Update the tool kind.")] = None + # Replace the locations collection. locations: Annotated[ Optional[List[ToolCallLocation]], Field(description="Replace the locations collection."), ] = None + # Update the raw input. rawInput: Annotated[Optional[Any], Field(description="Update the raw input.")] = None + # Update the raw output. rawOutput: Annotated[Optional[Any], Field(description="Update the raw output.")] = None sessionUpdate: Literal["tool_call_update"] - status: Annotated[Optional[str], Field(description="Update the execution status.")] = None + # Update the execution status. + status: Annotated[Optional[ToolCallStatus], Field(description="Update the execution status.")] = None + # Update the human-readable title. title: Annotated[Optional[str], Field(description="Update the human-readable title.")] = None + # The ID of the tool call being updated. toolCallId: Annotated[str, Field(description="The ID of the tool call being updated.")] class ToolCall(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # Content produced by the tool call. content: Annotated[ Optional[List[Union[ContentToolCallContent, FileEditToolCallContent, TerminalToolCallContent]]], Field(description="Content produced by the tool call."), ] = None + # The category of tool being invoked. + # Helps clients choose appropriate icons and UI treatment. kind: Annotated[ - Optional[str], + Optional[ToolKind], Field( description="The category of tool being invoked.\nHelps clients choose appropriate icons and UI treatment." ), ] = None + # File locations affected by this tool call. + # Enables "follow-along" features in clients. locations: Annotated[ Optional[List[ToolCallLocation]], Field(description='File locations affected by this tool call.\nEnables "follow-along" features in clients.'), ] = None + # Raw input parameters sent to the tool. rawInput: Annotated[Optional[Any], Field(description="Raw input parameters sent to the tool.")] = None + # Raw output returned by the tool. rawOutput: Annotated[Optional[Any], Field(description="Raw output returned by the tool.")] = None - status: Annotated[Optional[str], Field(description="Current execution status of the tool call.")] = None + # Current execution status of the tool call. + status: Annotated[Optional[ToolCallStatus], Field(description="Current execution status of the tool call.")] = None + # Human-readable title describing what the tool is doing. title: Annotated[ str, Field(description="Human-readable title describing what the tool is doing."), ] + # Unique identifier for this tool call within the session. toolCallId: Annotated[ str, Field(description="Unique identifier for this tool call within the session."), @@ -1029,11 +1343,14 @@ class ToolCall(BaseModel): class SessionNotification(BaseModel): + # Extension point for implementations field_meta: Annotated[ Optional[Any], Field(alias="_meta", description="Extension point for implementations"), ] = None + # The ID of the session this update pertains to. sessionId: Annotated[str, Field(description="The ID of the session this update pertains to.")] + # The actual update content. update: Annotated[ Union[ UserMessageChunk, diff --git a/src/acp/stdio.py b/src/acp/stdio.py index a0c1011..40aa5a8 100644 --- a/src/acp/stdio.py +++ b/src/acp/stdio.py @@ -1,12 +1,29 @@ from __future__ import annotations import asyncio +import asyncio.subprocess as aio_subprocess import contextlib import logging import platform import sys from asyncio import transports as aio_transports -from typing import cast +from collections.abc import AsyncIterator, Callable, Mapping +from contextlib import asynccontextmanager +from pathlib import Path +from typing import Any, cast + +from .agent.connection import AgentSideConnection +from .client.connection import ClientSideConnection +from .connection import Connection, MethodHandler, StreamObserver +from .interfaces import Agent, Client +from .transports import spawn_stdio_transport + +__all__ = [ + "spawn_agent_process", + "spawn_client_process", + "spawn_stdio_connection", + "stdio_streams", +] class _WritePipeProtocol(asyncio.BaseProtocol): @@ -110,3 +127,72 @@ async def stdio_streams() -> tuple[asyncio.StreamReader, asyncio.StreamWriter]: if platform.system() == "Windows": return await _windows_stdio_streams(loop) return await _posix_stdio_streams(loop) + + +@asynccontextmanager +async def spawn_stdio_connection( + handler: MethodHandler, + command: str, + *args: str, + env: Mapping[str, str] | None = None, + cwd: str | Path | None = None, + observers: list[StreamObserver] | None = None, + **transport_kwargs: Any, +) -> AsyncIterator[tuple[Connection, aio_subprocess.Process]]: + """Spawn a subprocess and bind its stdio to a low-level Connection.""" + async with spawn_stdio_transport(command, *args, env=env, cwd=cwd, **transport_kwargs) as (reader, writer, process): + conn = Connection(handler, writer, reader, observers=observers) + try: + yield conn, process + finally: + await conn.close() + + +@asynccontextmanager +async def spawn_agent_process( + to_client: Callable[[Agent], Client], + command: str, + *args: str, + env: Mapping[str, str] | None = None, + cwd: str | Path | None = None, + transport_kwargs: Mapping[str, Any] | None = None, + **connection_kwargs: Any, +) -> AsyncIterator[tuple[ClientSideConnection, aio_subprocess.Process]]: + """Spawn an ACP agent subprocess and return a ClientSideConnection to it.""" + async with spawn_stdio_transport( + command, + *args, + env=env, + cwd=cwd, + **(dict(transport_kwargs) if transport_kwargs else {}), + ) as (reader, writer, process): + conn = ClientSideConnection(to_client, writer, reader, **connection_kwargs) + try: + yield conn, process + finally: + await conn.close() + + +@asynccontextmanager +async def spawn_client_process( + to_agent: Callable[[AgentSideConnection], Agent], + command: str, + *args: str, + env: Mapping[str, str] | None = None, + cwd: str | Path | None = None, + transport_kwargs: Mapping[str, Any] | None = None, + **connection_kwargs: Any, +) -> AsyncIterator[tuple[AgentSideConnection, aio_subprocess.Process]]: + """Spawn an ACP client subprocess and return an AgentSideConnection to it.""" + async with spawn_stdio_transport( + command, + *args, + env=env, + cwd=cwd, + **(dict(transport_kwargs) if transport_kwargs else {}), + ) as (reader, writer, process): + conn = AgentSideConnection(to_agent, writer, reader, **connection_kwargs) + try: + yield conn, process + finally: + await conn.close() diff --git a/src/acp/task/__init__.py b/src/acp/task/__init__.py new file mode 100644 index 0000000..2896fbf --- /dev/null +++ b/src/acp/task/__init__.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Any + +__all__ = ["RpcTask", "RpcTaskKind"] + + +class RpcTaskKind(Enum): + REQUEST = "request" + NOTIFICATION = "notification" + + +@dataclass(slots=True) +class RpcTask: + kind: RpcTaskKind + message: dict[str, Any] + + +from .dispatcher import ( # noqa: E402 + DefaultMessageDispatcher, + MessageDispatcher, + NotificationRunner, + RequestRunner, +) +from .queue import InMemoryMessageQueue, MessageQueue # noqa: E402 +from .sender import MessageSender, SenderFactory # noqa: E402 +from .state import InMemoryMessageStateStore, MessageStateStore # noqa: E402 +from .supervisor import TaskSupervisor # noqa: E402 + +__all__ += [ + "DefaultMessageDispatcher", + "InMemoryMessageQueue", + "InMemoryMessageStateStore", + "MessageDispatcher", + "MessageQueue", + "MessageSender", + "MessageStateStore", + "NotificationRunner", + "RequestRunner", + "SenderFactory", + "TaskSupervisor", +] diff --git a/src/acp/task/dispatcher.py b/src/acp/task/dispatcher.py new file mode 100644 index 0000000..e8c5e76 --- /dev/null +++ b/src/acp/task/dispatcher.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +import asyncio +from collections.abc import Awaitable, Callable +from contextlib import suppress +from typing import Any, Protocol + +from . import RpcTaskKind +from .queue import MessageQueue +from .state import MessageStateStore +from .supervisor import TaskSupervisor + +__all__ = [ + "DefaultMessageDispatcher", + "MessageDispatcher", + "NotificationRunner", + "RequestRunner", +] + + +RequestRunner = Callable[[dict[str, Any]], Awaitable[Any]] +NotificationRunner = Callable[[dict[str, Any]], Awaitable[None]] + + +class MessageDispatcher(Protocol): + def start(self) -> None: ... + + async def stop(self) -> None: ... + + +class DefaultMessageDispatcher(MessageDispatcher): + """Background worker that consumes RPC tasks from a broker, coordinating with the store.""" + + def __init__( + self, + *, + queue: MessageQueue, + supervisor: TaskSupervisor, + store: MessageStateStore, + request_runner: RequestRunner, + notification_runner: NotificationRunner, + ) -> None: + self._queue = queue + self._supervisor = supervisor + self._store = store + self._request_runner = request_runner + self._notification_runner = notification_runner + self._task: asyncio.Task[None] | None = None + + def start(self) -> None: + if self._task is not None: + msg = "dispatcher already started" + raise RuntimeError(msg) + self._task = self._supervisor.create(self._run(), name="acp.Dispatcher.loop") + + async def _run(self) -> None: + try: + async for task in self._queue: + try: + if task.kind is RpcTaskKind.REQUEST: + await self._dispatch_request(task.message) + else: + await self._dispatch_notification(task.message) + finally: + self._queue.task_done() + except asyncio.CancelledError: + return + + async def stop(self) -> None: + await self._queue.close() + if self._task is not None: + with suppress(asyncio.CancelledError): + await self._task + self._task = None + + async def _dispatch_request(self, message: dict[str, Any]) -> None: + record = self._store.begin_incoming(message.get("method", ""), message.get("params")) + + async def runner() -> None: + try: + result = await self._request_runner(message) + except Exception as exc: + self._store.fail_incoming(record, exc) + raise + else: + self._store.complete_incoming(record, result) + + self._supervisor.create(runner(), name="acp.Dispatcher.request") + + async def _dispatch_notification(self, message: dict[str, Any]) -> None: + async def runner() -> None: + await self._notification_runner(message) + + self._supervisor.create(runner(), name="acp.Dispatcher.notification") diff --git a/src/acp/task/queue.py b/src/acp/task/queue.py new file mode 100644 index 0000000..6052635 --- /dev/null +++ b/src/acp/task/queue.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +import asyncio +from collections.abc import AsyncIterator +from contextlib import suppress +from typing import Protocol + +from . import RpcTask + +__all__ = ["InMemoryMessageQueue", "MessageQueue"] + + +class MessageQueue(Protocol): + async def publish(self, task: RpcTask) -> None: ... + + async def close(self) -> None: ... + + def task_done(self) -> None: ... + + async def join(self) -> None: ... + + def __aiter__(self) -> AsyncIterator[RpcTask]: ... + + +class InMemoryMessageQueue: + """Simple in-memory broker for RPC task dispatch.""" + + def __init__(self, *, maxsize: int = 0) -> None: + self._queue: asyncio.Queue[RpcTask | None] = asyncio.Queue(maxsize=maxsize) + self._closed = False + + async def publish(self, task: RpcTask) -> None: + if self._closed: + msg = "mssage queue already closed" + raise RuntimeError(msg) + await self._queue.put(task) + + async def close(self) -> None: + if self._closed: + return + self._closed = True + await self._queue.put(None) + + async def join(self) -> None: + await self._queue.join() + + def task_done(self) -> None: + with suppress(ValueError): + self._queue.task_done() + + def __aiter__(self) -> AsyncIterator[RpcTask]: + return _QueueIterator(self) + + +class _QueueIterator: + def __init__(self, queue: InMemoryMessageQueue) -> None: + self._queue = queue + + def __aiter__(self) -> _QueueIterator: + return self + + async def __anext__(self) -> RpcTask: + item = await self._queue._queue.get() + if item is None: + self._queue.task_done() + raise StopAsyncIteration + return item diff --git a/src/acp/task/sender.py b/src/acp/task/sender.py new file mode 100644 index 0000000..5662af2 --- /dev/null +++ b/src/acp/task/sender.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +import asyncio +import contextlib +import json +import logging +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +from .supervisor import TaskSupervisor + +__all__ = ["MessageSender", "SenderFactory"] + + +SenderFactory = Callable[[asyncio.StreamWriter, TaskSupervisor], "MessageSender"] + + +@dataclass(slots=True) +class _PendingSend: + payload: bytes + future: asyncio.Future[None] + + +class MessageSender: + def __init__(self, writer: asyncio.StreamWriter, supervisor: TaskSupervisor) -> None: + self._writer = writer + self._queue: asyncio.Queue[_PendingSend | None] = asyncio.Queue() + self._closed = False + self._task = supervisor.create(self._loop(), name="acp.Sender.loop", on_error=self._on_error) + + async def send(self, payload: dict[str, Any]) -> None: + data = (json.dumps(payload, separators=(",", ":")) + "\n").encode("utf-8") + future: asyncio.Future[None] = asyncio.get_running_loop().create_future() + await self._queue.put(_PendingSend(data, future)) + await future + + async def close(self) -> None: + if self._closed: + return + self._closed = True + await self._queue.put(None) + if self._task is not None: + with contextlib.suppress(asyncio.CancelledError): + await self._task + + async def _loop(self) -> None: + try: + while True: + item = await self._queue.get() + if item is None: + return + try: + self._writer.write(item.payload) + await self._writer.drain() + except Exception as exc: + if not item.future.done(): + item.future.set_exception(exc) + raise + else: + if not item.future.done(): + item.future.set_result(None) + except asyncio.CancelledError: + return + + def _on_error(self, task: asyncio.Task[Any], exc: BaseException) -> None: + logging.exception("Send loop failed", exc_info=exc) diff --git a/src/acp/task/state.py b/src/acp/task/state.py new file mode 100644 index 0000000..65baf0c --- /dev/null +++ b/src/acp/task/state.py @@ -0,0 +1,84 @@ +from __future__ import annotations + +import asyncio +from dataclasses import dataclass +from typing import Any, Protocol + +__all__ = [ + "InMemoryMessageStateStore", + "IncomingMessage", + "MessageStateStore", + "OutgoingMessage", +] + + +@dataclass(slots=True) +class OutgoingMessage: + request_id: int + method: str + future: asyncio.Future[Any] + + +@dataclass(slots=True) +class IncomingMessage: + method: str + params: Any + status: str = "pending" + result: Any = None + error: Any = None + + +class MessageStateStore(Protocol): + def register_outgoing(self, request_id: int, method: str) -> asyncio.Future[Any]: ... + + def resolve_outgoing(self, request_id: int, result: Any) -> None: ... + + def reject_outgoing(self, request_id: int, error: Any) -> None: ... + + def reject_all_outgoing(self, error: Any) -> None: ... + + def begin_incoming(self, method: str, params: Any) -> IncomingMessage: ... + + def complete_incoming(self, record: IncomingMessage, result: Any) -> None: ... + + def fail_incoming(self, record: IncomingMessage, error: Any) -> None: ... + + +class InMemoryMessageStateStore(MessageStateStore): + def __init__(self) -> None: + self._outgoing: dict[int, OutgoingMessage] = {} + self._incoming: list[IncomingMessage] = [] + + def register_outgoing(self, request_id: int, method: str) -> asyncio.Future[Any]: + future: asyncio.Future[Any] = asyncio.get_running_loop().create_future() + self._outgoing[request_id] = OutgoingMessage(request_id, method, future) + return future + + def resolve_outgoing(self, request_id: int, result: Any) -> None: + record = self._outgoing.pop(request_id, None) + if record and not record.future.done(): + record.future.set_result(result) + + def reject_outgoing(self, request_id: int, error: Any) -> None: + record = self._outgoing.pop(request_id, None) + if record and not record.future.done(): + record.future.set_exception(error) + + def reject_all_outgoing(self, error: Any) -> None: + for record in self._outgoing.values(): + if not record.future.done(): + record.future.set_exception(error) + self._outgoing.clear() + + def begin_incoming(self, method: str, params: Any) -> IncomingMessage: + record = IncomingMessage(method=method, params=params) + self._incoming.append(record) + return record + + def complete_incoming(self, record: IncomingMessage, result: Any) -> None: + record.status = "completed" + record.result = result + + def fail_incoming(self, record: IncomingMessage, error: Any) -> None: + record.status = "failed" + record.error = error diff --git a/src/acp/task/supervisor.py b/src/acp/task/supervisor.py new file mode 100644 index 0000000..7ad7e6d --- /dev/null +++ b/src/acp/task/supervisor.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +import asyncio +import logging +from collections.abc import Awaitable, Callable +from contextlib import suppress +from typing import Any + +__all__ = ["TaskSupervisor"] + +ErrorHandler = Callable[[asyncio.Task[Any], BaseException], None] + + +class TaskSupervisor: + """Track background tasks and provide graceful shutdown semantics. + + Inspired by fasta2a's task manager, this supervisor keeps a registry of + asyncio tasks created for request handling so they can be cancelled and + awaited reliably when the connection closes. + """ + + def __init__(self, *, source: str) -> None: + self._source = source + self._tasks: set[asyncio.Task[Any]] = set() + self._closed = False + self._error_handlers: list[ErrorHandler] = [] + + def add_error_handler(self, handler: ErrorHandler) -> None: + self._error_handlers.append(handler) + + def create( + self, + coroutine: Awaitable[Any], + *, + name: str | None = None, + on_error: ErrorHandler | None = None, + ) -> asyncio.Task[Any]: + if self._closed: + msg = f"TaskSupervisor for {self._source} already closed" + raise RuntimeError(msg) + task = asyncio.create_task(coroutine, name=name) + self._tasks.add(task) + task.add_done_callback(lambda t: self._on_done(t, on_error)) + return task + + def _on_done(self, task: asyncio.Task[Any], on_error: ErrorHandler | None) -> None: + self._tasks.discard(task) + if task.cancelled(): + return + try: + task.result() + except Exception as exc: + handled = False + if on_error is not None: + try: + on_error(task, exc) + handled = True + except Exception: + logging.exception("Error in %s task-specific error handler", self._source) + if not handled: + for handler in self._error_handlers: + try: + handler(task, exc) + handled = True + except Exception: + logging.exception("Error in %s supervisor error handler", self._source) + if not handled: + logging.exception("Unhandled error in %s task", self._source) + + async def shutdown(self) -> None: + self._closed = True + if not self._tasks: + return + tasks = list(self._tasks) + for task in tasks: + task.cancel() + for task in tasks: + with suppress(asyncio.CancelledError): + await task + self._tasks.clear() diff --git a/src/acp/telemetry.py b/src/acp/telemetry.py new file mode 100644 index 0000000..011ed46 --- /dev/null +++ b/src/acp/telemetry.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import os +from collections.abc import Mapping +from contextlib import AbstractContextManager, ExitStack, nullcontext +from typing import Any, cast + +try: + from logfire import span as logfire_span +except Exception: # pragma: no cover - logfire is optional + logfire_span = None # type: ignore[assignment] +else: # pragma: no cover - optional + os.environ.setdefault("LOGFIRE_IGNORE_NO_CONFIG", "1") + +try: # pragma: no cover - opentelemetry is optional + from opentelemetry.trace import get_tracer as otel_get_tracer +except Exception: # pragma: no cover - opentelemetry is optional + otel_get_tracer = None # type: ignore[assignment] + +DEFAULT_TAGS = ["acp"] +TRACER = otel_get_tracer(__name__) if otel_get_tracer else None + + +def _start_tracer_span(name: str, *, attributes: Mapping[str, Any] | None = None) -> AbstractContextManager[Any]: + if TRACER is None: + return nullcontext() + attrs = dict(attributes or {}) + return TRACER.start_as_current_span(name, attributes=attrs) + + +def span_context(name: str, *, attributes: Mapping[str, Any] | None = None) -> AbstractContextManager[None]: + if logfire_span is None and TRACER is None: + return nullcontext() + stack = ExitStack() + attrs: dict[str, Any] = {"logfire.tags": DEFAULT_TAGS} + if attributes: + attrs.update(attributes) + if logfire_span is not None: + stack.enter_context(logfire_span(name, attributes=attrs)) + stack.enter_context(_start_tracer_span(name, attributes=attributes)) + return cast(AbstractContextManager[None], stack) diff --git a/src/acp/terminal.py b/src/acp/terminal.py new file mode 100644 index 0000000..619039c --- /dev/null +++ b/src/acp/terminal.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from contextlib import suppress + +from .connection import Connection +from .meta import CLIENT_METHODS +from .schema import ( + KillTerminalCommandResponse, + ReleaseTerminalResponse, + TerminalOutputResponse, + WaitForTerminalExitResponse, +) + +__all__ = ["TerminalHandle"] + + +class TerminalHandle: + def __init__(self, terminal_id: str, session_id: str, conn: Connection) -> None: + self.id = terminal_id + self._session_id = session_id + self._conn = conn + + async def current_output(self) -> TerminalOutputResponse: + response = await self._conn.send_request( + CLIENT_METHODS["terminal_output"], + {"sessionId": self._session_id, "terminalId": self.id}, + ) + return TerminalOutputResponse.model_validate(response) + + async def wait_for_exit(self) -> WaitForTerminalExitResponse: + response = await self._conn.send_request( + CLIENT_METHODS["terminal_wait_for_exit"], + {"sessionId": self._session_id, "terminalId": self.id}, + ) + return WaitForTerminalExitResponse.model_validate(response) + + async def kill(self) -> KillTerminalCommandResponse: + response = await self._conn.send_request( + CLIENT_METHODS["terminal_kill"], + {"sessionId": self._session_id, "terminalId": self.id}, + ) + payload = response if isinstance(response, dict) else {} + return KillTerminalCommandResponse.model_validate(payload) + + async def release(self) -> ReleaseTerminalResponse: + response = await self._conn.send_request( + CLIENT_METHODS["terminal_release"], + {"sessionId": self._session_id, "terminalId": self.id}, + ) + payload = response if isinstance(response, dict) else {} + return ReleaseTerminalResponse.model_validate(payload) + + async def aclose(self) -> None: + """Release the terminal, ignoring errors that occur during shutdown.""" + with suppress(Exception): + await self.release() + + async def close(self) -> None: + await self.aclose() + + async def __aenter__(self) -> TerminalHandle: + return self + + async def __aexit__(self, exc_type, exc, tb) -> None: + await self.aclose() diff --git a/src/acp/transports.py b/src/acp/transports.py new file mode 100644 index 0000000..be2a002 --- /dev/null +++ b/src/acp/transports.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +import asyncio +import asyncio.subprocess as aio_subprocess +import contextlib +import os +from collections.abc import AsyncIterator, Mapping +from contextlib import asynccontextmanager +from pathlib import Path + +__all__ = ["DEFAULT_INHERITED_ENV_VARS", "default_environment", "spawn_stdio_transport"] + +DEFAULT_INHERITED_ENV_VARS = ( + [ + "APPDATA", + "HOMEDRIVE", + "HOMEPATH", + "LOCALAPPDATA", + "PATH", + "PATHEXT", + "PROCESSOR_ARCHITECTURE", + "SYSTEMDRIVE", + "SYSTEMROOT", + "TEMP", + "USERNAME", + "USERPROFILE", + ] + if os.name == "nt" + else ["HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER"] +) + + +def default_environment() -> dict[str, str]: + """Return a trimmed environment based on MCP best practices.""" + env: dict[str, str] = {} + for key in DEFAULT_INHERITED_ENV_VARS: + value = os.environ.get(key) + if value is None: + continue + # Skip function-style env vars on some shells (see MCP reference) + if value.startswith("()"): + continue + env[key] = value + return env + + +@asynccontextmanager +async def spawn_stdio_transport( + command: str, + *args: str, + env: Mapping[str, str] | None = None, + cwd: str | Path | None = None, + stderr: int | None = aio_subprocess.PIPE, + shutdown_timeout: float = 2.0, +) -> AsyncIterator[tuple[asyncio.StreamReader, asyncio.StreamWriter, aio_subprocess.Process]]: + """Launch a subprocess and expose its stdio streams as asyncio transports. + + This mirrors the defensive shutdown behaviour used by the MCP Python SDK: + close stdin first, wait for graceful exit, then escalate to terminate/kill. + """ + merged_env = dict(default_environment()) + if env: + merged_env.update(env) + + process = await asyncio.create_subprocess_exec( + command, + *args, + stdin=aio_subprocess.PIPE, + stdout=aio_subprocess.PIPE, + stderr=stderr, + env=merged_env, + cwd=str(cwd) if cwd is not None else None, + ) + + if process.stdout is None or process.stdin is None: + process.kill() + await process.wait() + msg = "spawn_stdio_transport requires stdout/stderr pipes" + raise RuntimeError(msg) + + try: + yield process.stdout, process.stdin, process + finally: + # Attempt graceful stdin shutdown first + if process.stdin is not None: + try: + process.stdin.write_eof() + except (AttributeError, OSError, RuntimeError): + process.stdin.close() + with contextlib.suppress(Exception): + await process.stdin.drain() + with contextlib.suppress(Exception): + process.stdin.close() + with contextlib.suppress(Exception): + await process.stdin.wait_closed() + + try: + await asyncio.wait_for(process.wait(), timeout=shutdown_timeout) + except asyncio.TimeoutError: + process.terminate() + try: + await asyncio.wait_for(process.wait(), timeout=shutdown_timeout) + except asyncio.TimeoutError: + process.kill() + await process.wait() diff --git a/src/acp/utils.py b/src/acp/utils.py new file mode 100644 index 0000000..b84a5a7 --- /dev/null +++ b/src/acp/utils.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +from typing import Any, TypeVar + +from pydantic import BaseModel + +from .connection import Connection + +__all__ = [ + "ensure_dict", + "normalize_result", + "notify_model", + "request_model", + "request_model_from_dict", + "request_optional_model", + "serialize_params", + "validate_model", + "validate_model_from_dict", + "validate_optional_model", +] + +ModelT = TypeVar("ModelT", bound=BaseModel) + + +def serialize_params(params: BaseModel) -> dict[str, Any]: + """Return a JSON-serializable representation used for RPC calls.""" + return params.model_dump(exclude_none=True, exclude_defaults=True) + + +def normalize_result(payload: Any) -> dict[str, Any]: + """Convert optional BaseModel/None responses into JSON-friendly payloads.""" + if payload is None: + return {} + if isinstance(payload, BaseModel): + return serialize_params(payload) + return payload + + +def ensure_dict(payload: Any) -> dict[str, Any]: + """Return payload when it is a dict, otherwise an empty dict.""" + return payload if isinstance(payload, dict) else {} + + +def validate_model(payload: Any, model_type: type[ModelT]) -> ModelT: + """Validate payload using the provided Pydantic model.""" + return model_type.model_validate(payload) + + +def validate_model_from_dict(payload: Any, model_type: type[ModelT]) -> ModelT: + """Validate payload, coercing non-dict values to an empty dict first.""" + return model_type.model_validate(ensure_dict(payload)) + + +def validate_optional_model(payload: Any, model_type: type[ModelT]) -> ModelT | None: + """Validate payload when it is a dict, otherwise return None.""" + if isinstance(payload, dict): + return model_type.model_validate(payload) + return None + + +async def request_model( + conn: Connection, + method: str, + params: BaseModel, + response_model: type[ModelT], +) -> ModelT: + """Send a request with serialized params and validate the response.""" + response = await conn.send_request(method, serialize_params(params)) + return validate_model(response, response_model) + + +async def request_model_from_dict( + conn: Connection, + method: str, + params: BaseModel, + response_model: type[ModelT], +) -> ModelT: + """Send a request and validate the response, coercing non-dict payloads.""" + response = await conn.send_request(method, serialize_params(params)) + return validate_model_from_dict(response, response_model) + + +async def request_optional_model( + conn: Connection, + method: str, + params: BaseModel, + response_model: type[ModelT], +) -> ModelT | None: + """Send a request and validate optional dict responses.""" + response = await conn.send_request(method, serialize_params(params)) + return validate_optional_model(response, response_model) + + +async def notify_model(conn: Connection, method: str, params: BaseModel) -> None: + """Send a notification with serialized params.""" + await conn.send_notification(method, serialize_params(params)) diff --git a/tests/golden/cancel_notification.json b/tests/golden/cancel_notification.json new file mode 100644 index 0000000..a5461d2 --- /dev/null +++ b/tests/golden/cancel_notification.json @@ -0,0 +1,3 @@ +{ + "sessionId": "sess_abc123def456" +} diff --git a/tests/golden/content_audio.json b/tests/golden/content_audio.json new file mode 100644 index 0000000..6cd650e --- /dev/null +++ b/tests/golden/content_audio.json @@ -0,0 +1,5 @@ +{ + "type": "audio", + "mimeType": "audio/wav", + "data": "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAAB..." +} diff --git a/tests/golden/content_image.json b/tests/golden/content_image.json new file mode 100644 index 0000000..fca8b88 --- /dev/null +++ b/tests/golden/content_image.json @@ -0,0 +1,5 @@ +{ + "type": "image", + "mimeType": "image/png", + "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB..." +} diff --git a/tests/golden/content_resource_blob.json b/tests/golden/content_resource_blob.json new file mode 100644 index 0000000..4832503 --- /dev/null +++ b/tests/golden/content_resource_blob.json @@ -0,0 +1,8 @@ +{ + "type": "resource", + "resource": { + "uri": "file:///home/user/document.pdf", + "mimeType": "application/pdf", + "blob": "" + } +} diff --git a/tests/golden/content_resource_link.json b/tests/golden/content_resource_link.json new file mode 100644 index 0000000..4e33c1e --- /dev/null +++ b/tests/golden/content_resource_link.json @@ -0,0 +1,7 @@ +{ + "type": "resource_link", + "uri": "file:///home/user/document.pdf", + "name": "document.pdf", + "mimeType": "application/pdf", + "size": 1024000 +} diff --git a/tests/golden/content_resource_text.json b/tests/golden/content_resource_text.json new file mode 100644 index 0000000..f73945a --- /dev/null +++ b/tests/golden/content_resource_text.json @@ -0,0 +1,8 @@ +{ + "type": "resource", + "resource": { + "uri": "file:///home/user/script.py", + "mimeType": "text/x-python", + "text": "def hello():\n print('Hello, world!')" + } +} diff --git a/tests/golden/content_text.json b/tests/golden/content_text.json new file mode 100644 index 0000000..63b2e85 --- /dev/null +++ b/tests/golden/content_text.json @@ -0,0 +1,4 @@ +{ + "type": "text", + "text": "What's the weather like today?" +} diff --git a/tests/golden/fs_read_text_file_request.json b/tests/golden/fs_read_text_file_request.json new file mode 100644 index 0000000..3d3ccca --- /dev/null +++ b/tests/golden/fs_read_text_file_request.json @@ -0,0 +1,6 @@ +{ + "sessionId": "sess_abc123def456", + "path": "/home/user/project/src/main.py", + "line": 10, + "limit": 50 +} diff --git a/tests/golden/fs_read_text_file_response.json b/tests/golden/fs_read_text_file_response.json new file mode 100644 index 0000000..b5dac57 --- /dev/null +++ b/tests/golden/fs_read_text_file_response.json @@ -0,0 +1,3 @@ +{ + "content": "def hello_world():\n print('Hello, world!')\n" +} diff --git a/tests/golden/fs_write_text_file_request.json b/tests/golden/fs_write_text_file_request.json new file mode 100644 index 0000000..efbad09 --- /dev/null +++ b/tests/golden/fs_write_text_file_request.json @@ -0,0 +1,5 @@ +{ + "sessionId": "sess_abc123def456", + "path": "/home/user/project/config.json", + "content": "{\n \"debug\": true,\n \"version\": \"1.0.0\"\n}" +} diff --git a/tests/golden/initialize_request.json b/tests/golden/initialize_request.json new file mode 100644 index 0000000..b239909 --- /dev/null +++ b/tests/golden/initialize_request.json @@ -0,0 +1,9 @@ +{ + "protocolVersion": 1, + "clientCapabilities": { + "fs": { + "readTextFile": true, + "writeTextFile": true + } + } +} diff --git a/tests/golden/initialize_response.json b/tests/golden/initialize_response.json new file mode 100644 index 0000000..66abb81 --- /dev/null +++ b/tests/golden/initialize_response.json @@ -0,0 +1,13 @@ +{ + "protocolVersion": 1, + "agentCapabilities": { + "loadSession": true, + "mcpCapabilities": {}, + "promptCapabilities": { + "image": true, + "audio": true, + "embeddedContext": true + } + }, + "authMethods": [] +} diff --git a/tests/golden/new_session_request.json b/tests/golden/new_session_request.json new file mode 100644 index 0000000..132c920 --- /dev/null +++ b/tests/golden/new_session_request.json @@ -0,0 +1,13 @@ +{ + "cwd": "/home/user/project", + "mcpServers": [ + { + "name": "filesystem", + "command": "/path/to/mcp-server", + "args": [ + "--stdio" + ], + "env": [] + } + ] +} diff --git a/tests/golden/new_session_response.json b/tests/golden/new_session_response.json new file mode 100644 index 0000000..a5461d2 --- /dev/null +++ b/tests/golden/new_session_response.json @@ -0,0 +1,3 @@ +{ + "sessionId": "sess_abc123def456" +} diff --git a/tests/golden/permission_outcome_cancelled.json b/tests/golden/permission_outcome_cancelled.json new file mode 100644 index 0000000..38f0331 --- /dev/null +++ b/tests/golden/permission_outcome_cancelled.json @@ -0,0 +1,3 @@ +{ + "outcome": "cancelled" +} diff --git a/tests/golden/permission_outcome_selected.json b/tests/golden/permission_outcome_selected.json new file mode 100644 index 0000000..3a194c2 --- /dev/null +++ b/tests/golden/permission_outcome_selected.json @@ -0,0 +1,4 @@ +{ + "outcome": "selected", + "optionId": "allow-once" +} diff --git a/tests/golden/prompt_request.json b/tests/golden/prompt_request.json new file mode 100644 index 0000000..816fae1 --- /dev/null +++ b/tests/golden/prompt_request.json @@ -0,0 +1,17 @@ +{ + "sessionId": "sess_abc123def456", + "prompt": [ + { + "type": "text", + "text": "Can you analyze this code for potential issues?" + }, + { + "type": "resource", + "resource": { + "uri": "file:///home/user/project/main.py", + "mimeType": "text/x-python", + "text": "def process_data(items):\n for item in items:\n print(item)" + } + } + ] +} diff --git a/tests/golden/request_permission_request.json b/tests/golden/request_permission_request.json new file mode 100644 index 0000000..1fb297f --- /dev/null +++ b/tests/golden/request_permission_request.json @@ -0,0 +1,18 @@ +{ + "sessionId": "sess_abc123def456", + "toolCall": { + "toolCallId": "call_001" + }, + "options": [ + { + "optionId": "allow-once", + "name": "Allow once", + "kind": "allow_once" + }, + { + "optionId": "reject-once", + "name": "Reject", + "kind": "reject_once" + } + ] +} diff --git a/tests/golden/request_permission_response_selected.json b/tests/golden/request_permission_response_selected.json new file mode 100644 index 0000000..e29b89b --- /dev/null +++ b/tests/golden/request_permission_response_selected.json @@ -0,0 +1,6 @@ +{ + "outcome": { + "outcome": "selected", + "optionId": "allow-once" + } +} diff --git a/tests/golden/session_update_agent_message_chunk.json b/tests/golden/session_update_agent_message_chunk.json new file mode 100644 index 0000000..7ace7ed --- /dev/null +++ b/tests/golden/session_update_agent_message_chunk.json @@ -0,0 +1,7 @@ +{ + "sessionUpdate": "agent_message_chunk", + "content": { + "type": "text", + "text": "The capital of France is Paris." + } +} diff --git a/tests/golden/session_update_agent_thought_chunk.json b/tests/golden/session_update_agent_thought_chunk.json new file mode 100644 index 0000000..893c13b --- /dev/null +++ b/tests/golden/session_update_agent_thought_chunk.json @@ -0,0 +1,7 @@ +{ + "sessionUpdate": "agent_thought_chunk", + "content": { + "type": "text", + "text": "Thinking about best approach..." + } +} diff --git a/tests/golden/session_update_plan.json b/tests/golden/session_update_plan.json new file mode 100644 index 0000000..bad3e8a --- /dev/null +++ b/tests/golden/session_update_plan.json @@ -0,0 +1,15 @@ +{ + "sessionUpdate": "plan", + "entries": [ + { + "content": "Check for syntax errors", + "priority": "high", + "status": "pending" + }, + { + "content": "Identify potential type issues", + "priority": "medium", + "status": "pending" + } + ] +} diff --git a/tests/golden/session_update_tool_call.json b/tests/golden/session_update_tool_call.json new file mode 100644 index 0000000..448649d --- /dev/null +++ b/tests/golden/session_update_tool_call.json @@ -0,0 +1,7 @@ +{ + "sessionUpdate": "tool_call", + "toolCallId": "call_001", + "title": "Reading configuration file", + "kind": "read", + "status": "pending" +} diff --git a/tests/golden/session_update_tool_call_edit.json b/tests/golden/session_update_tool_call_edit.json new file mode 100644 index 0000000..1cf0bda --- /dev/null +++ b/tests/golden/session_update_tool_call_edit.json @@ -0,0 +1,16 @@ +{ + "sessionUpdate": "tool_call", + "toolCallId": "call_003", + "title": "Apply edit", + "kind": "edit", + "status": "pending", + "locations": [ + { + "path": "/home/user/project/src/config.json" + } + ], + "rawInput": { + "path": "/home/user/project/src/config.json", + "content": "print('hello')" + } +} diff --git a/tests/golden/session_update_tool_call_locations_rawinput.json b/tests/golden/session_update_tool_call_locations_rawinput.json new file mode 100644 index 0000000..a1ac3e4 --- /dev/null +++ b/tests/golden/session_update_tool_call_locations_rawinput.json @@ -0,0 +1,13 @@ +{ + "sessionUpdate": "tool_call", + "toolCallId": "call_lr", + "title": "Tracking file", + "locations": [ + { + "path": "/home/user/project/src/config.json" + } + ], + "rawInput": { + "path": "/home/user/project/src/config.json" + } +} diff --git a/tests/golden/session_update_tool_call_read.json b/tests/golden/session_update_tool_call_read.json new file mode 100644 index 0000000..d533afb --- /dev/null +++ b/tests/golden/session_update_tool_call_read.json @@ -0,0 +1,15 @@ +{ + "sessionUpdate": "tool_call", + "toolCallId": "call_001", + "title": "Reading configuration file", + "kind": "read", + "status": "pending", + "locations": [ + { + "path": "/home/user/project/src/config.json" + } + ], + "rawInput": { + "path": "/home/user/project/src/config.json" + } +} diff --git a/tests/golden/session_update_tool_call_update_content.json b/tests/golden/session_update_tool_call_update_content.json new file mode 100644 index 0000000..e28b461 --- /dev/null +++ b/tests/golden/session_update_tool_call_update_content.json @@ -0,0 +1,14 @@ +{ + "sessionUpdate": "tool_call_update", + "toolCallId": "call_001", + "status": "in_progress", + "content": [ + { + "type": "content", + "content": { + "type": "text", + "text": "Found 3 configuration files..." + } + } + ] +} diff --git a/tests/golden/session_update_tool_call_update_more_fields.json b/tests/golden/session_update_tool_call_update_more_fields.json new file mode 100644 index 0000000..d5af335 --- /dev/null +++ b/tests/golden/session_update_tool_call_update_more_fields.json @@ -0,0 +1,27 @@ +{ + "sessionUpdate": "tool_call_update", + "toolCallId": "call_010", + "title": "Processing changes", + "kind": "edit", + "status": "completed", + "locations": [ + { + "path": "/home/user/project/src/config.json" + } + ], + "rawInput": { + "path": "/home/user/project/src/config.json" + }, + "rawOutput": { + "result": "ok" + }, + "content": [ + { + "type": "content", + "content": { + "type": "text", + "text": "Edit completed." + } + } + ] +} diff --git a/tests/golden/session_update_user_message_chunk.json b/tests/golden/session_update_user_message_chunk.json new file mode 100644 index 0000000..8ca73e7 --- /dev/null +++ b/tests/golden/session_update_user_message_chunk.json @@ -0,0 +1,7 @@ +{ + "sessionUpdate": "user_message_chunk", + "content": { + "type": "text", + "text": "What's the capital of France?" + } +} diff --git a/tests/golden/tool_content_content_text.json b/tests/golden/tool_content_content_text.json new file mode 100644 index 0000000..bf3b6f7 --- /dev/null +++ b/tests/golden/tool_content_content_text.json @@ -0,0 +1,7 @@ +{ + "type": "content", + "content": { + "type": "text", + "text": "Analysis complete. Found 3 issues." + } +} diff --git a/tests/golden/tool_content_diff.json b/tests/golden/tool_content_diff.json new file mode 100644 index 0000000..98482cb --- /dev/null +++ b/tests/golden/tool_content_diff.json @@ -0,0 +1,6 @@ +{ + "type": "diff", + "path": "/home/user/project/src/config.json", + "oldText": "{\n \"debug\": false\n}", + "newText": "{\n \"debug\": true\n}" +} diff --git a/tests/golden/tool_content_diff_no_old.json b/tests/golden/tool_content_diff_no_old.json new file mode 100644 index 0000000..c044187 --- /dev/null +++ b/tests/golden/tool_content_diff_no_old.json @@ -0,0 +1,5 @@ +{ + "type": "diff", + "path": "/home/user/project/src/config.json", + "newText": "{\n \"debug\": true\n}" +} diff --git a/tests/golden/tool_content_terminal.json b/tests/golden/tool_content_terminal.json new file mode 100644 index 0000000..fd0c676 --- /dev/null +++ b/tests/golden/tool_content_terminal.json @@ -0,0 +1,4 @@ +{ + "type": "terminal", + "terminalId": "term_001" +} diff --git a/tests/real_user/__init__.py b/tests/real_user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_cancel_prompt_flow.py b/tests/real_user/test_cancel_prompt_flow.py similarity index 90% rename from tests/test_cancel_prompt_flow.py rename to tests/real_user/test_cancel_prompt_flow.py index c43263e..45d7798 100644 --- a/tests/test_cancel_prompt_flow.py +++ b/tests/real_user/test_cancel_prompt_flow.py @@ -2,16 +2,12 @@ import pytest -from acp import ( - AgentSideConnection, - CancelNotification, - ClientSideConnection, - PromptRequest, - PromptResponse, -) +from acp import AgentSideConnection, CancelNotification, ClientSideConnection, PromptRequest, PromptResponse from acp.schema import TextContentBlock from tests.test_rpc import TestAgent, TestClient, _Server +# Regression from a real user session where cancel needed to interrupt a long-running prompt. + class LongRunningAgent(TestAgent): """Agent variant whose prompt waits for a cancel notification.""" diff --git a/tests/test_permission_flow.py b/tests/real_user/test_permission_flow.py similarity index 86% rename from tests/test_permission_flow.py rename to tests/real_user/test_permission_flow.py index a4478b3..f337cce 100644 --- a/tests/test_permission_flow.py +++ b/tests/real_user/test_permission_flow.py @@ -2,17 +2,12 @@ import pytest -from acp import ( - AgentSideConnection, - ClientSideConnection, - PromptRequest, - PromptResponse, - RequestPermissionRequest, - RequestPermissionResponse, -) +from acp import AgentSideConnection, ClientSideConnection, PromptRequest, PromptResponse, RequestPermissionRequest from acp.schema import PermissionOption, TextContentBlock, ToolCallUpdate from tests.test_rpc import TestAgent, TestClient, _Server +# Regression from real-world runs where agents paused prompts to obtain user permission. + class PermissionRequestAgent(TestAgent): """Agent that asks the client for permission during a prompt.""" @@ -20,15 +15,15 @@ class PermissionRequestAgent(TestAgent): def __init__(self, conn: AgentSideConnection) -> None: super().__init__() self._conn = conn - self.permission_responses: list[RequestPermissionResponse] = [] + self.permission_responses = [] async def prompt(self, params: PromptRequest) -> PromptResponse: permission = await self._conn.requestPermission( RequestPermissionRequest( sessionId=params.sessionId, options=[ - PermissionOption(optionId="allow", name="Allow", kind="allow"), - PermissionOption(optionId="deny", name="Deny", kind="deny"), + PermissionOption(optionId="allow", name="Allow", kind="allow_once"), + PermissionOption(optionId="deny", name="Deny", kind="reject_once"), ], toolCall=ToolCallUpdate(toolCallId="call-1", title="Write File"), ) @@ -43,7 +38,7 @@ async def test_agent_request_permission_roundtrip() -> None: client = TestClient() client.queue_permission_selected("allow") - captured_agent: list[PermissionRequestAgent] = [] + captured_agent = [] agent_conn = ClientSideConnection(lambda _conn: client, server.client_writer, server.client_reader) _agent_conn = AgentSideConnection( diff --git a/tests/test_gemini_example.py b/tests/test_gemini_example.py new file mode 100644 index 0000000..1702855 --- /dev/null +++ b/tests/test_gemini_example.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import os +import shlex +import shutil +import subprocess +import sys +from pathlib import Path + +import pytest + + +def _flag_enabled() -> bool: + value = os.environ.get("ACP_ENABLE_GEMINI_TESTS", "").strip().lower() + return value in {"1", "true", "yes", "on"} + + +def _resolve_gemini_binary() -> str | None: + override = os.environ.get("ACP_GEMINI_BIN") + if override: + return override + return shutil.which("gemini") + + +GEMINI_BIN = _resolve_gemini_binary() +pytestmark = pytest.mark.skipif( + not (_flag_enabled() and GEMINI_BIN), + reason="Gemini tests disabled. Set ACP_ENABLE_GEMINI_TESTS=1 and provide the gemini CLI.", +) + + +def test_gemini_example_smoke() -> None: + env = os.environ.copy() + src_path = str(Path(__file__).resolve().parent.parent / "src") + python_path = env.get("PYTHONPATH") + env["PYTHONPATH"] = src_path if not python_path else os.pathsep.join([src_path, python_path]) + + extra_args = shlex.split(env.get("ACP_GEMINI_TEST_ARGS", "")) + cmd = [ + sys.executable, + str(Path("examples/gemini.py").resolve()), + "--gemini", + GEMINI_BIN or "gemini", + "--yolo", + *extra_args, + ] + + proc = subprocess.Popen( # noqa: S603 - command is built from trusted inputs + cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + cwd=Path(__file__).resolve().parent.parent, + ) + + assert proc.stdin is not None + assert proc.stdout is not None + + try: + stdout, stderr = proc.communicate(":exit\n", timeout=120) + except subprocess.TimeoutExpired: + proc.kill() + stdout, stderr = proc.communicate() + pytest.fail(_format_failure("Gemini example timed out", stdout, stderr), pytrace=False) + + combined = f"{stdout}\n{stderr}" + if proc.returncode != 0: + auth_errors = ( + "Authentication failed", + "Authentication required", + "GOOGLE_CLOUD_PROJECT", + ) + if any(token in combined for token in auth_errors): + pytest.skip(f"Gemini CLI authentication required:\n{combined}") + pytest.fail( + _format_failure(f"Gemini example exited with {proc.returncode}", stdout, stderr), + pytrace=False, + ) + + assert "Connected to Gemini" in combined or "✅ Connected to Gemini" in combined + + +def _format_failure(prefix: str, stdout: str, stderr: str) -> str: + return f"{prefix}.\nstdout:\n{stdout}\nstderr:\n{stderr}" diff --git a/tests/test_golden.py b/tests/test_golden.py new file mode 100644 index 0000000..430bd04 --- /dev/null +++ b/tests/test_golden.py @@ -0,0 +1,232 @@ +from __future__ import annotations + +import json +from collections.abc import Callable +from pathlib import Path + +import pytest +from pydantic import BaseModel + +from acp import ( + audio_block, + embedded_blob_resource, + embedded_text_resource, + image_block, + plan_entry, + resource_block, + resource_link_block, + start_edit_tool_call, + start_read_tool_call, + start_tool_call, + text_block, + tool_content, + tool_diff_content, + tool_terminal_ref, + update_agent_message_text, + update_agent_thought_text, + update_plan, + update_tool_call, + update_user_message_text, +) +from acp.schema import ( + AgentMessageChunk, + AgentPlanUpdate, + AgentThoughtChunk, + AllowedOutcome, + AudioContentBlock, + CancelNotification, + ContentToolCallContent, + DeniedOutcome, + EmbeddedResourceContentBlock, + FileEditToolCallContent, + ImageContentBlock, + InitializeRequest, + InitializeResponse, + NewSessionRequest, + NewSessionResponse, + PromptRequest, + ReadTextFileRequest, + ReadTextFileResponse, + RequestPermissionRequest, + RequestPermissionResponse, + ResourceContentBlock, + TerminalToolCallContent, + TextContentBlock, + ToolCallLocation, + ToolCallProgress, + ToolCallStart, + UserMessageChunk, + WriteTextFileRequest, +) + +GOLDEN_DIR = Path(__file__).parent / "golden" + +# Map each golden fixture to the concrete schema model it should conform to. +GOLDEN_CASES: dict[str, type[BaseModel]] = { + "cancel_notification": CancelNotification, + "content_audio": AudioContentBlock, + "content_image": ImageContentBlock, + "content_resource_blob": EmbeddedResourceContentBlock, + "content_resource_link": ResourceContentBlock, + "content_resource_text": EmbeddedResourceContentBlock, + "content_text": TextContentBlock, + "fs_read_text_file_request": ReadTextFileRequest, + "fs_read_text_file_response": ReadTextFileResponse, + "fs_write_text_file_request": WriteTextFileRequest, + "initialize_request": InitializeRequest, + "initialize_response": InitializeResponse, + "new_session_request": NewSessionRequest, + "new_session_response": NewSessionResponse, + "permission_outcome_cancelled": DeniedOutcome, + "permission_outcome_selected": AllowedOutcome, + "prompt_request": PromptRequest, + "request_permission_request": RequestPermissionRequest, + "request_permission_response_selected": RequestPermissionResponse, + "session_update_agent_message_chunk": AgentMessageChunk, + "session_update_agent_thought_chunk": AgentThoughtChunk, + "session_update_plan": AgentPlanUpdate, + "session_update_tool_call": ToolCallStart, + "session_update_tool_call_edit": ToolCallStart, + "session_update_tool_call_locations_rawinput": ToolCallStart, + "session_update_tool_call_read": ToolCallStart, + "session_update_tool_call_update_content": ToolCallProgress, + "session_update_tool_call_update_more_fields": ToolCallProgress, + "session_update_user_message_chunk": UserMessageChunk, + "tool_content_content_text": ContentToolCallContent, + "tool_content_diff": FileEditToolCallContent, + "tool_content_diff_no_old": FileEditToolCallContent, + "tool_content_terminal": TerminalToolCallContent, +} + +_PARAMS = tuple(sorted(GOLDEN_CASES.items())) +_PARAM_IDS = [name for name, _ in _PARAMS] + +GOLDEN_BUILDERS: dict[str, Callable[[], BaseModel]] = { + "content_text": lambda: text_block("What's the weather like today?"), + "content_image": lambda: image_block("iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB...", "image/png"), + "content_audio": lambda: audio_block("UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAAB...", "audio/wav"), + "content_resource_text": lambda: resource_block( + embedded_text_resource( + "file:///home/user/script.py", + "def hello():\n print('Hello, world!')", + mime_type="text/x-python", + ) + ), + "content_resource_blob": lambda: resource_block( + embedded_blob_resource( + "file:///home/user/document.pdf", + "", + mime_type="application/pdf", + ) + ), + "content_resource_link": lambda: resource_link_block( + "document.pdf", + "file:///home/user/document.pdf", + mime_type="application/pdf", + size=1_024_000, + ), + "tool_content_content_text": lambda: tool_content(text_block("Analysis complete. Found 3 issues.")), + "tool_content_diff": lambda: tool_diff_content( + "/home/user/project/src/config.json", + '{\n "debug": true\n}', + '{\n "debug": false\n}', + ), + "tool_content_diff_no_old": lambda: tool_diff_content( + "/home/user/project/src/config.json", + '{\n "debug": true\n}', + ), + "tool_content_terminal": lambda: tool_terminal_ref("term_001"), + "session_update_user_message_chunk": lambda: update_user_message_text("What's the capital of France?"), + "session_update_agent_message_chunk": lambda: update_agent_message_text("The capital of France is Paris."), + "session_update_agent_thought_chunk": lambda: update_agent_thought_text("Thinking about best approach..."), + "session_update_plan": lambda: update_plan([ + plan_entry( + "Check for syntax errors", + priority="high", + status="pending", + ), + plan_entry( + "Identify potential type issues", + priority="medium", + status="pending", + ), + ]), + "session_update_tool_call": lambda: start_tool_call( + "call_001", + "Reading configuration file", + kind="read", + status="pending", + ), + "session_update_tool_call_read": lambda: start_read_tool_call( + "call_001", + "Reading configuration file", + "/home/user/project/src/config.json", + ), + "session_update_tool_call_edit": lambda: start_edit_tool_call( + "call_003", + "Apply edit", + "/home/user/project/src/config.json", + "print('hello')", + ), + "session_update_tool_call_locations_rawinput": lambda: start_tool_call( + "call_lr", + "Tracking file", + locations=[ToolCallLocation(path="/home/user/project/src/config.json")], + raw_input={"path": "/home/user/project/src/config.json"}, + ), + "session_update_tool_call_update_content": lambda: update_tool_call( + "call_001", + status="in_progress", + content=[tool_content(text_block("Found 3 configuration files..."))], + ), + "session_update_tool_call_update_more_fields": lambda: update_tool_call( + "call_010", + title="Processing changes", + kind="edit", + status="completed", + locations=[ToolCallLocation(path="/home/user/project/src/config.json")], + raw_input={"path": "/home/user/project/src/config.json"}, + raw_output={"result": "ok"}, + content=[tool_content(text_block("Edit completed."))], + ), +} + +_HELPER_PARAMS = tuple(sorted(GOLDEN_BUILDERS.items())) +_HELPER_IDS = [name for name, _ in _HELPER_PARAMS] + + +def _load_golden(name: str) -> dict: + path = GOLDEN_DIR / f"{name}.json" + return json.loads(path.read_text()) + + +def _dump_model(model: BaseModel) -> dict: + return model.model_dump(mode="json", by_alias=True, exclude_none=True, exclude_unset=True) + + +def test_golden_cases_covered() -> None: + available = {path.stem for path in GOLDEN_DIR.glob("*.json")} + assert available == set(GOLDEN_CASES), "Add the new golden file to GOLDEN_CASES." + + +@pytest.mark.parametrize( + ("name", "model_cls"), + _PARAMS, + ids=_PARAM_IDS, +) +def test_json_golden_roundtrip(name: str, model_cls: type[BaseModel]) -> None: + raw = _load_golden(name) + model = model_cls.model_validate(raw) + assert _dump_model(model) == raw + + +@pytest.mark.parametrize( + ("name", "builder"), + _HELPER_PARAMS, + ids=_HELPER_IDS, +) +def test_helpers_match_golden(name: str, builder: Callable[[], BaseModel]) -> None: + raw = _load_golden(name) + model = builder() + assert isinstance(model, BaseModel) + assert _dump_model(model) == raw diff --git a/tests/test_rpc.py b/tests/test_rpc.py index ea6fb6e..eba7321 100644 --- a/tests/test_rpc.py +++ b/tests/test_rpc.py @@ -1,6 +1,8 @@ import asyncio import contextlib import json +import sys +from pathlib import Path import pytest @@ -32,12 +34,22 @@ SetSessionModeResponse, WriteTextFileRequest, WriteTextFileResponse, + session_notification, + spawn_agent_process, + start_tool_call, + update_agent_message_text, + update_tool_call, ) from acp.schema import ( AgentMessageChunk, AllowedOutcome, DeniedOutcome, + PermissionOption, TextContentBlock, + ToolCallLocation, + ToolCallProgress, + ToolCallStart, + ToolCallUpdate, UserMessageChunk, ) @@ -411,3 +423,188 @@ async def test_ignore_invalid_messages(): # Should not receive any response lines with pytest.raises(asyncio.TimeoutError): await asyncio.wait_for(s.client_reader.readline(), timeout=0.1) + + +class _ExampleAgent(Agent): + __test__ = False + + def __init__(self) -> None: + self._conn: AgentSideConnection | None = None + self.permission_response: RequestPermissionResponse | None = None + self.prompt_requests: list[PromptRequest] = [] + + def bind(self, conn: AgentSideConnection) -> "_ExampleAgent": + self._conn = conn + return self + + async def initialize(self, params: InitializeRequest) -> InitializeResponse: + return InitializeResponse(protocolVersion=params.protocolVersion) + + async def newSession(self, params: NewSessionRequest) -> NewSessionResponse: + return NewSessionResponse(sessionId="sess_demo") + + async def prompt(self, params: PromptRequest) -> PromptResponse: + assert self._conn is not None + self.prompt_requests.append(params) + + await self._conn.sessionUpdate( + session_notification( + params.sessionId, + update_agent_message_text("I'll help you with that."), + ) + ) + + await self._conn.sessionUpdate( + session_notification( + params.sessionId, + start_tool_call( + "call_1", + "Modifying configuration", + kind="edit", + status="pending", + locations=[ToolCallLocation(path="/project/config.json")], + raw_input={"path": "/project/config.json"}, + ), + ) + ) + + permission_request = RequestPermissionRequest( + sessionId=params.sessionId, + toolCall=ToolCallUpdate( + toolCallId="call_1", + title="Modifying configuration", + kind="edit", + status="pending", + locations=[ToolCallLocation(path="/project/config.json")], + rawInput={"path": "/project/config.json"}, + ), + options=[ + PermissionOption(kind="allow_once", name="Allow", optionId="allow"), + PermissionOption(kind="reject_once", name="Reject", optionId="reject"), + ], + ) + response = await self._conn.requestPermission(permission_request) + self.permission_response = response + + if isinstance(response.outcome, AllowedOutcome) and response.outcome.optionId == "allow": + await self._conn.sessionUpdate( + session_notification( + params.sessionId, + update_tool_call( + "call_1", + status="completed", + raw_output={"success": True}, + ), + ) + ) + await self._conn.sessionUpdate( + session_notification( + params.sessionId, + update_agent_message_text("Done."), + ) + ) + + return PromptResponse(stopReason="end_turn") + + +class _ExampleClient(TestClient): + __test__ = False + + def __init__(self) -> None: + super().__init__() + self.permission_requests: list[RequestPermissionRequest] = [] + + async def requestPermission(self, params: RequestPermissionRequest) -> RequestPermissionResponse: + self.permission_requests.append(params) + if not params.options: + return RequestPermissionResponse(outcome=DeniedOutcome(outcome="cancelled")) + option = params.options[0] + return RequestPermissionResponse(outcome=AllowedOutcome(optionId=option.optionId, outcome="selected")) + + +@pytest.mark.asyncio +async def test_example_agent_permission_flow(): + async with _Server() as s: + agent = _ExampleAgent() + client = _ExampleClient() + + agent_conn = ClientSideConnection(lambda _conn: client, s.client_writer, s.client_reader) + AgentSideConnection(lambda conn: agent.bind(conn), s.server_writer, s.server_reader) + + init = await agent_conn.initialize(InitializeRequest(protocolVersion=1)) + assert init.protocolVersion == 1 + + session = await agent_conn.newSession(NewSessionRequest(mcpServers=[], cwd="/workspace")) + assert session.sessionId == "sess_demo" + + prompt = PromptRequest( + sessionId=session.sessionId, + prompt=[TextContentBlock(type="text", text="Please edit config")], + ) + resp = await agent_conn.prompt(prompt) + assert resp.stopReason == "end_turn" + + for _ in range(50): + if len(client.notifications) >= 4: + break + await asyncio.sleep(0.02) + + assert len(client.notifications) >= 4 + session_updates = [getattr(note.update, "sessionUpdate", None) for note in client.notifications] + assert session_updates[:4] == ["agent_message_chunk", "tool_call", "tool_call_update", "agent_message_chunk"] + + first_message = client.notifications[0].update + assert isinstance(first_message, AgentMessageChunk) + assert isinstance(first_message.content, TextContentBlock) + assert first_message.content.text == "I'll help you with that." + + tool_call = client.notifications[1].update + assert isinstance(tool_call, ToolCallStart) + assert tool_call.title == "Modifying configuration" + assert tool_call.status == "pending" + + tool_update = client.notifications[2].update + assert isinstance(tool_update, ToolCallProgress) + assert tool_update.status == "completed" + assert tool_update.rawOutput == {"success": True} + + final_message = client.notifications[3].update + assert isinstance(final_message, AgentMessageChunk) + assert isinstance(final_message.content, TextContentBlock) + assert final_message.content.text == "Done." + + assert len(client.permission_requests) == 1 + options = client.permission_requests[0].options + assert [opt.optionId for opt in options] == ["allow", "reject"] + + assert agent.permission_response is not None + assert isinstance(agent.permission_response.outcome, AllowedOutcome) + assert agent.permission_response.outcome.optionId == "allow" + + +@pytest.mark.asyncio +async def test_spawn_agent_process_roundtrip(tmp_path): + script = Path(__file__).parents[1] / "examples" / "echo_agent.py" + assert script.exists() + + test_client = TestClient() + + async with spawn_agent_process(lambda _agent: test_client, sys.executable, str(script)) as (client_conn, process): + init = await client_conn.initialize(InitializeRequest(protocolVersion=1)) + assert isinstance(init, InitializeResponse) + session = await client_conn.newSession(NewSessionRequest(cwd=str(tmp_path), mcpServers=[])) + prompt = PromptRequest( + sessionId=session.sessionId, + prompt=[TextContentBlock(type="text", text="hi spawn")], + ) + await client_conn.prompt(prompt) + + # Wait for echo agent notification to arrive + for _ in range(50): + if test_client.notifications: + break + await asyncio.sleep(0.02) + + assert test_client.notifications + + assert process.returncode is not None diff --git a/uv.lock b/uv.lock index 33dffe2..8f903ae 100644 --- a/uv.lock +++ b/uv.lock @@ -1,20 +1,25 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.10, <4.0" [[package]] name = "agent-client-protocol" -version = "0.4.5" +version = "0.4.9" source = { editable = "." } dependencies = [ { name = "pydantic" }, ] +[package.optional-dependencies] +logfire = [ + { name = "logfire" }, + { name = "opentelemetry-sdk" }, +] + [package.dev-dependencies] dev = [ { name = "datamodel-code-generator" }, { name = "deptry" }, - { name = "mini-swe-agent" }, { name = "mkdocs" }, { name = "mkdocs-material" }, { name = "mkdocstrings", extra = ["python"] }, @@ -28,13 +33,17 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "pydantic", specifier = ">=2.7" }] +requires-dist = [ + { name = "logfire", marker = "extra == 'logfire'", specifier = ">=0.14" }, + { name = "opentelemetry-sdk", marker = "extra == 'logfire'", specifier = ">=1.28.0" }, + { name = "pydantic", specifier = ">=2.7" }, +] +provides-extras = ["logfire"] [package.metadata.requires-dev] dev = [ { name = "datamodel-code-generator", specifier = ">=0.25" }, { name = "deptry", specifier = ">=0.23.0" }, - { name = "mini-swe-agent", specifier = ">=1.10.0" }, { name = "mkdocs", specifier = ">=1.4.2" }, { name = "mkdocs-material", specifier = ">=8.5.10" }, { name = "mkdocstrings", extras = ["python"], specifier = ">=0.26.1" }, @@ -47,114 +56,6 @@ dev = [ { name = "ty", specifier = ">=0.0.1a16" }, ] -[[package]] -name = "aiohappyeyeballs" -version = "2.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, -] - -[[package]] -name = "aiohttp" -version = "3.12.15" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohappyeyeballs" }, - { name = "aiosignal" }, - { name = "async-timeout", marker = "python_full_version < '3.11'" }, - { name = "attrs" }, - { name = "frozenlist" }, - { name = "multidict" }, - { name = "propcache" }, - { name = "yarl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/dc/ef9394bde9080128ad401ac7ede185267ed637df03b51f05d14d1c99ad67/aiohttp-3.12.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc", size = 703921, upload-time = "2025-07-29T05:49:43.584Z" }, - { url = "https://files.pythonhosted.org/packages/8f/42/63fccfc3a7ed97eb6e1a71722396f409c46b60a0552d8a56d7aad74e0df5/aiohttp-3.12.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af", size = 480288, upload-time = "2025-07-29T05:49:47.851Z" }, - { url = "https://files.pythonhosted.org/packages/9c/a2/7b8a020549f66ea2a68129db6960a762d2393248f1994499f8ba9728bbed/aiohttp-3.12.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421", size = 468063, upload-time = "2025-07-29T05:49:49.789Z" }, - { url = "https://files.pythonhosted.org/packages/8f/f5/d11e088da9176e2ad8220338ae0000ed5429a15f3c9dfd983f39105399cd/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79", size = 1650122, upload-time = "2025-07-29T05:49:51.874Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6b/b60ce2757e2faed3d70ed45dafee48cee7bfb878785a9423f7e883f0639c/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77", size = 1624176, upload-time = "2025-07-29T05:49:53.805Z" }, - { url = "https://files.pythonhosted.org/packages/dd/de/8c9fde2072a1b72c4fadecf4f7d4be7a85b1d9a4ab333d8245694057b4c6/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c", size = 1696583, upload-time = "2025-07-29T05:49:55.338Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ad/07f863ca3d895a1ad958a54006c6dafb4f9310f8c2fdb5f961b8529029d3/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4", size = 1738896, upload-time = "2025-07-29T05:49:57.045Z" }, - { url = "https://files.pythonhosted.org/packages/20/43/2bd482ebe2b126533e8755a49b128ec4e58f1a3af56879a3abdb7b42c54f/aiohttp-3.12.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6", size = 1643561, upload-time = "2025-07-29T05:49:58.762Z" }, - { url = "https://files.pythonhosted.org/packages/23/40/2fa9f514c4cf4cbae8d7911927f81a1901838baf5e09a8b2c299de1acfe5/aiohttp-3.12.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2", size = 1583685, upload-time = "2025-07-29T05:50:00.375Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c3/94dc7357bc421f4fb978ca72a201a6c604ee90148f1181790c129396ceeb/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d", size = 1627533, upload-time = "2025-07-29T05:50:02.306Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3f/1f8911fe1844a07001e26593b5c255a685318943864b27b4e0267e840f95/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb", size = 1638319, upload-time = "2025-07-29T05:50:04.282Z" }, - { url = "https://files.pythonhosted.org/packages/4e/46/27bf57a99168c4e145ffee6b63d0458b9c66e58bb70687c23ad3d2f0bd17/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5", size = 1613776, upload-time = "2025-07-29T05:50:05.863Z" }, - { url = "https://files.pythonhosted.org/packages/0f/7e/1d2d9061a574584bb4ad3dbdba0da90a27fdc795bc227def3a46186a8bc1/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b", size = 1693359, upload-time = "2025-07-29T05:50:07.563Z" }, - { url = "https://files.pythonhosted.org/packages/08/98/bee429b52233c4a391980a5b3b196b060872a13eadd41c3a34be9b1469ed/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065", size = 1716598, upload-time = "2025-07-29T05:50:09.33Z" }, - { url = "https://files.pythonhosted.org/packages/57/39/b0314c1ea774df3392751b686104a3938c63ece2b7ce0ba1ed7c0b4a934f/aiohttp-3.12.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1", size = 1644940, upload-time = "2025-07-29T05:50:11.334Z" }, - { url = "https://files.pythonhosted.org/packages/1b/83/3dacb8d3f8f512c8ca43e3fa8a68b20583bd25636ffa4e56ee841ffd79ae/aiohttp-3.12.15-cp310-cp310-win32.whl", hash = "sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a", size = 429239, upload-time = "2025-07-29T05:50:12.803Z" }, - { url = "https://files.pythonhosted.org/packages/eb/f9/470b5daba04d558c9673ca2034f28d067f3202a40e17804425f0c331c89f/aiohttp-3.12.15-cp310-cp310-win_amd64.whl", hash = "sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830", size = 452297, upload-time = "2025-07-29T05:50:14.266Z" }, - { url = "https://files.pythonhosted.org/packages/20/19/9e86722ec8e835959bd97ce8c1efa78cf361fa4531fca372551abcc9cdd6/aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117", size = 711246, upload-time = "2025-07-29T05:50:15.937Z" }, - { url = "https://files.pythonhosted.org/packages/71/f9/0a31fcb1a7d4629ac9d8f01f1cb9242e2f9943f47f5d03215af91c3c1a26/aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe", size = 483515, upload-time = "2025-07-29T05:50:17.442Z" }, - { url = "https://files.pythonhosted.org/packages/62/6c/94846f576f1d11df0c2e41d3001000527c0fdf63fce7e69b3927a731325d/aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9", size = 471776, upload-time = "2025-07-29T05:50:19.568Z" }, - { url = "https://files.pythonhosted.org/packages/f8/6c/f766d0aaafcee0447fad0328da780d344489c042e25cd58fde566bf40aed/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5", size = 1741977, upload-time = "2025-07-29T05:50:21.665Z" }, - { url = "https://files.pythonhosted.org/packages/17/e5/fb779a05ba6ff44d7bc1e9d24c644e876bfff5abe5454f7b854cace1b9cc/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728", size = 1690645, upload-time = "2025-07-29T05:50:23.333Z" }, - { url = "https://files.pythonhosted.org/packages/37/4e/a22e799c2035f5d6a4ad2cf8e7c1d1bd0923192871dd6e367dafb158b14c/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16", size = 1789437, upload-time = "2025-07-29T05:50:25.007Z" }, - { url = "https://files.pythonhosted.org/packages/28/e5/55a33b991f6433569babb56018b2fb8fb9146424f8b3a0c8ecca80556762/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0", size = 1828482, upload-time = "2025-07-29T05:50:26.693Z" }, - { url = "https://files.pythonhosted.org/packages/c6/82/1ddf0ea4f2f3afe79dffed5e8a246737cff6cbe781887a6a170299e33204/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b", size = 1730944, upload-time = "2025-07-29T05:50:28.382Z" }, - { url = "https://files.pythonhosted.org/packages/1b/96/784c785674117b4cb3877522a177ba1b5e4db9ce0fd519430b5de76eec90/aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd", size = 1668020, upload-time = "2025-07-29T05:50:30.032Z" }, - { url = "https://files.pythonhosted.org/packages/12/8a/8b75f203ea7e5c21c0920d84dd24a5c0e971fe1e9b9ebbf29ae7e8e39790/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8", size = 1716292, upload-time = "2025-07-29T05:50:31.983Z" }, - { url = "https://files.pythonhosted.org/packages/47/0b/a1451543475bb6b86a5cfc27861e52b14085ae232896a2654ff1231c0992/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50", size = 1711451, upload-time = "2025-07-29T05:50:33.989Z" }, - { url = "https://files.pythonhosted.org/packages/55/fd/793a23a197cc2f0d29188805cfc93aa613407f07e5f9da5cd1366afd9d7c/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676", size = 1691634, upload-time = "2025-07-29T05:50:35.846Z" }, - { url = "https://files.pythonhosted.org/packages/ca/bf/23a335a6670b5f5dfc6d268328e55a22651b440fca341a64fccf1eada0c6/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7", size = 1785238, upload-time = "2025-07-29T05:50:37.597Z" }, - { url = "https://files.pythonhosted.org/packages/57/4f/ed60a591839a9d85d40694aba5cef86dde9ee51ce6cca0bb30d6eb1581e7/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7", size = 1805701, upload-time = "2025-07-29T05:50:39.591Z" }, - { url = "https://files.pythonhosted.org/packages/85/e0/444747a9455c5de188c0f4a0173ee701e2e325d4b2550e9af84abb20cdba/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685", size = 1718758, upload-time = "2025-07-29T05:50:41.292Z" }, - { url = "https://files.pythonhosted.org/packages/36/ab/1006278d1ffd13a698e5dd4bfa01e5878f6bddefc296c8b62649753ff249/aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b", size = 428868, upload-time = "2025-07-29T05:50:43.063Z" }, - { url = "https://files.pythonhosted.org/packages/10/97/ad2b18700708452400278039272032170246a1bf8ec5d832772372c71f1a/aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d", size = 453273, upload-time = "2025-07-29T05:50:44.613Z" }, - { url = "https://files.pythonhosted.org/packages/63/97/77cb2450d9b35f517d6cf506256bf4f5bda3f93a66b4ad64ba7fc917899c/aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7", size = 702333, upload-time = "2025-07-29T05:50:46.507Z" }, - { url = "https://files.pythonhosted.org/packages/83/6d/0544e6b08b748682c30b9f65640d006e51f90763b41d7c546693bc22900d/aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444", size = 476948, upload-time = "2025-07-29T05:50:48.067Z" }, - { url = "https://files.pythonhosted.org/packages/3a/1d/c8c40e611e5094330284b1aea8a4b02ca0858f8458614fa35754cab42b9c/aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d", size = 469787, upload-time = "2025-07-29T05:50:49.669Z" }, - { url = "https://files.pythonhosted.org/packages/38/7d/b76438e70319796bfff717f325d97ce2e9310f752a267bfdf5192ac6082b/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c", size = 1716590, upload-time = "2025-07-29T05:50:51.368Z" }, - { url = "https://files.pythonhosted.org/packages/79/b1/60370d70cdf8b269ee1444b390cbd72ce514f0d1cd1a715821c784d272c9/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0", size = 1699241, upload-time = "2025-07-29T05:50:53.628Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2b/4968a7b8792437ebc12186db31523f541943e99bda8f30335c482bea6879/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab", size = 1754335, upload-time = "2025-07-29T05:50:55.394Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c1/49524ed553f9a0bec1a11fac09e790f49ff669bcd14164f9fab608831c4d/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb", size = 1800491, upload-time = "2025-07-29T05:50:57.202Z" }, - { url = "https://files.pythonhosted.org/packages/de/5e/3bf5acea47a96a28c121b167f5ef659cf71208b19e52a88cdfa5c37f1fcc/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545", size = 1719929, upload-time = "2025-07-29T05:50:59.192Z" }, - { url = "https://files.pythonhosted.org/packages/39/94/8ae30b806835bcd1cba799ba35347dee6961a11bd507db634516210e91d8/aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c", size = 1635733, upload-time = "2025-07-29T05:51:01.394Z" }, - { url = "https://files.pythonhosted.org/packages/7a/46/06cdef71dd03acd9da7f51ab3a9107318aee12ad38d273f654e4f981583a/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd", size = 1696790, upload-time = "2025-07-29T05:51:03.657Z" }, - { url = "https://files.pythonhosted.org/packages/02/90/6b4cfaaf92ed98d0ec4d173e78b99b4b1a7551250be8937d9d67ecb356b4/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f", size = 1718245, upload-time = "2025-07-29T05:51:05.911Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e6/2593751670fa06f080a846f37f112cbe6f873ba510d070136a6ed46117c6/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d", size = 1658899, upload-time = "2025-07-29T05:51:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/8f/28/c15bacbdb8b8eb5bf39b10680d129ea7410b859e379b03190f02fa104ffd/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519", size = 1738459, upload-time = "2025-07-29T05:51:09.56Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/c269cbc4faa01fb10f143b1670633a8ddd5b2e1ffd0548f7aa49cb5c70e2/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea", size = 1766434, upload-time = "2025-07-29T05:51:11.423Z" }, - { url = "https://files.pythonhosted.org/packages/52/b0/4ff3abd81aa7d929b27d2e1403722a65fc87b763e3a97b3a2a494bfc63bc/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3", size = 1726045, upload-time = "2025-07-29T05:51:13.689Z" }, - { url = "https://files.pythonhosted.org/packages/71/16/949225a6a2dd6efcbd855fbd90cf476052e648fb011aa538e3b15b89a57a/aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1", size = 423591, upload-time = "2025-07-29T05:51:15.452Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d8/fa65d2a349fe938b76d309db1a56a75c4fb8cc7b17a398b698488a939903/aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34", size = 450266, upload-time = "2025-07-29T05:51:17.239Z" }, - { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, - { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, - { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, - { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, - { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, - { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, - { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, - { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, - { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, - { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, - { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, - { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, - { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, -] - -[[package]] -name = "aiosignal" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "frozenlist" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, -] - [[package]] name = "annotated-types" version = "0.7.0" @@ -164,21 +65,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] -[[package]] -name = "anyio" -version = "4.10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "idna" }, - { name = "sniffio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } -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 = "argcomplete" version = "3.6.2" @@ -188,24 +74,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/da/e42d7a9d8dd33fa775f467e4028a47936da2f01e4b0e561f9ba0d74cb0ca/argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591", size = 43708, upload-time = "2025-04-03T04:57:01.591Z" }, ] -[[package]] -name = "async-timeout" -version = "5.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, -] - -[[package]] -name = "attrs" -version = "25.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, -] - [[package]] name = "babel" version = "2.17.0" @@ -453,15 +321,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] -[[package]] -name = "distro" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, -] - [[package]] name = "exceptiongroup" version = "1.3.0" @@ -475,27 +334,12 @@ wheels = [ ] [[package]] -name = "fastuuid" -version = "0.12.0" +name = "executing" +version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/17/13146a1e916bd2971d0a58db5e0a4ad23efdd49f78f33ac871c161f8007b/fastuuid-0.12.0.tar.gz", hash = "sha256:d0bd4e5b35aad2826403f4411937c89e7c88857b1513fe10f696544c03e9bd8e", size = 19180, upload-time = "2025-01-27T18:04:14.387Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/c3/9db9aee6f34e6dfd1f909d3d7432ac26e491a0471f8bb8b676c44b625b3f/fastuuid-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:22a900ef0956aacf862b460e20541fdae2d7c340594fe1bd6fdcb10d5f0791a9", size = 247356, upload-time = "2025-01-27T18:04:45.397Z" }, - { url = "https://files.pythonhosted.org/packages/14/a5/999e6e017af3d85841ce1e172d32fd27c8700804c125f496f71bfddc1a9f/fastuuid-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0302f5acf54dc75de30103025c5a95db06d6c2be36829043a0aa16fc170076bc", size = 258384, upload-time = "2025-01-27T18:04:03.562Z" }, - { url = "https://files.pythonhosted.org/packages/c4/e6/beae8411cac5b3b0b9d59ee08405eb39c3abe81dad459114363eff55c14a/fastuuid-0.12.0-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:7946b4a310cfc2d597dcba658019d72a2851612a2cebb949d809c0e2474cf0a6", size = 278480, upload-time = "2025-01-27T18:04:05.663Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f6/c598b9a052435716fc5a084ef17049edd35ca2c8241161269bfea4905ab4/fastuuid-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:a1b6764dd42bf0c46c858fb5ade7b7a3d93b7a27485a7a5c184909026694cd88", size = 156799, upload-time = "2025-01-27T18:05:41.867Z" }, - { url = "https://files.pythonhosted.org/packages/d4/99/555eab31381c7912103d4c8654082611e5e82a7bb88ad5ab067e36b622d7/fastuuid-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2bced35269315d16fe0c41003f8c9d63f2ee16a59295d90922cad5e6a67d0418", size = 247249, upload-time = "2025-01-27T18:03:23.092Z" }, - { url = "https://files.pythonhosted.org/packages/6d/3b/d62ce7f2af3d50a8e787603d44809770f43a3f2ff708bf10c252bf479109/fastuuid-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82106e4b0a24f4f2f73c88f89dadbc1533bb808900740ca5db9bbb17d3b0c824", size = 258369, upload-time = "2025-01-27T18:04:08.903Z" }, - { url = "https://files.pythonhosted.org/packages/86/23/33ec5355036745cf83ea9ca7576d2e0750ff8d268c03b4af40ed26f1a303/fastuuid-0.12.0-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:4db1bc7b8caa1d7412e1bea29b016d23a8d219131cff825b933eb3428f044dca", size = 278316, upload-time = "2025-01-27T18:04:12.74Z" }, - { url = "https://files.pythonhosted.org/packages/40/91/32ce82a14650148b6979ccd1a0089fd63d92505a90fb7156d2acc3245cbd/fastuuid-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:07afc8e674e67ac3d35a608c68f6809da5fab470fb4ef4469094fdb32ba36c51", size = 156643, upload-time = "2025-01-27T18:05:59.266Z" }, - { url = "https://files.pythonhosted.org/packages/f6/28/442e79d6219b90208cb243ac01db05d89cc4fdf8ecd563fb89476baf7122/fastuuid-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:328694a573fe9dce556b0b70c9d03776786801e028d82f0b6d9db1cb0521b4d1", size = 247372, upload-time = "2025-01-27T18:03:40.967Z" }, - { url = "https://files.pythonhosted.org/packages/40/eb/e0fd56890970ca7a9ec0d116844580988b692b1a749ac38e0c39e1dbdf23/fastuuid-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02acaea2c955bb2035a7d8e7b3fba8bd623b03746ae278e5fa932ef54c702f9f", size = 258200, upload-time = "2025-01-27T18:04:12.138Z" }, - { url = "https://files.pythonhosted.org/packages/f5/3c/4b30e376e65597a51a3dc929461a0dec77c8aec5d41d930f482b8f43e781/fastuuid-0.12.0-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:ed9f449cba8cf16cced252521aee06e633d50ec48c807683f21cc1d89e193eb0", size = 278446, upload-time = "2025-01-27T18:04:15.877Z" }, - { url = "https://files.pythonhosted.org/packages/fe/96/cc5975fd23d2197b3e29f650a7a9beddce8993eaf934fa4ac595b77bb71f/fastuuid-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:0df2ea4c9db96fd8f4fa38d0e88e309b3e56f8fd03675a2f6958a5b082a0c1e4", size = 157185, upload-time = "2025-01-27T18:06:19.21Z" }, - { url = "https://files.pythonhosted.org/packages/a9/e8/d2bb4f19e5ee15f6f8e3192a54a897678314151aa17d0fb766d2c2cbc03d/fastuuid-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7fe2407316a04ee8f06d3dbc7eae396d0a86591d92bafe2ca32fce23b1145786", size = 247512, upload-time = "2025-01-27T18:04:08.115Z" }, - { url = "https://files.pythonhosted.org/packages/bc/53/25e811d92fd60f5c65e098c3b68bd8f1a35e4abb6b77a153025115b680de/fastuuid-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9b31dd488d0778c36f8279b306dc92a42f16904cba54acca71e107d65b60b0c", size = 258257, upload-time = "2025-01-27T18:03:56.408Z" }, - { url = "https://files.pythonhosted.org/packages/10/23/73618e7793ea0b619caae2accd9e93e60da38dd78dd425002d319152ef2f/fastuuid-0.12.0-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:b19361ee649365eefc717ec08005972d3d1eb9ee39908022d98e3bfa9da59e37", size = 278559, upload-time = "2025-01-27T18:03:58.661Z" }, - { url = "https://files.pythonhosted.org/packages/e4/41/6317ecfc4757d5f2a604e5d3993f353ba7aee85fa75ad8b86fce6fc2fa40/fastuuid-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:8fc66b11423e6f3e1937385f655bedd67aebe56a3dcec0cb835351cfe7d358c9", size = 157276, upload-time = "2025-01-27T18:06:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, ] [[package]] @@ -507,109 +351,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, ] -[[package]] -name = "frozenlist" -version = "1.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/36/0da0a49409f6b47cc2d060dc8c9040b897b5902a8a4e37d9bc1deb11f680/frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a", size = 81304, upload-time = "2025-06-09T22:59:46.226Z" }, - { url = "https://files.pythonhosted.org/packages/77/f0/77c11d13d39513b298e267b22eb6cb559c103d56f155aa9a49097221f0b6/frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61", size = 47735, upload-time = "2025-06-09T22:59:48.133Z" }, - { url = "https://files.pythonhosted.org/packages/37/12/9d07fa18971a44150593de56b2f2947c46604819976784bcf6ea0d5db43b/frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d", size = 46775, upload-time = "2025-06-09T22:59:49.564Z" }, - { url = "https://files.pythonhosted.org/packages/70/34/f73539227e06288fcd1f8a76853e755b2b48bca6747e99e283111c18bcd4/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e", size = 224644, upload-time = "2025-06-09T22:59:51.35Z" }, - { url = "https://files.pythonhosted.org/packages/fb/68/c1d9c2f4a6e438e14613bad0f2973567586610cc22dcb1e1241da71de9d3/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9", size = 222125, upload-time = "2025-06-09T22:59:52.884Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d0/98e8f9a515228d708344d7c6986752be3e3192d1795f748c24bcf154ad99/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c", size = 233455, upload-time = "2025-06-09T22:59:54.74Z" }, - { url = "https://files.pythonhosted.org/packages/79/df/8a11bcec5600557f40338407d3e5bea80376ed1c01a6c0910fcfdc4b8993/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981", size = 227339, upload-time = "2025-06-09T22:59:56.187Z" }, - { url = "https://files.pythonhosted.org/packages/50/82/41cb97d9c9a5ff94438c63cc343eb7980dac4187eb625a51bdfdb7707314/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615", size = 212969, upload-time = "2025-06-09T22:59:57.604Z" }, - { url = "https://files.pythonhosted.org/packages/13/47/f9179ee5ee4f55629e4f28c660b3fdf2775c8bfde8f9c53f2de2d93f52a9/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50", size = 222862, upload-time = "2025-06-09T22:59:59.498Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/df81e41ec6b953902c8b7e3a83bee48b195cb0e5ec2eabae5d8330c78038/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa", size = 222492, upload-time = "2025-06-09T23:00:01.026Z" }, - { url = "https://files.pythonhosted.org/packages/84/17/30d6ea87fa95a9408245a948604b82c1a4b8b3e153cea596421a2aef2754/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577", size = 238250, upload-time = "2025-06-09T23:00:03.401Z" }, - { url = "https://files.pythonhosted.org/packages/8f/00/ecbeb51669e3c3df76cf2ddd66ae3e48345ec213a55e3887d216eb4fbab3/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59", size = 218720, upload-time = "2025-06-09T23:00:05.282Z" }, - { url = "https://files.pythonhosted.org/packages/1a/c0/c224ce0e0eb31cc57f67742071bb470ba8246623c1823a7530be0e76164c/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e", size = 232585, upload-time = "2025-06-09T23:00:07.962Z" }, - { url = "https://files.pythonhosted.org/packages/55/3c/34cb694abf532f31f365106deebdeac9e45c19304d83cf7d51ebbb4ca4d1/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd", size = 234248, upload-time = "2025-06-09T23:00:09.428Z" }, - { url = "https://files.pythonhosted.org/packages/98/c0/2052d8b6cecda2e70bd81299e3512fa332abb6dcd2969b9c80dfcdddbf75/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718", size = 221621, upload-time = "2025-06-09T23:00:11.32Z" }, - { url = "https://files.pythonhosted.org/packages/c5/bf/7dcebae315436903b1d98ffb791a09d674c88480c158aa171958a3ac07f0/frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e", size = 39578, upload-time = "2025-06-09T23:00:13.526Z" }, - { url = "https://files.pythonhosted.org/packages/8f/5f/f69818f017fa9a3d24d1ae39763e29b7f60a59e46d5f91b9c6b21622f4cd/frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464", size = 43830, upload-time = "2025-06-09T23:00:14.98Z" }, - { url = "https://files.pythonhosted.org/packages/34/7e/803dde33760128acd393a27eb002f2020ddb8d99d30a44bfbaab31c5f08a/frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a", size = 82251, upload-time = "2025-06-09T23:00:16.279Z" }, - { url = "https://files.pythonhosted.org/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750", size = 48183, upload-time = "2025-06-09T23:00:17.698Z" }, - { url = "https://files.pythonhosted.org/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd", size = 47107, upload-time = "2025-06-09T23:00:18.952Z" }, - { url = "https://files.pythonhosted.org/packages/79/26/85314b8a83187c76a37183ceed886381a5f992975786f883472fcb6dc5f2/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2", size = 237333, upload-time = "2025-06-09T23:00:20.275Z" }, - { url = "https://files.pythonhosted.org/packages/1f/fd/e5b64f7d2c92a41639ffb2ad44a6a82f347787abc0c7df5f49057cf11770/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f", size = 231724, upload-time = "2025-06-09T23:00:21.705Z" }, - { url = "https://files.pythonhosted.org/packages/20/fb/03395c0a43a5976af4bf7534759d214405fbbb4c114683f434dfdd3128ef/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30", size = 245842, upload-time = "2025-06-09T23:00:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/d0/15/c01c8e1dffdac5d9803507d824f27aed2ba76b6ed0026fab4d9866e82f1f/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98", size = 239767, upload-time = "2025-06-09T23:00:25.103Z" }, - { url = "https://files.pythonhosted.org/packages/14/99/3f4c6fe882c1f5514b6848aa0a69b20cb5e5d8e8f51a339d48c0e9305ed0/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86", size = 224130, upload-time = "2025-06-09T23:00:27.061Z" }, - { url = "https://files.pythonhosted.org/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae", size = 235301, upload-time = "2025-06-09T23:00:29.02Z" }, - { url = "https://files.pythonhosted.org/packages/03/3c/3e3390d75334a063181625343e8daab61b77e1b8214802cc4e8a1bb678fc/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8", size = 234606, upload-time = "2025-06-09T23:00:30.514Z" }, - { url = "https://files.pythonhosted.org/packages/23/1e/58232c19608b7a549d72d9903005e2d82488f12554a32de2d5fb59b9b1ba/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31", size = 248372, upload-time = "2025-06-09T23:00:31.966Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a4/e4a567e01702a88a74ce8a324691e62a629bf47d4f8607f24bf1c7216e7f/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7", size = 229860, upload-time = "2025-06-09T23:00:33.375Z" }, - { url = "https://files.pythonhosted.org/packages/73/a6/63b3374f7d22268b41a9db73d68a8233afa30ed164c46107b33c4d18ecdd/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5", size = 245893, upload-time = "2025-06-09T23:00:35.002Z" }, - { url = "https://files.pythonhosted.org/packages/6d/eb/d18b3f6e64799a79673c4ba0b45e4cfbe49c240edfd03a68be20002eaeaa/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898", size = 246323, upload-time = "2025-06-09T23:00:36.468Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f5/720f3812e3d06cd89a1d5db9ff6450088b8f5c449dae8ffb2971a44da506/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56", size = 233149, upload-time = "2025-06-09T23:00:37.963Z" }, - { url = "https://files.pythonhosted.org/packages/69/68/03efbf545e217d5db8446acfd4c447c15b7c8cf4dbd4a58403111df9322d/frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7", size = 39565, upload-time = "2025-06-09T23:00:39.753Z" }, - { url = "https://files.pythonhosted.org/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d", size = 44019, upload-time = "2025-06-09T23:00:40.988Z" }, - { url = "https://files.pythonhosted.org/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424, upload-time = "2025-06-09T23:00:42.24Z" }, - { url = "https://files.pythonhosted.org/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952, upload-time = "2025-06-09T23:00:43.481Z" }, - { url = "https://files.pythonhosted.org/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688, upload-time = "2025-06-09T23:00:44.793Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084, upload-time = "2025-06-09T23:00:46.125Z" }, - { url = "https://files.pythonhosted.org/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524, upload-time = "2025-06-09T23:00:47.73Z" }, - { url = "https://files.pythonhosted.org/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493, upload-time = "2025-06-09T23:00:49.742Z" }, - { url = "https://files.pythonhosted.org/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116, upload-time = "2025-06-09T23:00:51.352Z" }, - { url = "https://files.pythonhosted.org/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557, upload-time = "2025-06-09T23:00:52.855Z" }, - { url = "https://files.pythonhosted.org/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820, upload-time = "2025-06-09T23:00:54.43Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542, upload-time = "2025-06-09T23:00:56.409Z" }, - { url = "https://files.pythonhosted.org/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350, upload-time = "2025-06-09T23:00:58.468Z" }, - { url = "https://files.pythonhosted.org/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093, upload-time = "2025-06-09T23:01:00.015Z" }, - { url = "https://files.pythonhosted.org/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482, upload-time = "2025-06-09T23:01:01.474Z" }, - { url = "https://files.pythonhosted.org/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590, upload-time = "2025-06-09T23:01:02.961Z" }, - { url = "https://files.pythonhosted.org/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785, upload-time = "2025-06-09T23:01:05.095Z" }, - { url = "https://files.pythonhosted.org/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487, upload-time = "2025-06-09T23:01:06.54Z" }, - { url = "https://files.pythonhosted.org/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874, upload-time = "2025-06-09T23:01:07.752Z" }, - { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" }, - { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" }, - { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" }, - { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" }, - { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" }, - { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" }, - { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" }, - { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" }, - { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" }, - { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" }, - { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" }, - { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" }, - { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" }, - { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" }, - { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" }, - { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" }, - { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" }, - { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" }, - { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" }, - { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" }, - { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" }, - { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" }, - { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" }, - { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" }, - { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" }, - { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" }, - { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" }, - { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" }, - { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" }, - { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" }, - { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" }, - { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, -] - -[[package]] -name = "fsspec" -version = "2025.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/de/e0/bab50af11c2d75c9c4a2a26a5254573c0bd97cea152254401510950486fa/fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19", size = 304847, upload-time = "2025-09-02T19:10:49.215Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/71/70db47e4f6ce3e5c37a607355f80da8860a33226be640226ac52cb05ef2e/fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7", size = 199289, upload-time = "2025-09-02T19:10:47.708Z" }, -] - [[package]] name = "genson" version = "1.3.0" @@ -632,86 +373,27 @@ wheels = [ ] [[package]] -name = "griffe" -version = "1.14.0" +name = "googleapis-common-protos" +version = "1.70.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ec/d7/6c09dd7ce4c7837e4cdb11dce980cb45ae3cd87677298dc3b781b6bce7d3/griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13", size = 424684, upload-time = "2025-09-05T15:02:29.167Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "hf-xet" -version = "1.1.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/0f/5b60fc28ee7f8cc17a5114a584fd6b86e11c3e0a6e142a7f97a161e9640a/hf_xet-1.1.9.tar.gz", hash = "sha256:c99073ce404462e909f1d5839b2d14a3827b8fe75ed8aed551ba6609c026c803", size = 484242, upload-time = "2025-08-27T23:05:19.441Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/12/56e1abb9a44cdef59a411fe8a8673313195711b5ecce27880eb9c8fa90bd/hf_xet-1.1.9-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:a3b6215f88638dd7a6ff82cb4e738dcbf3d863bf667997c093a3c990337d1160", size = 2762553, upload-time = "2025-08-27T23:05:15.153Z" }, - { url = "https://files.pythonhosted.org/packages/3a/e6/2d0d16890c5f21b862f5df3146519c182e7f0ae49b4b4bf2bd8a40d0b05e/hf_xet-1.1.9-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9b486de7a64a66f9a172f4b3e0dfe79c9f0a93257c501296a2521a13495a698a", size = 2623216, upload-time = "2025-08-27T23:05:13.778Z" }, - { url = "https://files.pythonhosted.org/packages/81/42/7e6955cf0621e87491a1fb8cad755d5c2517803cea174229b0ec00ff0166/hf_xet-1.1.9-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c5a840c2c4e6ec875ed13703a60e3523bc7f48031dfd750923b2a4d1a5fc3c", size = 3186789, upload-time = "2025-08-27T23:05:12.368Z" }, - { url = "https://files.pythonhosted.org/packages/df/8b/759233bce05457f5f7ec062d63bbfd2d0c740b816279eaaa54be92aa452a/hf_xet-1.1.9-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:96a6139c9e44dad1c52c52520db0fffe948f6bce487cfb9d69c125f254bb3790", size = 3088747, upload-time = "2025-08-27T23:05:10.439Z" }, - { url = "https://files.pythonhosted.org/packages/6c/3c/28cc4db153a7601a996985bcb564f7b8f5b9e1a706c7537aad4b4809f358/hf_xet-1.1.9-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ad1022e9a998e784c97b2173965d07fe33ee26e4594770b7785a8cc8f922cd95", size = 3251429, upload-time = "2025-08-27T23:05:16.471Z" }, - { url = "https://files.pythonhosted.org/packages/84/17/7caf27a1d101bfcb05be85850d4aa0a265b2e1acc2d4d52a48026ef1d299/hf_xet-1.1.9-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:86754c2d6d5afb11b0a435e6e18911a4199262fe77553f8c50d75e21242193ea", size = 3354643, upload-time = "2025-08-27T23:05:17.828Z" }, - { url = "https://files.pythonhosted.org/packages/cd/50/0c39c9eed3411deadcc98749a6699d871b822473f55fe472fad7c01ec588/hf_xet-1.1.9-cp37-abi3-win_amd64.whl", hash = "sha256:5aad3933de6b725d61d51034e04174ed1dce7a57c63d530df0014dea15a40127", size = 2804797, upload-time = "2025-08-27T23:05:20.77Z" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, + { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903, upload-time = "2025-04-14T10:17:02.924Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530, upload-time = "2025-04-14T10:17:01.271Z" }, ] [[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -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]] -name = "huggingface-hub" -version = "0.34.4" +name = "griffe" +version = "1.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "tqdm" }, - { name = "typing-extensions" }, + { name = "colorama" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/45/c9/bdbe19339f76d12985bc03572f330a01a93c04dffecaaea3061bdd7fb892/huggingface_hub-0.34.4.tar.gz", hash = "sha256:a4228daa6fb001be3f4f4bdaf9a0db00e1739235702848df00885c9b5742c85c", size = 459768, upload-time = "2025-08-08T09:14:52.365Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/d7/6c09dd7ce4c7837e4cdb11dce980cb45ae3cd87677298dc3b781b6bce7d3/griffe-1.14.0.tar.gz", hash = "sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13", size = 424684, upload-time = "2025-09-05T15:02:29.167Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/7b/bb06b061991107cd8783f300adff3e7b7f284e330fd82f507f2a1417b11d/huggingface_hub-0.34.4-py3-none-any.whl", hash = "sha256:9b365d781739c93ff90c359844221beef048403f1bc1f1c123c191257c3c890a", size = 561452, upload-time = "2025-08-08T09:14:50.159Z" }, + { url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" }, ] [[package]] @@ -788,137 +470,22 @@ wheels = [ ] [[package]] -name = "jiter" -version = "0.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759, upload-time = "2025-05-18T19:04:59.73Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/7e/4011b5c77bec97cb2b572f566220364e3e21b51c48c5bd9c4a9c26b41b67/jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303", size = 317215, upload-time = "2025-05-18T19:03:04.303Z" }, - { url = "https://files.pythonhosted.org/packages/8a/4f/144c1b57c39692efc7ea7d8e247acf28e47d0912800b34d0ad815f6b2824/jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e", size = 322814, upload-time = "2025-05-18T19:03:06.433Z" }, - { url = "https://files.pythonhosted.org/packages/63/1f/db977336d332a9406c0b1f0b82be6f71f72526a806cbb2281baf201d38e3/jiter-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8b3e0068c26ddedc7abc6fac37da2d0af16b921e288a5a613f4b86f050354f", size = 345237, upload-time = "2025-05-18T19:03:07.833Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1c/aa30a4a775e8a672ad7f21532bdbfb269f0706b39c6ff14e1f86bdd9e5ff/jiter-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:286299b74cc49e25cd42eea19b72aa82c515d2f2ee12d11392c56d8701f52224", size = 370999, upload-time = "2025-05-18T19:03:09.338Z" }, - { url = "https://files.pythonhosted.org/packages/35/df/f8257abc4207830cb18880781b5f5b716bad5b2a22fb4330cfd357407c5b/jiter-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ed5649ceeaeffc28d87fb012d25a4cd356dcd53eff5acff1f0466b831dda2a7", size = 491109, upload-time = "2025-05-18T19:03:11.13Z" }, - { url = "https://files.pythonhosted.org/packages/06/76/9e1516fd7b4278aa13a2cc7f159e56befbea9aa65c71586305e7afa8b0b3/jiter-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ab0051160cb758a70716448908ef14ad476c3774bd03ddce075f3c1f90a3d6", size = 388608, upload-time = "2025-05-18T19:03:12.911Z" }, - { url = "https://files.pythonhosted.org/packages/6d/64/67750672b4354ca20ca18d3d1ccf2c62a072e8a2d452ac3cf8ced73571ef/jiter-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03997d2f37f6b67d2f5c475da4412be584e1cec273c1cfc03d642c46db43f8cf", size = 352454, upload-time = "2025-05-18T19:03:14.741Z" }, - { url = "https://files.pythonhosted.org/packages/96/4d/5c4e36d48f169a54b53a305114be3efa2bbffd33b648cd1478a688f639c1/jiter-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c404a99352d839fed80d6afd6c1d66071f3bacaaa5c4268983fc10f769112e90", size = 391833, upload-time = "2025-05-18T19:03:16.426Z" }, - { url = "https://files.pythonhosted.org/packages/0b/de/ce4a6166a78810bd83763d2fa13f85f73cbd3743a325469a4a9289af6dae/jiter-0.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66e989410b6666d3ddb27a74c7e50d0829704ede652fd4c858e91f8d64b403d0", size = 523646, upload-time = "2025-05-18T19:03:17.704Z" }, - { url = "https://files.pythonhosted.org/packages/a2/a6/3bc9acce53466972964cf4ad85efecb94f9244539ab6da1107f7aed82934/jiter-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b532d3af9ef4f6374609a3bcb5e05a1951d3bf6190dc6b176fdb277c9bbf15ee", size = 514735, upload-time = "2025-05-18T19:03:19.44Z" }, - { url = "https://files.pythonhosted.org/packages/b4/d8/243c2ab8426a2a4dea85ba2a2ba43df379ccece2145320dfd4799b9633c5/jiter-0.10.0-cp310-cp310-win32.whl", hash = "sha256:da9be20b333970e28b72edc4dff63d4fec3398e05770fb3205f7fb460eb48dd4", size = 210747, upload-time = "2025-05-18T19:03:21.184Z" }, - { url = "https://files.pythonhosted.org/packages/37/7a/8021bd615ef7788b98fc76ff533eaac846322c170e93cbffa01979197a45/jiter-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:f59e533afed0c5b0ac3eba20d2548c4a550336d8282ee69eb07b37ea526ee4e5", size = 207484, upload-time = "2025-05-18T19:03:23.046Z" }, - { url = "https://files.pythonhosted.org/packages/1b/dd/6cefc6bd68b1c3c979cecfa7029ab582b57690a31cd2f346c4d0ce7951b6/jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978", size = 317473, upload-time = "2025-05-18T19:03:25.942Z" }, - { url = "https://files.pythonhosted.org/packages/be/cf/fc33f5159ce132be1d8dd57251a1ec7a631c7df4bd11e1cd198308c6ae32/jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc", size = 321971, upload-time = "2025-05-18T19:03:27.255Z" }, - { url = "https://files.pythonhosted.org/packages/68/a4/da3f150cf1d51f6c472616fb7650429c7ce053e0c962b41b68557fdf6379/jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d", size = 345574, upload-time = "2025-05-18T19:03:28.63Z" }, - { url = "https://files.pythonhosted.org/packages/84/34/6e8d412e60ff06b186040e77da5f83bc158e9735759fcae65b37d681f28b/jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2", size = 371028, upload-time = "2025-05-18T19:03:30.292Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d9/9ee86173aae4576c35a2f50ae930d2ccb4c4c236f6cb9353267aa1d626b7/jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61", size = 491083, upload-time = "2025-05-18T19:03:31.654Z" }, - { url = "https://files.pythonhosted.org/packages/d9/2c/f955de55e74771493ac9e188b0f731524c6a995dffdcb8c255b89c6fb74b/jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db", size = 388821, upload-time = "2025-05-18T19:03:33.184Z" }, - { url = "https://files.pythonhosted.org/packages/81/5a/0e73541b6edd3f4aada586c24e50626c7815c561a7ba337d6a7eb0a915b4/jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5", size = 352174, upload-time = "2025-05-18T19:03:34.965Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c0/61eeec33b8c75b31cae42be14d44f9e6fe3ac15a4e58010256ac3abf3638/jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606", size = 391869, upload-time = "2025-05-18T19:03:36.436Z" }, - { url = "https://files.pythonhosted.org/packages/41/22/5beb5ee4ad4ef7d86f5ea5b4509f680a20706c4a7659e74344777efb7739/jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605", size = 523741, upload-time = "2025-05-18T19:03:38.168Z" }, - { url = "https://files.pythonhosted.org/packages/ea/10/768e8818538e5817c637b0df52e54366ec4cebc3346108a4457ea7a98f32/jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5", size = 514527, upload-time = "2025-05-18T19:03:39.577Z" }, - { url = "https://files.pythonhosted.org/packages/73/6d/29b7c2dc76ce93cbedabfd842fc9096d01a0550c52692dfc33d3cc889815/jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7", size = 210765, upload-time = "2025-05-18T19:03:41.271Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c9/d394706deb4c660137caf13e33d05a031d734eb99c051142e039d8ceb794/jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812", size = 209234, upload-time = "2025-05-18T19:03:42.918Z" }, - { url = "https://files.pythonhosted.org/packages/6d/b5/348b3313c58f5fbfb2194eb4d07e46a35748ba6e5b3b3046143f3040bafa/jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b", size = 312262, upload-time = "2025-05-18T19:03:44.637Z" }, - { url = "https://files.pythonhosted.org/packages/9c/4a/6a2397096162b21645162825f058d1709a02965606e537e3304b02742e9b/jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744", size = 320124, upload-time = "2025-05-18T19:03:46.341Z" }, - { url = "https://files.pythonhosted.org/packages/2a/85/1ce02cade7516b726dd88f59a4ee46914bf79d1676d1228ef2002ed2f1c9/jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2", size = 345330, upload-time = "2025-05-18T19:03:47.596Z" }, - { url = "https://files.pythonhosted.org/packages/75/d0/bb6b4f209a77190ce10ea8d7e50bf3725fc16d3372d0a9f11985a2b23eff/jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026", size = 369670, upload-time = "2025-05-18T19:03:49.334Z" }, - { url = "https://files.pythonhosted.org/packages/a0/f5/a61787da9b8847a601e6827fbc42ecb12be2c925ced3252c8ffcb56afcaf/jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c", size = 489057, upload-time = "2025-05-18T19:03:50.66Z" }, - { url = "https://files.pythonhosted.org/packages/12/e4/6f906272810a7b21406c760a53aadbe52e99ee070fc5c0cb191e316de30b/jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959", size = 389372, upload-time = "2025-05-18T19:03:51.98Z" }, - { url = "https://files.pythonhosted.org/packages/e2/ba/77013b0b8ba904bf3762f11e0129b8928bff7f978a81838dfcc958ad5728/jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a", size = 352038, upload-time = "2025-05-18T19:03:53.703Z" }, - { url = "https://files.pythonhosted.org/packages/67/27/c62568e3ccb03368dbcc44a1ef3a423cb86778a4389e995125d3d1aaa0a4/jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95", size = 391538, upload-time = "2025-05-18T19:03:55.046Z" }, - { url = "https://files.pythonhosted.org/packages/c0/72/0d6b7e31fc17a8fdce76164884edef0698ba556b8eb0af9546ae1a06b91d/jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea", size = 523557, upload-time = "2025-05-18T19:03:56.386Z" }, - { url = "https://files.pythonhosted.org/packages/2f/09/bc1661fbbcbeb6244bd2904ff3a06f340aa77a2b94e5a7373fd165960ea3/jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b", size = 514202, upload-time = "2025-05-18T19:03:57.675Z" }, - { url = "https://files.pythonhosted.org/packages/1b/84/5a5d5400e9d4d54b8004c9673bbe4403928a00d28529ff35b19e9d176b19/jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01", size = 211781, upload-time = "2025-05-18T19:03:59.025Z" }, - { url = "https://files.pythonhosted.org/packages/9b/52/7ec47455e26f2d6e5f2ea4951a0652c06e5b995c291f723973ae9e724a65/jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49", size = 206176, upload-time = "2025-05-18T19:04:00.305Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b0/279597e7a270e8d22623fea6c5d4eeac328e7d95c236ed51a2b884c54f70/jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644", size = 311617, upload-time = "2025-05-18T19:04:02.078Z" }, - { url = "https://files.pythonhosted.org/packages/91/e3/0916334936f356d605f54cc164af4060e3e7094364add445a3bc79335d46/jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a", size = 318947, upload-time = "2025-05-18T19:04:03.347Z" }, - { url = "https://files.pythonhosted.org/packages/6a/8e/fd94e8c02d0e94539b7d669a7ebbd2776e51f329bb2c84d4385e8063a2ad/jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6", size = 344618, upload-time = "2025-05-18T19:04:04.709Z" }, - { url = "https://files.pythonhosted.org/packages/6f/b0/f9f0a2ec42c6e9c2e61c327824687f1e2415b767e1089c1d9135f43816bd/jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3", size = 368829, upload-time = "2025-05-18T19:04:06.912Z" }, - { url = "https://files.pythonhosted.org/packages/e8/57/5bbcd5331910595ad53b9fd0c610392ac68692176f05ae48d6ce5c852967/jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2", size = 491034, upload-time = "2025-05-18T19:04:08.222Z" }, - { url = "https://files.pythonhosted.org/packages/9b/be/c393df00e6e6e9e623a73551774449f2f23b6ec6a502a3297aeeece2c65a/jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25", size = 388529, upload-time = "2025-05-18T19:04:09.566Z" }, - { url = "https://files.pythonhosted.org/packages/42/3e/df2235c54d365434c7f150b986a6e35f41ebdc2f95acea3036d99613025d/jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041", size = 350671, upload-time = "2025-05-18T19:04:10.98Z" }, - { url = "https://files.pythonhosted.org/packages/c6/77/71b0b24cbcc28f55ab4dbfe029f9a5b73aeadaba677843fc6dc9ed2b1d0a/jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca", size = 390864, upload-time = "2025-05-18T19:04:12.722Z" }, - { url = "https://files.pythonhosted.org/packages/6a/d3/ef774b6969b9b6178e1d1e7a89a3bd37d241f3d3ec5f8deb37bbd203714a/jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4", size = 522989, upload-time = "2025-05-18T19:04:14.261Z" }, - { url = "https://files.pythonhosted.org/packages/0c/41/9becdb1d8dd5d854142f45a9d71949ed7e87a8e312b0bede2de849388cb9/jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e", size = 513495, upload-time = "2025-05-18T19:04:15.603Z" }, - { url = "https://files.pythonhosted.org/packages/9c/36/3468e5a18238bdedae7c4d19461265b5e9b8e288d3f86cd89d00cbb48686/jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d", size = 211289, upload-time = "2025-05-18T19:04:17.541Z" }, - { url = "https://files.pythonhosted.org/packages/7e/07/1c96b623128bcb913706e294adb5f768fb7baf8db5e1338ce7b4ee8c78ef/jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4", size = 205074, upload-time = "2025-05-18T19:04:19.21Z" }, - { url = "https://files.pythonhosted.org/packages/54/46/caa2c1342655f57d8f0f2519774c6d67132205909c65e9aa8255e1d7b4f4/jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca", size = 318225, upload-time = "2025-05-18T19:04:20.583Z" }, - { url = "https://files.pythonhosted.org/packages/43/84/c7d44c75767e18946219ba2d703a5a32ab37b0bc21886a97bc6062e4da42/jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070", size = 350235, upload-time = "2025-05-18T19:04:22.363Z" }, - { url = "https://files.pythonhosted.org/packages/01/16/f5a0135ccd968b480daad0e6ab34b0c7c5ba3bc447e5088152696140dcb3/jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca", size = 207278, upload-time = "2025-05-18T19:04:23.627Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9b/1d646da42c3de6c2188fdaa15bce8ecb22b635904fc68be025e21249ba44/jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522", size = 310866, upload-time = "2025-05-18T19:04:24.891Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0e/26538b158e8a7c7987e94e7aeb2999e2e82b1f9d2e1f6e9874ddf71ebda0/jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8", size = 318772, upload-time = "2025-05-18T19:04:26.161Z" }, - { url = "https://files.pythonhosted.org/packages/7b/fb/d302893151caa1c2636d6574d213e4b34e31fd077af6050a9c5cbb42f6fb/jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216", size = 344534, upload-time = "2025-05-18T19:04:27.495Z" }, - { url = "https://files.pythonhosted.org/packages/01/d8/5780b64a149d74e347c5128d82176eb1e3241b1391ac07935693466d6219/jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4", size = 369087, upload-time = "2025-05-18T19:04:28.896Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5b/f235a1437445160e777544f3ade57544daf96ba7e96c1a5b24a6f7ac7004/jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426", size = 490694, upload-time = "2025-05-18T19:04:30.183Z" }, - { url = "https://files.pythonhosted.org/packages/85/a9/9c3d4617caa2ff89cf61b41e83820c27ebb3f7b5fae8a72901e8cd6ff9be/jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12", size = 388992, upload-time = "2025-05-18T19:04:32.028Z" }, - { url = "https://files.pythonhosted.org/packages/68/b1/344fd14049ba5c94526540af7eb661871f9c54d5f5601ff41a959b9a0bbd/jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9", size = 351723, upload-time = "2025-05-18T19:04:33.467Z" }, - { url = "https://files.pythonhosted.org/packages/41/89/4c0e345041186f82a31aee7b9d4219a910df672b9fef26f129f0cda07a29/jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a", size = 392215, upload-time = "2025-05-18T19:04:34.827Z" }, - { url = "https://files.pythonhosted.org/packages/55/58/ee607863e18d3f895feb802154a2177d7e823a7103f000df182e0f718b38/jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853", size = 522762, upload-time = "2025-05-18T19:04:36.19Z" }, - { url = "https://files.pythonhosted.org/packages/15/d0/9123fb41825490d16929e73c212de9a42913d68324a8ce3c8476cae7ac9d/jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86", size = 513427, upload-time = "2025-05-18T19:04:37.544Z" }, - { url = "https://files.pythonhosted.org/packages/d8/b3/2bd02071c5a2430d0b70403a34411fc519c2f227da7b03da9ba6a956f931/jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357", size = 210127, upload-time = "2025-05-18T19:04:38.837Z" }, - { url = "https://files.pythonhosted.org/packages/03/0c/5fe86614ea050c3ecd728ab4035534387cd41e7c1855ef6c031f1ca93e3f/jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00", size = 318527, upload-time = "2025-05-18T19:04:40.612Z" }, - { url = "https://files.pythonhosted.org/packages/b3/4a/4175a563579e884192ba6e81725fc0448b042024419be8d83aa8a80a3f44/jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5", size = 354213, upload-time = "2025-05-18T19:04:41.894Z" }, -] - -[[package]] -name = "jsonschema" -version = "4.25.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "jsonschema-specifications" }, - { name = "referencing" }, - { name = "rpds-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2025.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, -] - -[[package]] -name = "linkify-it-py" -version = "2.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "uc-micro-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2a/ae/bb56c6828e4797ba5a4821eec7c43b8bf40f69cda4d4f5f8c8a2810ec96a/linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048", size = 27946, upload-time = "2024-02-04T14:48:04.179Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/1e/b832de447dee8b582cac175871d2f6c3d5077cc56d5575cadba1fd1cccfa/linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79", size = 19820, upload-time = "2024-02-04T14:48:02.496Z" }, -] - -[[package]] -name = "litellm" -version = "1.76.2" +name = "logfire" +version = "4.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiohttp" }, - { name = "click" }, - { name = "fastuuid" }, - { name = "httpx" }, - { name = "importlib-metadata" }, - { name = "jinja2" }, - { name = "jsonschema" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "tiktoken" }, - { name = "tokenizers" }, + { name = "executing" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-sdk" }, + { name = "protobuf" }, + { name = "rich" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/a3/f7c00c660972eed1ba5ed53771ac9b4235e7fb1dc410e91d35aff2778ae7/litellm-1.76.2.tar.gz", hash = "sha256:fc7af111fa0f06943d8dbebed73f88000f9902f0d0ee0882c57d0bd5c1a37ecb", size = 10189238, upload-time = "2025-09-04T00:25:09.472Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/79/6137e240c7d798e25e2cfcf0c7f209aa5136e138c50d3809f75b1f032748/logfire-4.13.0.tar.gz", hash = "sha256:692a203e75343ac9b1a2fed1fbb4ed62d6285cfc63439c985087b8e8972024f1", size = 547685, upload-time = "2025-10-09T17:34:13.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/f4/980cc81c21424026dcb48a541654fd6f4286891825a3d0dd51f02b65cbc3/litellm-1.76.2-py3-none-any.whl", hash = "sha256:a9a2ef64a598b5b4ae245f1de6afc400856477cd6f708ff633d95e2275605a45", size = 8973847, upload-time = "2025-09-04T00:25:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/29/f9/9ce9a483d14e61be9eb01209b077c80a424f6ae9200aa1e3ce56cb6dff26/logfire-4.13.0-py3-none-any.whl", hash = "sha256:6da1ecf9f1d73dda2faea24ab10c9405ddc46dcd7e03c29c0ca3ead4ec59e56e", size = 228113, upload-time = "2025-10-09T17:34:09.859Z" }, ] [[package]] @@ -942,14 +509,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] -[package.optional-dependencies] -linkify = [ - { name = "linkify-it-py" }, -] -plugins = [ - { name = "mdit-py-plugins" }, -] - [[package]] name = "markupsafe" version = "3.0.2" @@ -1008,18 +567,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, ] -[[package]] -name = "mdit-py-plugins" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, -] - [[package]] name = "mdurl" version = "0.1.2" @@ -1038,29 +585,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, ] -[[package]] -name = "mini-swe-agent" -version = "1.10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jinja2" }, - { name = "litellm" }, - { name = "openai" }, - { name = "platformdirs" }, - { name = "prompt-toolkit" }, - { name = "python-dotenv" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "rich" }, - { name = "tenacity" }, - { name = "textual" }, - { name = "typer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ee/72/3ee88176f2a7dd99da3bf57f70c0a0aa5d3728e3b6711ed659a0cdd67eb9/mini_swe_agent-1.10.0.tar.gz", hash = "sha256:c0fe700fe58bb24aa706f5aec7a812ead63cbf522a67dcb96dba26d3fc21136f", size = 45158, upload-time = "2025-08-29T01:40:26.475Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/06/fd298c6781261084a8233218231ffebdc315593b728f4535ef2c80517b9f/mini_swe_agent-1.10.0-py3-none-any.whl", hash = "sha256:47be76446f7b1975d844270a5d8349cc09811adc8ea3c70cac47f2b29cc7a63b", size = 68367, upload-time = "2025-08-29T01:40:25.528Z" }, -] - [[package]] name = "mkdocs" version = "1.6.1" @@ -1191,108 +715,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, ] -[[package]] -name = "multidict" -version = "6.6.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843, upload-time = "2025-08-11T12:08:48.217Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/6b/86f353088c1358e76fd30b0146947fddecee812703b604ee901e85cd2a80/multidict-6.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8aa6f0bd8125ddd04a6593437bad6a7e70f300ff4180a531654aa2ab3f6d58f", size = 77054, upload-time = "2025-08-11T12:06:02.99Z" }, - { url = "https://files.pythonhosted.org/packages/19/5d/c01dc3d3788bb877bd7f5753ea6eb23c1beeca8044902a8f5bfb54430f63/multidict-6.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9e5853bbd7264baca42ffc53391b490d65fe62849bf2c690fa3f6273dbcd0cb", size = 44914, upload-time = "2025-08-11T12:06:05.264Z" }, - { url = "https://files.pythonhosted.org/packages/46/44/964dae19ea42f7d3e166474d8205f14bb811020e28bc423d46123ddda763/multidict-6.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0af5f9dee472371e36d6ae38bde009bd8ce65ac7335f55dcc240379d7bed1495", size = 44601, upload-time = "2025-08-11T12:06:06.627Z" }, - { url = "https://files.pythonhosted.org/packages/31/20/0616348a1dfb36cb2ab33fc9521de1f27235a397bf3f59338e583afadd17/multidict-6.6.4-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:d24f351e4d759f5054b641c81e8291e5d122af0fca5c72454ff77f7cbe492de8", size = 224821, upload-time = "2025-08-11T12:06:08.06Z" }, - { url = "https://files.pythonhosted.org/packages/14/26/5d8923c69c110ff51861af05bd27ca6783011b96725d59ccae6d9daeb627/multidict-6.6.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db6a3810eec08280a172a6cd541ff4a5f6a97b161d93ec94e6c4018917deb6b7", size = 242608, upload-time = "2025-08-11T12:06:09.697Z" }, - { url = "https://files.pythonhosted.org/packages/5c/cc/e2ad3ba9459aa34fa65cf1f82a5c4a820a2ce615aacfb5143b8817f76504/multidict-6.6.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a1b20a9d56b2d81e2ff52ecc0670d583eaabaa55f402e8d16dd062373dbbe796", size = 222324, upload-time = "2025-08-11T12:06:10.905Z" }, - { url = "https://files.pythonhosted.org/packages/19/db/4ed0f65701afbc2cb0c140d2d02928bb0fe38dd044af76e58ad7c54fd21f/multidict-6.6.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c9854df0eaa610a23494c32a6f44a3a550fb398b6b51a56e8c6b9b3689578db", size = 253234, upload-time = "2025-08-11T12:06:12.658Z" }, - { url = "https://files.pythonhosted.org/packages/94/c1/5160c9813269e39ae14b73debb907bfaaa1beee1762da8c4fb95df4764ed/multidict-6.6.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4bb7627fd7a968f41905a4d6343b0d63244a0623f006e9ed989fa2b78f4438a0", size = 251613, upload-time = "2025-08-11T12:06:13.97Z" }, - { url = "https://files.pythonhosted.org/packages/05/a9/48d1bd111fc2f8fb98b2ed7f9a115c55a9355358432a19f53c0b74d8425d/multidict-6.6.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caebafea30ed049c57c673d0b36238b1748683be2593965614d7b0e99125c877", size = 241649, upload-time = "2025-08-11T12:06:15.204Z" }, - { url = "https://files.pythonhosted.org/packages/85/2a/f7d743df0019408768af8a70d2037546a2be7b81fbb65f040d76caafd4c5/multidict-6.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ad887a8250eb47d3ab083d2f98db7f48098d13d42eb7a3b67d8a5c795f224ace", size = 239238, upload-time = "2025-08-11T12:06:16.467Z" }, - { url = "https://files.pythonhosted.org/packages/cb/b8/4f4bb13323c2d647323f7919201493cf48ebe7ded971717bfb0f1a79b6bf/multidict-6.6.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ed8358ae7d94ffb7c397cecb62cbac9578a83ecefc1eba27b9090ee910e2efb6", size = 233517, upload-time = "2025-08-11T12:06:18.107Z" }, - { url = "https://files.pythonhosted.org/packages/33/29/4293c26029ebfbba4f574febd2ed01b6f619cfa0d2e344217d53eef34192/multidict-6.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ecab51ad2462197a4c000b6d5701fc8585b80eecb90583635d7e327b7b6923eb", size = 243122, upload-time = "2025-08-11T12:06:19.361Z" }, - { url = "https://files.pythonhosted.org/packages/20/60/a1c53628168aa22447bfde3a8730096ac28086704a0d8c590f3b63388d0c/multidict-6.6.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c5c97aa666cf70e667dfa5af945424ba1329af5dd988a437efeb3a09430389fb", size = 248992, upload-time = "2025-08-11T12:06:20.661Z" }, - { url = "https://files.pythonhosted.org/packages/a3/3b/55443a0c372f33cae5d9ec37a6a973802884fa0ab3586659b197cf8cc5e9/multidict-6.6.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:9a950b7cf54099c1209f455ac5970b1ea81410f2af60ed9eb3c3f14f0bfcf987", size = 243708, upload-time = "2025-08-11T12:06:21.891Z" }, - { url = "https://files.pythonhosted.org/packages/7c/60/a18c6900086769312560b2626b18e8cca22d9e85b1186ba77f4755b11266/multidict-6.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:163c7ea522ea9365a8a57832dea7618e6cbdc3cd75f8c627663587459a4e328f", size = 237498, upload-time = "2025-08-11T12:06:23.206Z" }, - { url = "https://files.pythonhosted.org/packages/11/3d/8bdd8bcaff2951ce2affccca107a404925a2beafedd5aef0b5e4a71120a6/multidict-6.6.4-cp310-cp310-win32.whl", hash = "sha256:17d2cbbfa6ff20821396b25890f155f40c986f9cfbce5667759696d83504954f", size = 41415, upload-time = "2025-08-11T12:06:24.77Z" }, - { url = "https://files.pythonhosted.org/packages/c0/53/cab1ad80356a4cd1b685a254b680167059b433b573e53872fab245e9fc95/multidict-6.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:ce9a40fbe52e57e7edf20113a4eaddfacac0561a0879734e636aa6d4bb5e3fb0", size = 46046, upload-time = "2025-08-11T12:06:25.893Z" }, - { url = "https://files.pythonhosted.org/packages/cf/9a/874212b6f5c1c2d870d0a7adc5bb4cfe9b0624fa15cdf5cf757c0f5087ae/multidict-6.6.4-cp310-cp310-win_arm64.whl", hash = "sha256:01d0959807a451fe9fdd4da3e139cb5b77f7328baf2140feeaf233e1d777b729", size = 43147, upload-time = "2025-08-11T12:06:27.534Z" }, - { url = "https://files.pythonhosted.org/packages/6b/7f/90a7f01e2d005d6653c689039977f6856718c75c5579445effb7e60923d1/multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c", size = 76472, upload-time = "2025-08-11T12:06:29.006Z" }, - { url = "https://files.pythonhosted.org/packages/54/a3/bed07bc9e2bb302ce752f1dabc69e884cd6a676da44fb0e501b246031fdd/multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb", size = 44634, upload-time = "2025-08-11T12:06:30.374Z" }, - { url = "https://files.pythonhosted.org/packages/a7/4b/ceeb4f8f33cf81277da464307afeaf164fb0297947642585884f5cad4f28/multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e", size = 44282, upload-time = "2025-08-11T12:06:31.958Z" }, - { url = "https://files.pythonhosted.org/packages/03/35/436a5da8702b06866189b69f655ffdb8f70796252a8772a77815f1812679/multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded", size = 229696, upload-time = "2025-08-11T12:06:33.087Z" }, - { url = "https://files.pythonhosted.org/packages/b6/0e/915160be8fecf1fca35f790c08fb74ca684d752fcba62c11daaf3d92c216/multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683", size = 246665, upload-time = "2025-08-11T12:06:34.448Z" }, - { url = "https://files.pythonhosted.org/packages/08/ee/2f464330acd83f77dcc346f0b1a0eaae10230291450887f96b204b8ac4d3/multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a", size = 225485, upload-time = "2025-08-11T12:06:35.672Z" }, - { url = "https://files.pythonhosted.org/packages/71/cc/9a117f828b4d7fbaec6adeed2204f211e9caf0a012692a1ee32169f846ae/multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9", size = 257318, upload-time = "2025-08-11T12:06:36.98Z" }, - { url = "https://files.pythonhosted.org/packages/25/77/62752d3dbd70e27fdd68e86626c1ae6bccfebe2bb1f84ae226363e112f5a/multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50", size = 254689, upload-time = "2025-08-11T12:06:38.233Z" }, - { url = "https://files.pythonhosted.org/packages/00/6e/fac58b1072a6fc59af5e7acb245e8754d3e1f97f4f808a6559951f72a0d4/multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52", size = 246709, upload-time = "2025-08-11T12:06:39.517Z" }, - { url = "https://files.pythonhosted.org/packages/01/ef/4698d6842ef5e797c6db7744b0081e36fb5de3d00002cc4c58071097fac3/multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6", size = 243185, upload-time = "2025-08-11T12:06:40.796Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c9/d82e95ae1d6e4ef396934e9b0e942dfc428775f9554acf04393cce66b157/multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e", size = 237838, upload-time = "2025-08-11T12:06:42.595Z" }, - { url = "https://files.pythonhosted.org/packages/57/cf/f94af5c36baaa75d44fab9f02e2a6bcfa0cd90acb44d4976a80960759dbc/multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3", size = 246368, upload-time = "2025-08-11T12:06:44.304Z" }, - { url = "https://files.pythonhosted.org/packages/4a/fe/29f23460c3d995f6a4b678cb2e9730e7277231b981f0b234702f0177818a/multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c", size = 253339, upload-time = "2025-08-11T12:06:45.597Z" }, - { url = "https://files.pythonhosted.org/packages/29/b6/fd59449204426187b82bf8a75f629310f68c6adc9559dc922d5abe34797b/multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b", size = 246933, upload-time = "2025-08-11T12:06:46.841Z" }, - { url = "https://files.pythonhosted.org/packages/19/52/d5d6b344f176a5ac3606f7a61fb44dc746e04550e1a13834dff722b8d7d6/multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f", size = 242225, upload-time = "2025-08-11T12:06:48.588Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d3/5b2281ed89ff4d5318d82478a2a2450fcdfc3300da48ff15c1778280ad26/multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2", size = 41306, upload-time = "2025-08-11T12:06:49.95Z" }, - { url = "https://files.pythonhosted.org/packages/74/7d/36b045c23a1ab98507aefd44fd8b264ee1dd5e5010543c6fccf82141ccef/multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e", size = 46029, upload-time = "2025-08-11T12:06:51.082Z" }, - { url = "https://files.pythonhosted.org/packages/0f/5e/553d67d24432c5cd52b49047f2d248821843743ee6d29a704594f656d182/multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf", size = 43017, upload-time = "2025-08-11T12:06:52.243Z" }, - { url = "https://files.pythonhosted.org/packages/05/f6/512ffd8fd8b37fb2680e5ac35d788f1d71bbaf37789d21a820bdc441e565/multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8", size = 76516, upload-time = "2025-08-11T12:06:53.393Z" }, - { url = "https://files.pythonhosted.org/packages/99/58/45c3e75deb8855c36bd66cc1658007589662ba584dbf423d01df478dd1c5/multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3", size = 45394, upload-time = "2025-08-11T12:06:54.555Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ca/e8c4472a93a26e4507c0b8e1f0762c0d8a32de1328ef72fd704ef9cc5447/multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b", size = 43591, upload-time = "2025-08-11T12:06:55.672Z" }, - { url = "https://files.pythonhosted.org/packages/05/51/edf414f4df058574a7265034d04c935aa84a89e79ce90fcf4df211f47b16/multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287", size = 237215, upload-time = "2025-08-11T12:06:57.213Z" }, - { url = "https://files.pythonhosted.org/packages/c8/45/8b3d6dbad8cf3252553cc41abea09ad527b33ce47a5e199072620b296902/multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138", size = 258299, upload-time = "2025-08-11T12:06:58.946Z" }, - { url = "https://files.pythonhosted.org/packages/3c/e8/8ca2e9a9f5a435fc6db40438a55730a4bf4956b554e487fa1b9ae920f825/multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6", size = 242357, upload-time = "2025-08-11T12:07:00.301Z" }, - { url = "https://files.pythonhosted.org/packages/0f/84/80c77c99df05a75c28490b2af8f7cba2a12621186e0a8b0865d8e745c104/multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9", size = 268369, upload-time = "2025-08-11T12:07:01.638Z" }, - { url = "https://files.pythonhosted.org/packages/0d/e9/920bfa46c27b05fb3e1ad85121fd49f441492dca2449c5bcfe42e4565d8a/multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c", size = 269341, upload-time = "2025-08-11T12:07:02.943Z" }, - { url = "https://files.pythonhosted.org/packages/af/65/753a2d8b05daf496f4a9c367fe844e90a1b2cac78e2be2c844200d10cc4c/multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402", size = 256100, upload-time = "2025-08-11T12:07:04.564Z" }, - { url = "https://files.pythonhosted.org/packages/09/54/655be13ae324212bf0bc15d665a4e34844f34c206f78801be42f7a0a8aaa/multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7", size = 253584, upload-time = "2025-08-11T12:07:05.914Z" }, - { url = "https://files.pythonhosted.org/packages/5c/74/ab2039ecc05264b5cec73eb018ce417af3ebb384ae9c0e9ed42cb33f8151/multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f", size = 251018, upload-time = "2025-08-11T12:07:08.301Z" }, - { url = "https://files.pythonhosted.org/packages/af/0a/ccbb244ac848e56c6427f2392741c06302bbfba49c0042f1eb3c5b606497/multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d", size = 251477, upload-time = "2025-08-11T12:07:10.248Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b0/0ed49bba775b135937f52fe13922bc64a7eaf0a3ead84a36e8e4e446e096/multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7", size = 263575, upload-time = "2025-08-11T12:07:11.928Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d9/7fb85a85e14de2e44dfb6a24f03c41e2af8697a6df83daddb0e9b7569f73/multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802", size = 259649, upload-time = "2025-08-11T12:07:13.244Z" }, - { url = "https://files.pythonhosted.org/packages/03/9e/b3a459bcf9b6e74fa461a5222a10ff9b544cb1cd52fd482fb1b75ecda2a2/multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24", size = 251505, upload-time = "2025-08-11T12:07:14.57Z" }, - { url = "https://files.pythonhosted.org/packages/86/a2/8022f78f041dfe6d71e364001a5cf987c30edfc83c8a5fb7a3f0974cff39/multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793", size = 41888, upload-time = "2025-08-11T12:07:15.904Z" }, - { url = "https://files.pythonhosted.org/packages/c7/eb/d88b1780d43a56db2cba24289fa744a9d216c1a8546a0dc3956563fd53ea/multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e", size = 46072, upload-time = "2025-08-11T12:07:17.045Z" }, - { url = "https://files.pythonhosted.org/packages/9f/16/b929320bf5750e2d9d4931835a4c638a19d2494a5b519caaaa7492ebe105/multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364", size = 43222, upload-time = "2025-08-11T12:07:18.328Z" }, - { url = "https://files.pythonhosted.org/packages/3a/5d/e1db626f64f60008320aab00fbe4f23fc3300d75892a3381275b3d284580/multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e", size = 75848, upload-time = "2025-08-11T12:07:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657", size = 45060, upload-time = "2025-08-11T12:07:21.163Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da", size = 43269, upload-time = "2025-08-11T12:07:22.392Z" }, - { url = "https://files.pythonhosted.org/packages/dc/31/d54eb0c62516776f36fe67f84a732f97e0b0e12f98d5685bebcc6d396910/multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa", size = 237158, upload-time = "2025-08-11T12:07:23.636Z" }, - { url = "https://files.pythonhosted.org/packages/c4/1c/8a10c1c25b23156e63b12165a929d8eb49a6ed769fdbefb06e6f07c1e50d/multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f", size = 257076, upload-time = "2025-08-11T12:07:25.049Z" }, - { url = "https://files.pythonhosted.org/packages/ad/86/90e20b5771d6805a119e483fd3d1e8393e745a11511aebca41f0da38c3e2/multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0", size = 240694, upload-time = "2025-08-11T12:07:26.458Z" }, - { url = "https://files.pythonhosted.org/packages/e7/49/484d3e6b535bc0555b52a0a26ba86e4d8d03fd5587d4936dc59ba7583221/multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879", size = 266350, upload-time = "2025-08-11T12:07:27.94Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b4/aa4c5c379b11895083d50021e229e90c408d7d875471cb3abf721e4670d6/multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a", size = 267250, upload-time = "2025-08-11T12:07:29.303Z" }, - { url = "https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f", size = 254900, upload-time = "2025-08-11T12:07:30.764Z" }, - { url = "https://files.pythonhosted.org/packages/17/38/58b27fed927c07035abc02befacab42491e7388ca105e087e6e0215ead64/multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5", size = 252355, upload-time = "2025-08-11T12:07:32.205Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a1/dad75d23a90c29c02b5d6f3d7c10ab36c3197613be5d07ec49c7791e186c/multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438", size = 250061, upload-time = "2025-08-11T12:07:33.623Z" }, - { url = "https://files.pythonhosted.org/packages/b8/1a/ac2216b61c7f116edab6dc3378cca6c70dc019c9a457ff0d754067c58b20/multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e", size = 249675, upload-time = "2025-08-11T12:07:34.958Z" }, - { url = "https://files.pythonhosted.org/packages/d4/79/1916af833b800d13883e452e8e0977c065c4ee3ab7a26941fbfdebc11895/multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7", size = 261247, upload-time = "2025-08-11T12:07:36.588Z" }, - { url = "https://files.pythonhosted.org/packages/c5/65/d1f84fe08ac44a5fc7391cbc20a7cedc433ea616b266284413fd86062f8c/multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812", size = 257960, upload-time = "2025-08-11T12:07:39.735Z" }, - { url = "https://files.pythonhosted.org/packages/13/b5/29ec78057d377b195ac2c5248c773703a6b602e132a763e20ec0457e7440/multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a", size = 250078, upload-time = "2025-08-11T12:07:41.525Z" }, - { url = "https://files.pythonhosted.org/packages/c4/0e/7e79d38f70a872cae32e29b0d77024bef7834b0afb406ddae6558d9e2414/multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69", size = 41708, upload-time = "2025-08-11T12:07:43.405Z" }, - { url = "https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf", size = 45912, upload-time = "2025-08-11T12:07:45.082Z" }, - { url = "https://files.pythonhosted.org/packages/c7/87/3bac136181e271e29170d8d71929cdeddeb77f3e8b6a0c08da3a8e9da114/multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605", size = 43076, upload-time = "2025-08-11T12:07:46.746Z" }, - { url = "https://files.pythonhosted.org/packages/64/94/0a8e63e36c049b571c9ae41ee301ada29c3fee9643d9c2548d7d558a1d99/multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb", size = 82812, upload-time = "2025-08-11T12:07:48.402Z" }, - { url = "https://files.pythonhosted.org/packages/25/1a/be8e369dfcd260d2070a67e65dd3990dd635cbd735b98da31e00ea84cd4e/multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e", size = 48313, upload-time = "2025-08-11T12:07:49.679Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/dd4ade298674b2f9a7b06a32c94ffbc0497354df8285f27317c66433ce3b/multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f", size = 46777, upload-time = "2025-08-11T12:07:51.318Z" }, - { url = "https://files.pythonhosted.org/packages/89/db/98aa28bc7e071bfba611ac2ae803c24e96dd3a452b4118c587d3d872c64c/multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773", size = 229321, upload-time = "2025-08-11T12:07:52.965Z" }, - { url = "https://files.pythonhosted.org/packages/c7/bc/01ddda2a73dd9d167bd85d0e8ef4293836a8f82b786c63fb1a429bc3e678/multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e", size = 249954, upload-time = "2025-08-11T12:07:54.423Z" }, - { url = "https://files.pythonhosted.org/packages/06/78/6b7c0f020f9aa0acf66d0ab4eb9f08375bac9a50ff5e3edb1c4ccd59eafc/multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0", size = 228612, upload-time = "2025-08-11T12:07:55.914Z" }, - { url = "https://files.pythonhosted.org/packages/00/44/3faa416f89b2d5d76e9d447296a81521e1c832ad6e40b92f990697b43192/multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395", size = 257528, upload-time = "2025-08-11T12:07:57.371Z" }, - { url = "https://files.pythonhosted.org/packages/05/5f/77c03b89af0fcb16f018f668207768191fb9dcfb5e3361a5e706a11db2c9/multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45", size = 256329, upload-time = "2025-08-11T12:07:58.844Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e9/ed750a2a9afb4f8dc6f13dc5b67b514832101b95714f1211cd42e0aafc26/multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb", size = 247928, upload-time = "2025-08-11T12:08:01.037Z" }, - { url = "https://files.pythonhosted.org/packages/1f/b5/e0571bc13cda277db7e6e8a532791d4403dacc9850006cb66d2556e649c0/multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5", size = 245228, upload-time = "2025-08-11T12:08:02.96Z" }, - { url = "https://files.pythonhosted.org/packages/f3/a3/69a84b0eccb9824491f06368f5b86e72e4af54c3067c37c39099b6687109/multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141", size = 235869, upload-time = "2025-08-11T12:08:04.746Z" }, - { url = "https://files.pythonhosted.org/packages/a9/9d/28802e8f9121a6a0804fa009debf4e753d0a59969ea9f70be5f5fdfcb18f/multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d", size = 243446, upload-time = "2025-08-11T12:08:06.332Z" }, - { url = "https://files.pythonhosted.org/packages/38/ea/6c98add069b4878c1d66428a5f5149ddb6d32b1f9836a826ac764b9940be/multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d", size = 252299, upload-time = "2025-08-11T12:08:07.931Z" }, - { url = "https://files.pythonhosted.org/packages/3a/09/8fe02d204473e14c0af3affd50af9078839dfca1742f025cca765435d6b4/multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0", size = 246926, upload-time = "2025-08-11T12:08:09.467Z" }, - { url = "https://files.pythonhosted.org/packages/37/3d/7b1e10d774a6df5175ecd3c92bff069e77bed9ec2a927fdd4ff5fe182f67/multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92", size = 243383, upload-time = "2025-08-11T12:08:10.981Z" }, - { url = "https://files.pythonhosted.org/packages/50/b0/a6fae46071b645ae98786ab738447de1ef53742eaad949f27e960864bb49/multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e", size = 47775, upload-time = "2025-08-11T12:08:12.439Z" }, - { url = "https://files.pythonhosted.org/packages/b2/0a/2436550b1520091af0600dff547913cb2d66fbac27a8c33bc1b1bccd8d98/multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4", size = 53100, upload-time = "2025-08-11T12:08:13.823Z" }, - { url = "https://files.pythonhosted.org/packages/97/ea/43ac51faff934086db9c072a94d327d71b7d8b40cd5dcb47311330929ef0/multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad", size = 45501, upload-time = "2025-08-11T12:08:15.173Z" }, - { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, -] - [[package]] name = "mypy-extensions" version = "1.1.0" @@ -1312,22 +734,100 @@ wheels = [ ] [[package]] -name = "openai" -version = "1.106.1" +name = "opentelemetry-api" +version = "1.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "tqdm" }, + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/04/05040d7ce33a907a2a02257e601992f0cdf11c73b33f13c4492bf6c3d6d5/opentelemetry_api-1.37.0.tar.gz", hash = "sha256:540735b120355bd5112738ea53621f8d5edb35ebcd6fe21ada3ab1c61d1cd9a7", size = 64923, upload-time = "2025-09-11T10:29:01.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/48/28ed9e55dcf2f453128df738210a980e09f4e468a456fa3c763dbc8be70a/opentelemetry_api-1.37.0-py3-none-any.whl", hash = "sha256:accf2024d3e89faec14302213bc39550ec0f4095d1cf5ca688e1bfb1c8612f47", size = 65732, upload-time = "2025-09-11T10:28:41.826Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/6c/10018cbcc1e6fff23aac67d7fd977c3d692dbe5f9ef9bb4db5c1268726cc/opentelemetry_exporter_otlp_proto_common-1.37.0.tar.gz", hash = "sha256:c87a1bdd9f41fdc408d9cc9367bb53f8d2602829659f2b90be9f9d79d0bfe62c", size = 20430, upload-time = "2025-09-11T10:29:03.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/13/b4ef09837409a777f3c0af2a5b4ba9b7af34872bc43609dda0c209e4060d/opentelemetry_exporter_otlp_proto_common-1.37.0-py3-none-any.whl", hash = "sha256:53038428449c559b0c564b8d718df3314da387109c4d36bd1b94c9a641b0292e", size = 18359, upload-time = "2025-09-11T10:28:44.939Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/e3/6e320aeb24f951449e73867e53c55542bebbaf24faeee7623ef677d66736/opentelemetry_exporter_otlp_proto_http-1.37.0.tar.gz", hash = "sha256:e52e8600f1720d6de298419a802108a8f5afa63c96809ff83becb03f874e44ac", size = 17281, upload-time = "2025-09-11T10:29:04.844Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/e9/70d74a664d83976556cec395d6bfedd9b85ec1498b778367d5f93e373397/opentelemetry_exporter_otlp_proto_http-1.37.0-py3-none-any.whl", hash = "sha256:54c42b39945a6cc9d9a2a33decb876eabb9547e0dcb49df090122773447f1aef", size = 19576, upload-time = "2025-09-11T10:28:46.726Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.58b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/36/7c307d9be8ce4ee7beb86d7f1d31027f2a6a89228240405a858d6e4d64f9/opentelemetry_instrumentation-0.58b0.tar.gz", hash = "sha256:df640f3ac715a3e05af145c18f527f4422c6ab6c467e40bd24d2ad75a00cb705", size = 31549, upload-time = "2025-09-11T11:42:14.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/db/5ff1cd6c5ca1d12ecf1b73be16fbb2a8af2114ee46d4b0e6d4b23f4f4db7/opentelemetry_instrumentation-0.58b0-py3-none-any.whl", hash = "sha256:50f97ac03100676c9f7fc28197f8240c7290ca1baa12da8bfbb9a1de4f34cc45", size = 33019, upload-time = "2025-09-11T11:41:00.624Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/ea/a75f36b463a36f3c5a10c0b5292c58b31dbdde74f6f905d3d0ab2313987b/opentelemetry_proto-1.37.0.tar.gz", hash = "sha256:30f5c494faf66f77faeaefa35ed4443c5edb3b0aa46dad073ed7210e1a789538", size = 46151, upload-time = "2025-09-11T10:29:11.04Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/25/f89ea66c59bd7687e218361826c969443c4fa15dfe89733f3bf1e2a9e971/opentelemetry_proto-1.37.0-py3-none-any.whl", hash = "sha256:8ed8c066ae8828bbf0c39229979bdf583a126981142378a9cbe9d6fd5701c6e2", size = 72534, upload-time = "2025-09-11T10:28:56.831Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/b6/1aff7d6b8e9f0c3ac26bfbb57b9861a6711d5d60bd7dd5f7eebbf80509b7/openai-1.106.1.tar.gz", hash = "sha256:5f575967e3a05555825c43829cdcd50be6e49ab6a3e5262f0937a3f791f917f1", size = 561095, upload-time = "2025-09-04T18:17:15.303Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/62/2e0ca80d7fe94f0b193135375da92c640d15fe81f636658d2acf373086bc/opentelemetry_sdk-1.37.0.tar.gz", hash = "sha256:cc8e089c10953ded765b5ab5669b198bbe0af1b3f89f1007d19acd32dc46dda5", size = 170404, upload-time = "2025-09-11T10:29:11.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/e1/47887212baa7bc0532880d33d5eafbdb46fcc4b53789b903282a74a85b5b/openai-1.106.1-py3-none-any.whl", hash = "sha256:bfdef37c949f80396c59f2c17e0eda35414979bc07ef3379596a93c9ed044f3a", size = 930768, upload-time = "2025-09-04T18:17:13.349Z" }, + { url = "https://files.pythonhosted.org/packages/9f/62/9f4ad6a54126fb00f7ed4bb5034964c6e4f00fcd5a905e115bd22707e20d/opentelemetry_sdk-1.37.0-py3-none-any.whl", hash = "sha256:8f3c3c22063e52475c5dbced7209495c2c16723d016d39287dfc215d1771257c", size = 131941, upload-time = "2025-09-11T10:28:57.83Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.58b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/1b/90701d91e6300d9f2fb352153fb1721ed99ed1f6ea14fa992c756016e63a/opentelemetry_semantic_conventions-0.58b0.tar.gz", hash = "sha256:6bd46f51264279c433755767bb44ad00f1c9e2367e1b42af563372c5a6fa0c25", size = 129867, upload-time = "2025-09-11T10:29:12.597Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/90/68152b7465f50285d3ce2481b3aec2f82822e3f52e5152eeeaf516bab841/opentelemetry_semantic_conventions-0.58b0-py3-none-any.whl", hash = "sha256:5564905ab1458b96684db1340232729fce3b5375a06e140e8904c78e4f815b28", size = 207954, upload-time = "2025-09-11T10:28:59.218Z" }, ] [[package]] @@ -1392,104 +892,17 @@ wheels = [ ] [[package]] -name = "prompt-toolkit" -version = "3.0.52" +name = "protobuf" +version = "6.32.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, -] - -[[package]] -name = "propcache" -version = "0.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/14/510deed325e262afeb8b360043c5d7c960da7d3ecd6d6f9496c9c56dc7f4/propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770", size = 73178, upload-time = "2025-06-09T22:53:40.126Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4e/ad52a7925ff01c1325653a730c7ec3175a23f948f08626a534133427dcff/propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3", size = 43133, upload-time = "2025-06-09T22:53:41.965Z" }, - { url = "https://files.pythonhosted.org/packages/63/7c/e9399ba5da7780871db4eac178e9c2e204c23dd3e7d32df202092a1ed400/propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3", size = 43039, upload-time = "2025-06-09T22:53:43.268Z" }, - { url = "https://files.pythonhosted.org/packages/22/e1/58da211eb8fdc6fc854002387d38f415a6ca5f5c67c1315b204a5d3e9d7a/propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e", size = 201903, upload-time = "2025-06-09T22:53:44.872Z" }, - { url = "https://files.pythonhosted.org/packages/c4/0a/550ea0f52aac455cb90111c8bab995208443e46d925e51e2f6ebdf869525/propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220", size = 213362, upload-time = "2025-06-09T22:53:46.707Z" }, - { url = "https://files.pythonhosted.org/packages/5a/af/9893b7d878deda9bb69fcf54600b247fba7317761b7db11fede6e0f28bd0/propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb", size = 210525, upload-time = "2025-06-09T22:53:48.547Z" }, - { url = "https://files.pythonhosted.org/packages/7c/bb/38fd08b278ca85cde36d848091ad2b45954bc5f15cce494bb300b9285831/propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614", size = 198283, upload-time = "2025-06-09T22:53:50.067Z" }, - { url = "https://files.pythonhosted.org/packages/78/8c/9fe55bd01d362bafb413dfe508c48753111a1e269737fa143ba85693592c/propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50", size = 191872, upload-time = "2025-06-09T22:53:51.438Z" }, - { url = "https://files.pythonhosted.org/packages/54/14/4701c33852937a22584e08abb531d654c8bcf7948a8f87ad0a4822394147/propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339", size = 199452, upload-time = "2025-06-09T22:53:53.229Z" }, - { url = "https://files.pythonhosted.org/packages/16/44/447f2253d859602095356007657ee535e0093215ea0b3d1d6a41d16e5201/propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0", size = 191567, upload-time = "2025-06-09T22:53:54.541Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b3/e4756258749bb2d3b46defcff606a2f47410bab82be5824a67e84015b267/propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2", size = 193015, upload-time = "2025-06-09T22:53:56.44Z" }, - { url = "https://files.pythonhosted.org/packages/1e/df/e6d3c7574233164b6330b9fd697beeac402afd367280e6dc377bb99b43d9/propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7", size = 204660, upload-time = "2025-06-09T22:53:57.839Z" }, - { url = "https://files.pythonhosted.org/packages/b2/53/e4d31dd5170b4a0e2e6b730f2385a96410633b4833dc25fe5dffd1f73294/propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b", size = 206105, upload-time = "2025-06-09T22:53:59.638Z" }, - { url = "https://files.pythonhosted.org/packages/7f/fe/74d54cf9fbe2a20ff786e5f7afcfde446588f0cf15fb2daacfbc267b866c/propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c", size = 196980, upload-time = "2025-06-09T22:54:01.071Z" }, - { url = "https://files.pythonhosted.org/packages/22/ec/c469c9d59dada8a7679625e0440b544fe72e99311a4679c279562051f6fc/propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70", size = 37679, upload-time = "2025-06-09T22:54:03.003Z" }, - { url = "https://files.pythonhosted.org/packages/38/35/07a471371ac89d418f8d0b699c75ea6dca2041fbda360823de21f6a9ce0a/propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9", size = 41459, upload-time = "2025-06-09T22:54:04.134Z" }, - { url = "https://files.pythonhosted.org/packages/80/8d/e8b436717ab9c2cfc23b116d2c297305aa4cd8339172a456d61ebf5669b8/propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be", size = 74207, upload-time = "2025-06-09T22:54:05.399Z" }, - { url = "https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f", size = 43648, upload-time = "2025-06-09T22:54:08.023Z" }, - { url = "https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9", size = 43496, upload-time = "2025-06-09T22:54:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/ce/e96392460f9fb68461fabab3e095cb00c8ddf901205be4eae5ce246e5b7e/propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf", size = 217288, upload-time = "2025-06-09T22:54:10.466Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2a/866726ea345299f7ceefc861a5e782b045545ae6940851930a6adaf1fca6/propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9", size = 227456, upload-time = "2025-06-09T22:54:11.828Z" }, - { url = "https://files.pythonhosted.org/packages/de/03/07d992ccb6d930398689187e1b3c718339a1c06b8b145a8d9650e4726166/propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66", size = 225429, upload-time = "2025-06-09T22:54:13.823Z" }, - { url = "https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df", size = 213472, upload-time = "2025-06-09T22:54:15.232Z" }, - { url = "https://files.pythonhosted.org/packages/a6/85/f01f5d97e54e428885a5497ccf7f54404cbb4f906688a1690cd51bf597dc/propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2", size = 204480, upload-time = "2025-06-09T22:54:17.104Z" }, - { url = "https://files.pythonhosted.org/packages/e3/79/7bf5ab9033b8b8194cc3f7cf1aaa0e9c3256320726f64a3e1f113a812dce/propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7", size = 214530, upload-time = "2025-06-09T22:54:18.512Z" }, - { url = "https://files.pythonhosted.org/packages/31/0b/bd3e0c00509b609317df4a18e6b05a450ef2d9a963e1d8bc9c9415d86f30/propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95", size = 205230, upload-time = "2025-06-09T22:54:19.947Z" }, - { url = "https://files.pythonhosted.org/packages/7a/23/fae0ff9b54b0de4e819bbe559508da132d5683c32d84d0dc2ccce3563ed4/propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e", size = 206754, upload-time = "2025-06-09T22:54:21.716Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7f/ad6a3c22630aaa5f618b4dc3c3598974a72abb4c18e45a50b3cdd091eb2f/propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e", size = 218430, upload-time = "2025-06-09T22:54:23.17Z" }, - { url = "https://files.pythonhosted.org/packages/5b/2c/ba4f1c0e8a4b4c75910742f0d333759d441f65a1c7f34683b4a74c0ee015/propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf", size = 223884, upload-time = "2025-06-09T22:54:25.539Z" }, - { url = "https://files.pythonhosted.org/packages/88/e4/ebe30fc399e98572019eee82ad0caf512401661985cbd3da5e3140ffa1b0/propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e", size = 211480, upload-time = "2025-06-09T22:54:26.892Z" }, - { url = "https://files.pythonhosted.org/packages/96/0a/7d5260b914e01d1d0906f7f38af101f8d8ed0dc47426219eeaf05e8ea7c2/propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897", size = 37757, upload-time = "2025-06-09T22:54:28.241Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39", size = 41500, upload-time = "2025-06-09T22:54:29.4Z" }, - { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" }, - { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" }, - { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" }, - { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" }, - { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" }, - { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" }, - { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" }, - { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" }, - { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" }, - { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" }, - { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" }, - { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" }, - { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, - { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, - { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, - { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, - { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, - { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, - { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, - { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, - { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, - { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, - { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, - { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, - { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, - { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, - { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, - { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, - { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, - { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, - { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, - { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, - { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, - { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/fa/a4/cc17347aa2897568beece2e674674359f911d6fe21b0b8d6268cd42727ac/protobuf-6.32.1.tar.gz", hash = "sha256:ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d", size = 440635, upload-time = "2025-09-11T21:38:42.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/98/645183ea03ab3995d29086b8bf4f7562ebd3d10c9a4b14ee3f20d47cfe50/protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085", size = 424411, upload-time = "2025-09-11T21:38:27.427Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f3/6f58f841f6ebafe076cebeae33fc336e900619d34b1c93e4b5c97a81fdfa/protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1", size = 435738, upload-time = "2025-09-11T21:38:30.959Z" }, + { url = "https://files.pythonhosted.org/packages/10/56/a8a3f4e7190837139e68c7002ec749190a163af3e330f65d90309145a210/protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281", size = 426454, upload-time = "2025-09-11T21:38:34.076Z" }, + { url = "https://files.pythonhosted.org/packages/3f/be/8dd0a927c559b37d7a6c8ab79034fd167dcc1f851595f2e641ad62be8643/protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4", size = 322874, upload-time = "2025-09-11T21:38:35.509Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f6/88d77011b605ef979aace37b7703e4eefad066f7e84d935e5a696515c2dd/protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710", size = 322013, upload-time = "2025-09-11T21:38:37.017Z" }, + { url = "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346", size = 169289, upload-time = "2025-09-11T21:38:41.234Z" }, ] [[package]] @@ -1737,99 +1150,6 @@ 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 = "referencing" -version = "0.36.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "rpds-py" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, -] - -[[package]] -name = "regex" -version = "2025.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/5a/4c63457fbcaf19d138d72b2e9b39405954f98c0349b31c601bfcb151582c/regex-2025.9.1.tar.gz", hash = "sha256:88ac07b38d20b54d79e704e38aa3bd2c0f8027432164226bdee201a1c0c9c9ff", size = 400852, upload-time = "2025-09-01T22:10:10.479Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/c1/ed9ef923156105a78aa004f9390e5dd87eadc29f5ca8840f172cadb638de/regex-2025.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5aa2a6a73bf218515484b36a0d20c6ad9dc63f6339ff6224147b0e2c095ee55", size = 484813, upload-time = "2025-09-01T22:07:45.528Z" }, - { url = "https://files.pythonhosted.org/packages/05/de/97957618a774c67f892609eee2fafe3e30703fbbba66de5e6b79d7196dbc/regex-2025.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c2ff5c01d5e47ad5fc9d31bcd61e78c2fa0068ed00cab86b7320214446da766", size = 288981, upload-time = "2025-09-01T22:07:48.464Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b0/441afadd0a6ffccbd58a9663e5bdd182daa237893e5f8ceec6ff9df4418a/regex-2025.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d49dc84e796b666181de8a9973284cad6616335f01b52bf099643253094920fc", size = 286608, upload-time = "2025-09-01T22:07:50.484Z" }, - { url = "https://files.pythonhosted.org/packages/6e/cf/d89aecaf17e999ab11a3ef73fc9ab8b64f4e156f121250ef84340b35338d/regex-2025.9.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9914fe1040874f83c15fcea86d94ea54091b0666eab330aaab69e30d106aabe", size = 780459, upload-time = "2025-09-01T22:07:52.34Z" }, - { url = "https://files.pythonhosted.org/packages/f6/05/05884594a9975a29597917bbdd6837f7b97e8ac23faf22d628aa781e58f7/regex-2025.9.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e71bceb3947362ec5eabd2ca0870bb78eae4edfc60c6c21495133c01b6cd2df4", size = 849276, upload-time = "2025-09-01T22:07:54.591Z" }, - { url = "https://files.pythonhosted.org/packages/8c/8d/2b3067506838d02096bf107beb129b2ce328cdf776d6474b7f542c0a7bfd/regex-2025.9.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:67a74456f410fe5e869239ee7a5423510fe5121549af133809d9591a8075893f", size = 897320, upload-time = "2025-09-01T22:07:56.129Z" }, - { url = "https://files.pythonhosted.org/packages/9e/b3/0f9f7766e980b900df0ba9901b52871a2e4203698fb35cdebd219240d5f7/regex-2025.9.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c3b96ed0223b32dbdc53a83149b6de7ca3acd5acd9c8e64b42a166228abe29c", size = 789931, upload-time = "2025-09-01T22:07:57.834Z" }, - { url = "https://files.pythonhosted.org/packages/47/9f/7b2f29c8f8b698eb44be5fc68e8b9c8d32e99635eac5defc98de114e9f35/regex-2025.9.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:113d5aa950f428faf46fd77d452df62ebb4cc6531cb619f6cc30a369d326bfbd", size = 780764, upload-time = "2025-09-01T22:07:59.413Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ac/56176caa86155c14462531eb0a4ddc450d17ba8875001122b3b7c0cb01bf/regex-2025.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fcdeb38de4f7f3d69d798f4f371189061446792a84e7c92b50054c87aae9c07c", size = 773610, upload-time = "2025-09-01T22:08:01.042Z" }, - { url = "https://files.pythonhosted.org/packages/39/e8/9d6b9bd43998268a9de2f35602077519cacc9cb149f7381758cf8f502ba7/regex-2025.9.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4bcdff370509164b67a6c8ec23c9fb40797b72a014766fdc159bb809bd74f7d8", size = 844090, upload-time = "2025-09-01T22:08:02.94Z" }, - { url = "https://files.pythonhosted.org/packages/fd/92/d89743b089005cae4cb81cc2fe177e180b7452e60f29de53af34349640f8/regex-2025.9.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:7383efdf6e8e8c61d85e00cfb2e2e18da1a621b8bfb4b0f1c2747db57b942b8f", size = 834775, upload-time = "2025-09-01T22:08:04.781Z" }, - { url = "https://files.pythonhosted.org/packages/01/8f/86a3e0aaa89295d2a3445bb238e56369963ef6b02a5b4aa3362f4e687413/regex-2025.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1ec2bd3bdf0f73f7e9f48dca550ba7d973692d5e5e9a90ac42cc5f16c4432d8b", size = 778521, upload-time = "2025-09-01T22:08:06.596Z" }, - { url = "https://files.pythonhosted.org/packages/3e/df/72072acb370ee8577c255717f8a58264f1d0de40aa3c9e6ebd5271cac633/regex-2025.9.1-cp310-cp310-win32.whl", hash = "sha256:9627e887116c4e9c0986d5c3b4f52bcfe3df09850b704f62ec3cbf177a0ae374", size = 264105, upload-time = "2025-09-01T22:08:08.708Z" }, - { url = "https://files.pythonhosted.org/packages/97/73/fb82faaf0375aeaa1bb675008246c79b6779fa5688585a35327610ea0e2e/regex-2025.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:94533e32dc0065eca43912ee6649c90ea0681d59f56d43c45b5bcda9a740b3dd", size = 276131, upload-time = "2025-09-01T22:08:10.156Z" }, - { url = "https://files.pythonhosted.org/packages/d3/3a/77d7718a2493e54725494f44da1a1e55704743dc4b8fabe5b0596f7b8014/regex-2025.9.1-cp310-cp310-win_arm64.whl", hash = "sha256:a874a61bb580d48642ffd338570ee24ab13fa023779190513fcacad104a6e251", size = 268462, upload-time = "2025-09-01T22:08:11.651Z" }, - { url = "https://files.pythonhosted.org/packages/06/4d/f741543c0c59f96c6625bc6c11fea1da2e378b7d293ffff6f318edc0ce14/regex-2025.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e5bcf112b09bfd3646e4db6bf2e598534a17d502b0c01ea6550ba4eca780c5e6", size = 484811, upload-time = "2025-09-01T22:08:12.834Z" }, - { url = "https://files.pythonhosted.org/packages/c2/bd/27e73e92635b6fbd51afc26a414a3133243c662949cd1cda677fe7bb09bd/regex-2025.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:67a0295a3c31d675a9ee0238d20238ff10a9a2fdb7a1323c798fc7029578b15c", size = 288977, upload-time = "2025-09-01T22:08:14.499Z" }, - { url = "https://files.pythonhosted.org/packages/eb/7d/7dc0c6efc8bc93cd6e9b947581f5fde8a5dbaa0af7c4ec818c5729fdc807/regex-2025.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea8267fbadc7d4bd7c1301a50e85c2ff0de293ff9452a1a9f8d82c6cafe38179", size = 286606, upload-time = "2025-09-01T22:08:15.881Z" }, - { url = "https://files.pythonhosted.org/packages/d1/01/9b5c6dd394f97c8f2c12f6e8f96879c9ac27292a718903faf2e27a0c09f6/regex-2025.9.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aeff21de7214d15e928fb5ce757f9495214367ba62875100d4c18d293750cc1", size = 792436, upload-time = "2025-09-01T22:08:17.38Z" }, - { url = "https://files.pythonhosted.org/packages/fc/24/b7430cfc6ee34bbb3db6ff933beb5e7692e5cc81e8f6f4da63d353566fb0/regex-2025.9.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d89f1bbbbbc0885e1c230f7770d5e98f4f00b0ee85688c871d10df8b184a6323", size = 858705, upload-time = "2025-09-01T22:08:19.037Z" }, - { url = "https://files.pythonhosted.org/packages/d6/98/155f914b4ea6ae012663188545c4f5216c11926d09b817127639d618b003/regex-2025.9.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca3affe8ddea498ba9d294ab05f5f2d3b5ad5d515bc0d4a9016dd592a03afe52", size = 905881, upload-time = "2025-09-01T22:08:20.377Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a7/a470e7bc8259c40429afb6d6a517b40c03f2f3e455c44a01abc483a1c512/regex-2025.9.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91892a7a9f0a980e4c2c85dd19bc14de2b219a3a8867c4b5664b9f972dcc0c78", size = 798968, upload-time = "2025-09-01T22:08:22.081Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fa/33f6fec4d41449fea5f62fdf5e46d668a1c046730a7f4ed9f478331a8e3a/regex-2025.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e1cb40406f4ae862710615f9f636c1e030fd6e6abe0e0f65f6a695a2721440c6", size = 781884, upload-time = "2025-09-01T22:08:23.832Z" }, - { url = "https://files.pythonhosted.org/packages/42/de/2b45f36ab20da14eedddf5009d370625bc5942d9953fa7e5037a32d66843/regex-2025.9.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:94f6cff6f7e2149c7e6499a6ecd4695379eeda8ccbccb9726e8149f2fe382e92", size = 852935, upload-time = "2025-09-01T22:08:25.536Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f9/878f4fc92c87e125e27aed0f8ee0d1eced9b541f404b048f66f79914475a/regex-2025.9.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6c0226fb322b82709e78c49cc33484206647f8a39954d7e9de1567f5399becd0", size = 844340, upload-time = "2025-09-01T22:08:27.141Z" }, - { url = "https://files.pythonhosted.org/packages/90/c2/5b6f2bce6ece5f8427c718c085eca0de4bbb4db59f54db77aa6557aef3e9/regex-2025.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a12f59c7c380b4fcf7516e9cbb126f95b7a9518902bcf4a852423ff1dcd03e6a", size = 787238, upload-time = "2025-09-01T22:08:28.75Z" }, - { url = "https://files.pythonhosted.org/packages/47/66/1ef1081c831c5b611f6f55f6302166cfa1bc9574017410ba5595353f846a/regex-2025.9.1-cp311-cp311-win32.whl", hash = "sha256:49865e78d147a7a4f143064488da5d549be6bfc3f2579e5044cac61f5c92edd4", size = 264118, upload-time = "2025-09-01T22:08:30.388Z" }, - { url = "https://files.pythonhosted.org/packages/ad/e0/8adc550d7169df1d6b9be8ff6019cda5291054a0107760c2f30788b6195f/regex-2025.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:d34b901f6f2f02ef60f4ad3855d3a02378c65b094efc4b80388a3aeb700a5de7", size = 276151, upload-time = "2025-09-01T22:08:32.073Z" }, - { url = "https://files.pythonhosted.org/packages/cb/bd/46fef29341396d955066e55384fb93b0be7d64693842bf4a9a398db6e555/regex-2025.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:47d7c2dab7e0b95b95fd580087b6ae196039d62306a592fa4e162e49004b6299", size = 268460, upload-time = "2025-09-01T22:08:33.281Z" }, - { url = "https://files.pythonhosted.org/packages/39/ef/a0372febc5a1d44c1be75f35d7e5aff40c659ecde864d7fa10e138f75e74/regex-2025.9.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:84a25164bd8dcfa9f11c53f561ae9766e506e580b70279d05a7946510bdd6f6a", size = 486317, upload-time = "2025-09-01T22:08:34.529Z" }, - { url = "https://files.pythonhosted.org/packages/b5/25/d64543fb7eb41a1024786d518cc57faf1ce64aa6e9ddba097675a0c2f1d2/regex-2025.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:645e88a73861c64c1af558dd12294fb4e67b5c1eae0096a60d7d8a2143a611c7", size = 289698, upload-time = "2025-09-01T22:08:36.162Z" }, - { url = "https://files.pythonhosted.org/packages/d8/dc/fbf31fc60be317bd9f6f87daa40a8a9669b3b392aa8fe4313df0a39d0722/regex-2025.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10a450cba5cd5409526ee1d4449f42aad38dd83ac6948cbd6d7f71ca7018f7db", size = 287242, upload-time = "2025-09-01T22:08:37.794Z" }, - { url = "https://files.pythonhosted.org/packages/0f/74/f933a607a538f785da5021acf5323961b4620972e2c2f1f39b6af4b71db7/regex-2025.9.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9dc5991592933a4192c166eeb67b29d9234f9c86344481173d1bc52f73a7104", size = 797441, upload-time = "2025-09-01T22:08:39.108Z" }, - { url = "https://files.pythonhosted.org/packages/89/d0/71fc49b4f20e31e97f199348b8c4d6e613e7b6a54a90eb1b090c2b8496d7/regex-2025.9.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a32291add816961aab472f4fad344c92871a2ee33c6c219b6598e98c1f0108f2", size = 862654, upload-time = "2025-09-01T22:08:40.586Z" }, - { url = "https://files.pythonhosted.org/packages/59/05/984edce1411a5685ba9abbe10d42cdd9450aab4a022271f9585539788150/regex-2025.9.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:588c161a68a383478e27442a678e3b197b13c5ba51dbba40c1ccb8c4c7bee9e9", size = 910862, upload-time = "2025-09-01T22:08:42.416Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/5c891bb5fe0691cc1bad336e3a94b9097fbcf9707ec8ddc1dce9f0397289/regex-2025.9.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47829ffaf652f30d579534da9085fe30c171fa2a6744a93d52ef7195dc38218b", size = 801991, upload-time = "2025-09-01T22:08:44.072Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ae/fd10d6ad179910f7a1b3e0a7fde1ef8bb65e738e8ac4fd6ecff3f52252e4/regex-2025.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e978e5a35b293ea43f140c92a3269b6ab13fe0a2bf8a881f7ac740f5a6ade85", size = 786651, upload-time = "2025-09-01T22:08:46.079Z" }, - { url = "https://files.pythonhosted.org/packages/30/cf/9d686b07bbc5bf94c879cc168db92542d6bc9fb67088d03479fef09ba9d3/regex-2025.9.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4cf09903e72411f4bf3ac1eddd624ecfd423f14b2e4bf1c8b547b72f248b7bf7", size = 856556, upload-time = "2025-09-01T22:08:48.376Z" }, - { url = "https://files.pythonhosted.org/packages/91/9d/302f8a29bb8a49528abbab2d357a793e2a59b645c54deae0050f8474785b/regex-2025.9.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d016b0f77be63e49613c9e26aaf4a242f196cd3d7a4f15898f5f0ab55c9b24d2", size = 849001, upload-time = "2025-09-01T22:08:50.067Z" }, - { url = "https://files.pythonhosted.org/packages/93/fa/b4c6dbdedc85ef4caec54c817cd5f4418dbfa2453214119f2538082bf666/regex-2025.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:656563e620de6908cd1c9d4f7b9e0777e3341ca7db9d4383bcaa44709c90281e", size = 788138, upload-time = "2025-09-01T22:08:51.933Z" }, - { url = "https://files.pythonhosted.org/packages/4a/1b/91ee17a3cbf87f81e8c110399279d0e57f33405468f6e70809100f2ff7d8/regex-2025.9.1-cp312-cp312-win32.whl", hash = "sha256:df33f4ef07b68f7ab637b1dbd70accbf42ef0021c201660656601e8a9835de45", size = 264524, upload-time = "2025-09-01T22:08:53.75Z" }, - { url = "https://files.pythonhosted.org/packages/92/28/6ba31cce05b0f1ec6b787921903f83bd0acf8efde55219435572af83c350/regex-2025.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:5aba22dfbc60cda7c0853516104724dc904caa2db55f2c3e6e984eb858d3edf3", size = 275489, upload-time = "2025-09-01T22:08:55.037Z" }, - { url = "https://files.pythonhosted.org/packages/bd/ed/ea49f324db00196e9ef7fe00dd13c6164d5173dd0f1bbe495e61bb1fb09d/regex-2025.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:ec1efb4c25e1849c2685fa95da44bfde1b28c62d356f9c8d861d4dad89ed56e9", size = 268589, upload-time = "2025-09-01T22:08:56.369Z" }, - { url = "https://files.pythonhosted.org/packages/98/25/b2959ce90c6138c5142fe5264ee1f9b71a0c502ca4c7959302a749407c79/regex-2025.9.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bc6834727d1b98d710a63e6c823edf6ffbf5792eba35d3fa119531349d4142ef", size = 485932, upload-time = "2025-09-01T22:08:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/49/2e/6507a2a85f3f2be6643438b7bd976e67ad73223692d6988eb1ff444106d3/regex-2025.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c3dc05b6d579875719bccc5f3037b4dc80433d64e94681a0061845bd8863c025", size = 289568, upload-time = "2025-09-01T22:08:59.258Z" }, - { url = "https://files.pythonhosted.org/packages/c7/d8/de4a4b57215d99868f1640e062a7907e185ec7476b4b689e2345487c1ff4/regex-2025.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22213527df4c985ec4a729b055a8306272d41d2f45908d7bacb79be0fa7a75ad", size = 286984, upload-time = "2025-09-01T22:09:00.835Z" }, - { url = "https://files.pythonhosted.org/packages/03/15/e8cb403403a57ed316e80661db0e54d7aa2efcd85cb6156f33cc18746922/regex-2025.9.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e3f6e3c5a5a1adc3f7ea1b5aec89abfc2f4fbfba55dafb4343cd1d084f715b2", size = 797514, upload-time = "2025-09-01T22:09:02.538Z" }, - { url = "https://files.pythonhosted.org/packages/e4/26/2446f2b9585fed61faaa7e2bbce3aca7dd8df6554c32addee4c4caecf24a/regex-2025.9.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcb89c02a0d6c2bec9b0bb2d8c78782699afe8434493bfa6b4021cc51503f249", size = 862586, upload-time = "2025-09-01T22:09:04.322Z" }, - { url = "https://files.pythonhosted.org/packages/fd/b8/82ffbe9c0992c31bbe6ae1c4b4e21269a5df2559102b90543c9b56724c3c/regex-2025.9.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b0e2f95413eb0c651cd1516a670036315b91b71767af83bc8525350d4375ccba", size = 910815, upload-time = "2025-09-01T22:09:05.978Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d8/7303ea38911759c1ee30cc5bc623ee85d3196b733c51fd6703c34290a8d9/regex-2025.9.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a41dc039e1c97d3c2ed3e26523f748e58c4de3ea7a31f95e1cf9ff973fff5a", size = 802042, upload-time = "2025-09-01T22:09:07.865Z" }, - { url = "https://files.pythonhosted.org/packages/fc/0e/6ad51a55ed4b5af512bb3299a05d33309bda1c1d1e1808fa869a0bed31bc/regex-2025.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f0b4258b161094f66857a26ee938d3fe7b8a5063861e44571215c44fbf0e5df", size = 786764, upload-time = "2025-09-01T22:09:09.362Z" }, - { url = "https://files.pythonhosted.org/packages/8d/d5/394e3ffae6baa5a9217bbd14d96e0e5da47bb069d0dbb8278e2681a2b938/regex-2025.9.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bf70e18ac390e6977ea7e56f921768002cb0fa359c4199606c7219854ae332e0", size = 856557, upload-time = "2025-09-01T22:09:11.129Z" }, - { url = "https://files.pythonhosted.org/packages/cd/80/b288d3910c41194ad081b9fb4b371b76b0bbfdce93e7709fc98df27b37dc/regex-2025.9.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b84036511e1d2bb0a4ff1aec26951caa2dea8772b223c9e8a19ed8885b32dbac", size = 849108, upload-time = "2025-09-01T22:09:12.877Z" }, - { url = "https://files.pythonhosted.org/packages/d1/cd/5ec76bf626d0d5abdc277b7a1734696f5f3d14fbb4a3e2540665bc305d85/regex-2025.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c2e05dcdfe224047f2a59e70408274c325d019aad96227ab959403ba7d58d2d7", size = 788201, upload-time = "2025-09-01T22:09:14.561Z" }, - { url = "https://files.pythonhosted.org/packages/b5/36/674672f3fdead107565a2499f3007788b878188acec6d42bc141c5366c2c/regex-2025.9.1-cp313-cp313-win32.whl", hash = "sha256:3b9a62107a7441b81ca98261808fed30ae36ba06c8b7ee435308806bd53c1ed8", size = 264508, upload-time = "2025-09-01T22:09:16.193Z" }, - { url = "https://files.pythonhosted.org/packages/83/ad/931134539515eb64ce36c24457a98b83c1b2e2d45adf3254b94df3735a76/regex-2025.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:b38afecc10c177eb34cfae68d669d5161880849ba70c05cbfbe409f08cc939d7", size = 275469, upload-time = "2025-09-01T22:09:17.462Z" }, - { url = "https://files.pythonhosted.org/packages/24/8c/96d34e61c0e4e9248836bf86d69cb224fd222f270fa9045b24e218b65604/regex-2025.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:ec329890ad5e7ed9fc292858554d28d58d56bf62cf964faf0aa57964b21155a0", size = 268586, upload-time = "2025-09-01T22:09:18.948Z" }, - { url = "https://files.pythonhosted.org/packages/21/b1/453cbea5323b049181ec6344a803777914074b9726c9c5dc76749966d12d/regex-2025.9.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:72fb7a016467d364546f22b5ae86c45680a4e0de6b2a6f67441d22172ff641f1", size = 486111, upload-time = "2025-09-01T22:09:20.734Z" }, - { url = "https://files.pythonhosted.org/packages/f6/0e/92577f197bd2f7652c5e2857f399936c1876978474ecc5b068c6d8a79c86/regex-2025.9.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c9527fa74eba53f98ad86be2ba003b3ebe97e94b6eb2b916b31b5f055622ef03", size = 289520, upload-time = "2025-09-01T22:09:22.249Z" }, - { url = "https://files.pythonhosted.org/packages/af/c6/b472398116cca7ea5a6c4d5ccd0fc543f7fd2492cb0c48d2852a11972f73/regex-2025.9.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c905d925d194c83a63f92422af7544ec188301451b292c8b487f0543726107ca", size = 287215, upload-time = "2025-09-01T22:09:23.657Z" }, - { url = "https://files.pythonhosted.org/packages/cf/11/f12ecb0cf9ca792a32bb92f758589a84149017467a544f2f6bfb45c0356d/regex-2025.9.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74df7c74a63adcad314426b1f4ea6054a5ab25d05b0244f0c07ff9ce640fa597", size = 797855, upload-time = "2025-09-01T22:09:25.197Z" }, - { url = "https://files.pythonhosted.org/packages/46/88/bbb848f719a540fb5997e71310f16f0b33a92c5d4b4d72d4311487fff2a3/regex-2025.9.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4f6e935e98ea48c7a2e8be44494de337b57a204470e7f9c9c42f912c414cd6f5", size = 863363, upload-time = "2025-09-01T22:09:26.705Z" }, - { url = "https://files.pythonhosted.org/packages/54/a9/2321eb3e2838f575a78d48e03c1e83ea61bd08b74b7ebbdeca8abc50fc25/regex-2025.9.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4a62d033cd9ebefc7c5e466731a508dfabee827d80b13f455de68a50d3c2543d", size = 910202, upload-time = "2025-09-01T22:09:28.906Z" }, - { url = "https://files.pythonhosted.org/packages/33/07/d1d70835d7d11b7e126181f316f7213c4572ecf5c5c97bdbb969fb1f38a2/regex-2025.9.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef971ebf2b93bdc88d8337238be4dfb851cc97ed6808eb04870ef67589415171", size = 801808, upload-time = "2025-09-01T22:09:30.733Z" }, - { url = "https://files.pythonhosted.org/packages/13/d1/29e4d1bed514ef2bf3a4ead3cb8bb88ca8af94130239a4e68aa765c35b1c/regex-2025.9.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d936a1db208bdca0eca1f2bb2c1ba1d8370b226785c1e6db76e32a228ffd0ad5", size = 786824, upload-time = "2025-09-01T22:09:32.61Z" }, - { url = "https://files.pythonhosted.org/packages/33/27/20d8ccb1bee460faaa851e6e7cc4cfe852a42b70caa1dca22721ba19f02f/regex-2025.9.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:7e786d9e4469698fc63815b8de08a89165a0aa851720eb99f5e0ea9d51dd2b6a", size = 857406, upload-time = "2025-09-01T22:09:34.117Z" }, - { url = "https://files.pythonhosted.org/packages/74/fe/60c6132262dc36430d51e0c46c49927d113d3a38c1aba6a26c7744c84cf3/regex-2025.9.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6b81d7dbc5466ad2c57ce3a0ddb717858fe1a29535c8866f8514d785fdb9fc5b", size = 848593, upload-time = "2025-09-01T22:09:35.598Z" }, - { url = "https://files.pythonhosted.org/packages/cc/ae/2d4ff915622fabbef1af28387bf71e7f2f4944a348b8460d061e85e29bf0/regex-2025.9.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cd4890e184a6feb0ef195338a6ce68906a8903a0f2eb7e0ab727dbc0a3156273", size = 787951, upload-time = "2025-09-01T22:09:37.139Z" }, - { url = "https://files.pythonhosted.org/packages/85/37/dc127703a9e715a284cc2f7dbdd8a9776fd813c85c126eddbcbdd1ca5fec/regex-2025.9.1-cp314-cp314-win32.whl", hash = "sha256:34679a86230e46164c9e0396b56cab13c0505972343880b9e705083cc5b8ec86", size = 269833, upload-time = "2025-09-01T22:09:39.245Z" }, - { url = "https://files.pythonhosted.org/packages/83/bf/4bed4d3d0570e16771defd5f8f15f7ea2311edcbe91077436d6908956c4a/regex-2025.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:a1196e530a6bfa5f4bde029ac5b0295a6ecfaaffbfffede4bbaf4061d9455b70", size = 278742, upload-time = "2025-09-01T22:09:40.651Z" }, - { url = "https://files.pythonhosted.org/packages/cf/3e/7d7ac6fd085023312421e0d69dfabdfb28e116e513fadbe9afe710c01893/regex-2025.9.1-cp314-cp314-win_arm64.whl", hash = "sha256:f46d525934871ea772930e997d577d48c6983e50f206ff7b66d4ac5f8941e993", size = 271860, upload-time = "2025-09-01T22:09:42.413Z" }, -] - [[package]] name = "requests" version = "2.32.5" @@ -1870,141 +1190,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, ] -[[package]] -name = "rpds-py" -version = "0.27.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/ed/3aef893e2dd30e77e35d20d4ddb45ca459db59cead748cad9796ad479411/rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef", size = 371606, upload-time = "2025-08-27T12:12:25.189Z" }, - { url = "https://files.pythonhosted.org/packages/6d/82/9818b443e5d3eb4c83c3994561387f116aae9833b35c484474769c4a8faf/rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be", size = 353452, upload-time = "2025-08-27T12:12:27.433Z" }, - { url = "https://files.pythonhosted.org/packages/99/c7/d2a110ffaaa397fc6793a83c7bd3545d9ab22658b7cdff05a24a4535cc45/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61", size = 381519, upload-time = "2025-08-27T12:12:28.719Z" }, - { url = "https://files.pythonhosted.org/packages/5a/bc/e89581d1f9d1be7d0247eaef602566869fdc0d084008ba139e27e775366c/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb", size = 394424, upload-time = "2025-08-27T12:12:30.207Z" }, - { url = "https://files.pythonhosted.org/packages/ac/2e/36a6861f797530e74bb6ed53495f8741f1ef95939eed01d761e73d559067/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657", size = 523467, upload-time = "2025-08-27T12:12:31.808Z" }, - { url = "https://files.pythonhosted.org/packages/c4/59/c1bc2be32564fa499f988f0a5c6505c2f4746ef96e58e4d7de5cf923d77e/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013", size = 402660, upload-time = "2025-08-27T12:12:33.444Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ec/ef8bf895f0628dd0a59e54d81caed6891663cb9c54a0f4bb7da918cb88cf/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a", size = 384062, upload-time = "2025-08-27T12:12:34.857Z" }, - { url = "https://files.pythonhosted.org/packages/69/f7/f47ff154be8d9a5e691c083a920bba89cef88d5247c241c10b9898f595a1/rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1", size = 401289, upload-time = "2025-08-27T12:12:36.085Z" }, - { url = "https://files.pythonhosted.org/packages/3b/d9/ca410363efd0615814ae579f6829cafb39225cd63e5ea5ed1404cb345293/rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10", size = 417718, upload-time = "2025-08-27T12:12:37.401Z" }, - { url = "https://files.pythonhosted.org/packages/e3/a0/8cb5c2ff38340f221cc067cc093d1270e10658ba4e8d263df923daa18e86/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808", size = 558333, upload-time = "2025-08-27T12:12:38.672Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8c/1b0de79177c5d5103843774ce12b84caa7164dfc6cd66378768d37db11bf/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8", size = 589127, upload-time = "2025-08-27T12:12:41.48Z" }, - { url = "https://files.pythonhosted.org/packages/c8/5e/26abb098d5e01266b0f3a2488d299d19ccc26849735d9d2b95c39397e945/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9", size = 554899, upload-time = "2025-08-27T12:12:42.925Z" }, - { url = "https://files.pythonhosted.org/packages/de/41/905cc90ced13550db017f8f20c6d8e8470066c5738ba480d7ba63e3d136b/rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4", size = 217450, upload-time = "2025-08-27T12:12:44.813Z" }, - { url = "https://files.pythonhosted.org/packages/75/3d/6bef47b0e253616ccdf67c283e25f2d16e18ccddd38f92af81d5a3420206/rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1", size = 228447, upload-time = "2025-08-27T12:12:46.204Z" }, - { url = "https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881", size = 371063, upload-time = "2025-08-27T12:12:47.856Z" }, - { url = "https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5", size = 353210, upload-time = "2025-08-27T12:12:49.187Z" }, - { url = "https://files.pythonhosted.org/packages/3a/57/f5eb3ecf434342f4f1a46009530e93fd201a0b5b83379034ebdb1d7c1a58/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e", size = 381636, upload-time = "2025-08-27T12:12:50.492Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f4/ef95c5945e2ceb5119571b184dd5a1cc4b8541bbdf67461998cfeac9cb1e/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c", size = 394341, upload-time = "2025-08-27T12:12:52.024Z" }, - { url = "https://files.pythonhosted.org/packages/5a/7e/4bd610754bf492d398b61725eb9598ddd5eb86b07d7d9483dbcd810e20bc/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195", size = 523428, upload-time = "2025-08-27T12:12:53.779Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e5/059b9f65a8c9149361a8b75094864ab83b94718344db511fd6117936ed2a/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52", size = 402923, upload-time = "2025-08-27T12:12:55.15Z" }, - { url = "https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed", size = 384094, upload-time = "2025-08-27T12:12:57.194Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e1/dc9094d6ff566bff87add8a510c89b9e158ad2ecd97ee26e677da29a9e1b/rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a", size = 401093, upload-time = "2025-08-27T12:12:58.985Z" }, - { url = "https://files.pythonhosted.org/packages/37/8e/ac8577e3ecdd5593e283d46907d7011618994e1d7ab992711ae0f78b9937/rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde", size = 417969, upload-time = "2025-08-27T12:13:00.367Z" }, - { url = "https://files.pythonhosted.org/packages/66/6d/87507430a8f74a93556fe55c6485ba9c259949a853ce407b1e23fea5ba31/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21", size = 558302, upload-time = "2025-08-27T12:13:01.737Z" }, - { url = "https://files.pythonhosted.org/packages/3a/bb/1db4781ce1dda3eecc735e3152659a27b90a02ca62bfeea17aee45cc0fbc/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9", size = 589259, upload-time = "2025-08-27T12:13:03.127Z" }, - { url = "https://files.pythonhosted.org/packages/7b/0e/ae1c8943d11a814d01b482e1f8da903f88047a962dff9bbdadf3bd6e6fd1/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948", size = 554983, upload-time = "2025-08-27T12:13:04.516Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d5/0b2a55415931db4f112bdab072443ff76131b5ac4f4dc98d10d2d357eb03/rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39", size = 217154, upload-time = "2025-08-27T12:13:06.278Z" }, - { url = "https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15", size = 228627, upload-time = "2025-08-27T12:13:07.625Z" }, - { url = "https://files.pythonhosted.org/packages/8d/3f/4fd04c32abc02c710f09a72a30c9a55ea3cc154ef8099078fd50a0596f8e/rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746", size = 220998, upload-time = "2025-08-27T12:13:08.972Z" }, - { url = "https://files.pythonhosted.org/packages/bd/fe/38de28dee5df58b8198c743fe2bea0c785c6d40941b9950bac4cdb71a014/rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", size = 361887, upload-time = "2025-08-27T12:13:10.233Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/4b6c7eedc7dd90986bf0fab6ea2a091ec11c01b15f8ba0a14d3f80450468/rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", size = 345795, upload-time = "2025-08-27T12:13:11.65Z" }, - { url = "https://files.pythonhosted.org/packages/6f/0e/e650e1b81922847a09cca820237b0edee69416a01268b7754d506ade11ad/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", size = 385121, upload-time = "2025-08-27T12:13:13.008Z" }, - { url = "https://files.pythonhosted.org/packages/1b/ea/b306067a712988e2bff00dcc7c8f31d26c29b6d5931b461aa4b60a013e33/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", size = 398976, upload-time = "2025-08-27T12:13:14.368Z" }, - { url = "https://files.pythonhosted.org/packages/2c/0a/26dc43c8840cb8fe239fe12dbc8d8de40f2365e838f3d395835dde72f0e5/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", size = 525953, upload-time = "2025-08-27T12:13:15.774Z" }, - { url = "https://files.pythonhosted.org/packages/22/14/c85e8127b573aaf3a0cbd7fbb8c9c99e735a4a02180c84da2a463b766e9e/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", size = 407915, upload-time = "2025-08-27T12:13:17.379Z" }, - { url = "https://files.pythonhosted.org/packages/ed/7b/8f4fee9ba1fb5ec856eb22d725a4efa3deb47f769597c809e03578b0f9d9/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", size = 386883, upload-time = "2025-08-27T12:13:18.704Z" }, - { url = "https://files.pythonhosted.org/packages/86/47/28fa6d60f8b74fcdceba81b272f8d9836ac0340570f68f5df6b41838547b/rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", size = 405699, upload-time = "2025-08-27T12:13:20.089Z" }, - { url = "https://files.pythonhosted.org/packages/d0/fd/c5987b5e054548df56953a21fe2ebed51fc1ec7c8f24fd41c067b68c4a0a/rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", size = 423713, upload-time = "2025-08-27T12:13:21.436Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ba/3c4978b54a73ed19a7d74531be37a8bcc542d917c770e14d372b8daea186/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", size = 562324, upload-time = "2025-08-27T12:13:22.789Z" }, - { url = "https://files.pythonhosted.org/packages/b5/6c/6943a91768fec16db09a42b08644b960cff540c66aab89b74be6d4a144ba/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", size = 593646, upload-time = "2025-08-27T12:13:24.122Z" }, - { url = "https://files.pythonhosted.org/packages/11/73/9d7a8f4be5f4396f011a6bb7a19fe26303a0dac9064462f5651ced2f572f/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", size = 558137, upload-time = "2025-08-27T12:13:25.557Z" }, - { url = "https://files.pythonhosted.org/packages/6e/96/6772cbfa0e2485bcceef8071de7821f81aeac8bb45fbfd5542a3e8108165/rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", size = 221343, upload-time = "2025-08-27T12:13:26.967Z" }, - { url = "https://files.pythonhosted.org/packages/67/b6/c82f0faa9af1c6a64669f73a17ee0eeef25aff30bb9a1c318509efe45d84/rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", size = 232497, upload-time = "2025-08-27T12:13:28.326Z" }, - { url = "https://files.pythonhosted.org/packages/e1/96/2817b44bd2ed11aebacc9251da03689d56109b9aba5e311297b6902136e2/rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", size = 222790, upload-time = "2025-08-27T12:13:29.71Z" }, - { url = "https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", size = 361741, upload-time = "2025-08-27T12:13:31.039Z" }, - { url = "https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", size = 345574, upload-time = "2025-08-27T12:13:32.902Z" }, - { url = "https://files.pythonhosted.org/packages/20/42/ee2b2ca114294cd9847d0ef9c26d2b0851b2e7e00bf14cc4c0b581df0fc3/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", size = 385051, upload-time = "2025-08-27T12:13:34.228Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e8/1e430fe311e4799e02e2d1af7c765f024e95e17d651612425b226705f910/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", size = 398395, upload-time = "2025-08-27T12:13:36.132Z" }, - { url = "https://files.pythonhosted.org/packages/82/95/9dc227d441ff2670651c27a739acb2535ccaf8b351a88d78c088965e5996/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", size = 524334, upload-time = "2025-08-27T12:13:37.562Z" }, - { url = "https://files.pythonhosted.org/packages/87/01/a670c232f401d9ad461d9a332aa4080cd3cb1d1df18213dbd0d2a6a7ab51/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", size = 407691, upload-time = "2025-08-27T12:13:38.94Z" }, - { url = "https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", size = 386868, upload-time = "2025-08-27T12:13:40.192Z" }, - { url = "https://files.pythonhosted.org/packages/3b/03/8c897fb8b5347ff6c1cc31239b9611c5bf79d78c984430887a353e1409a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", size = 405469, upload-time = "2025-08-27T12:13:41.496Z" }, - { url = "https://files.pythonhosted.org/packages/da/07/88c60edc2df74850d496d78a1fdcdc7b54360a7f610a4d50008309d41b94/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", size = 422125, upload-time = "2025-08-27T12:13:42.802Z" }, - { url = "https://files.pythonhosted.org/packages/6b/86/5f4c707603e41b05f191a749984f390dabcbc467cf833769b47bf14ba04f/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", size = 562341, upload-time = "2025-08-27T12:13:44.472Z" }, - { url = "https://files.pythonhosted.org/packages/b2/92/3c0cb2492094e3cd9baf9e49bbb7befeceb584ea0c1a8b5939dca4da12e5/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", size = 592511, upload-time = "2025-08-27T12:13:45.898Z" }, - { url = "https://files.pythonhosted.org/packages/10/bb/82e64fbb0047c46a168faa28d0d45a7851cd0582f850b966811d30f67ad8/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", size = 557736, upload-time = "2025-08-27T12:13:47.408Z" }, - { url = "https://files.pythonhosted.org/packages/00/95/3c863973d409210da7fb41958172c6b7dbe7fc34e04d3cc1f10bb85e979f/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", size = 221462, upload-time = "2025-08-27T12:13:48.742Z" }, - { url = "https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", size = 232034, upload-time = "2025-08-27T12:13:50.11Z" }, - { url = "https://files.pythonhosted.org/packages/c7/78/3958f3f018c01923823f1e47f1cc338e398814b92d83cd278364446fac66/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", size = 222392, upload-time = "2025-08-27T12:13:52.587Z" }, - { url = "https://files.pythonhosted.org/packages/01/76/1cdf1f91aed5c3a7bf2eba1f1c4e4d6f57832d73003919a20118870ea659/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", size = 358355, upload-time = "2025-08-27T12:13:54.012Z" }, - { url = "https://files.pythonhosted.org/packages/c3/6f/bf142541229374287604caf3bb2a4ae17f0a580798fd72d3b009b532db4e/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", size = 342138, upload-time = "2025-08-27T12:13:55.791Z" }, - { url = "https://files.pythonhosted.org/packages/1a/77/355b1c041d6be40886c44ff5e798b4e2769e497b790f0f7fd1e78d17e9a8/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", size = 380247, upload-time = "2025-08-27T12:13:57.683Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a4/d9cef5c3946ea271ce2243c51481971cd6e34f21925af2783dd17b26e815/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", size = 390699, upload-time = "2025-08-27T12:13:59.137Z" }, - { url = "https://files.pythonhosted.org/packages/3a/06/005106a7b8c6c1a7e91b73169e49870f4af5256119d34a361ae5240a0c1d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", size = 521852, upload-time = "2025-08-27T12:14:00.583Z" }, - { url = "https://files.pythonhosted.org/packages/e5/3e/50fb1dac0948e17a02eb05c24510a8fe12d5ce8561c6b7b7d1339ab7ab9c/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", size = 402582, upload-time = "2025-08-27T12:14:02.034Z" }, - { url = "https://files.pythonhosted.org/packages/cb/b0/f4e224090dc5b0ec15f31a02d746ab24101dd430847c4d99123798661bfc/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", size = 384126, upload-time = "2025-08-27T12:14:03.437Z" }, - { url = "https://files.pythonhosted.org/packages/54/77/ac339d5f82b6afff1df8f0fe0d2145cc827992cb5f8eeb90fc9f31ef7a63/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", size = 399486, upload-time = "2025-08-27T12:14:05.443Z" }, - { url = "https://files.pythonhosted.org/packages/d6/29/3e1c255eee6ac358c056a57d6d6869baa00a62fa32eea5ee0632039c50a3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", size = 414832, upload-time = "2025-08-27T12:14:06.902Z" }, - { url = "https://files.pythonhosted.org/packages/3f/db/6d498b844342deb3fa1d030598db93937a9964fcf5cb4da4feb5f17be34b/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", size = 557249, upload-time = "2025-08-27T12:14:08.37Z" }, - { url = "https://files.pythonhosted.org/packages/60/f3/690dd38e2310b6f68858a331399b4d6dbb9132c3e8ef8b4333b96caf403d/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", size = 587356, upload-time = "2025-08-27T12:14:10.034Z" }, - { url = "https://files.pythonhosted.org/packages/86/e3/84507781cccd0145f35b1dc32c72675200c5ce8d5b30f813e49424ef68fc/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", size = 555300, upload-time = "2025-08-27T12:14:11.783Z" }, - { url = "https://files.pythonhosted.org/packages/e5/ee/375469849e6b429b3516206b4580a79e9ef3eb12920ddbd4492b56eaacbe/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", size = 216714, upload-time = "2025-08-27T12:14:13.629Z" }, - { url = "https://files.pythonhosted.org/packages/21/87/3fc94e47c9bd0742660e84706c311a860dcae4374cf4a03c477e23ce605a/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", size = 228943, upload-time = "2025-08-27T12:14:14.937Z" }, - { url = "https://files.pythonhosted.org/packages/70/36/b6e6066520a07cf029d385de869729a895917b411e777ab1cde878100a1d/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", size = 362472, upload-time = "2025-08-27T12:14:16.333Z" }, - { url = "https://files.pythonhosted.org/packages/af/07/b4646032e0dcec0df9c73a3bd52f63bc6c5f9cda992f06bd0e73fe3fbebd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", size = 345676, upload-time = "2025-08-27T12:14:17.764Z" }, - { url = "https://files.pythonhosted.org/packages/b0/16/2f1003ee5d0af4bcb13c0cf894957984c32a6751ed7206db2aee7379a55e/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", size = 385313, upload-time = "2025-08-27T12:14:19.829Z" }, - { url = "https://files.pythonhosted.org/packages/05/cd/7eb6dd7b232e7f2654d03fa07f1414d7dfc980e82ba71e40a7c46fd95484/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", size = 399080, upload-time = "2025-08-27T12:14:21.531Z" }, - { url = "https://files.pythonhosted.org/packages/20/51/5829afd5000ec1cb60f304711f02572d619040aa3ec033d8226817d1e571/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", size = 523868, upload-time = "2025-08-27T12:14:23.485Z" }, - { url = "https://files.pythonhosted.org/packages/05/2c/30eebca20d5db95720ab4d2faec1b5e4c1025c473f703738c371241476a2/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", size = 408750, upload-time = "2025-08-27T12:14:24.924Z" }, - { url = "https://files.pythonhosted.org/packages/90/1a/cdb5083f043597c4d4276eae4e4c70c55ab5accec078da8611f24575a367/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", size = 387688, upload-time = "2025-08-27T12:14:27.537Z" }, - { url = "https://files.pythonhosted.org/packages/7c/92/cf786a15320e173f945d205ab31585cc43969743bb1a48b6888f7a2b0a2d/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", size = 407225, upload-time = "2025-08-27T12:14:28.981Z" }, - { url = "https://files.pythonhosted.org/packages/33/5c/85ee16df5b65063ef26017bef33096557a4c83fbe56218ac7cd8c235f16d/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", size = 423361, upload-time = "2025-08-27T12:14:30.469Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8e/1c2741307fcabd1a334ecf008e92c4f47bb6f848712cf15c923becfe82bb/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", size = 562493, upload-time = "2025-08-27T12:14:31.987Z" }, - { url = "https://files.pythonhosted.org/packages/04/03/5159321baae9b2222442a70c1f988cbbd66b9be0675dd3936461269be360/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", size = 592623, upload-time = "2025-08-27T12:14:33.543Z" }, - { url = "https://files.pythonhosted.org/packages/ff/39/c09fd1ad28b85bc1d4554a8710233c9f4cefd03d7717a1b8fbfd171d1167/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", size = 558800, upload-time = "2025-08-27T12:14:35.436Z" }, - { url = "https://files.pythonhosted.org/packages/c5/d6/99228e6bbcf4baa764b18258f519a9035131d91b538d4e0e294313462a98/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", size = 221943, upload-time = "2025-08-27T12:14:36.898Z" }, - { url = "https://files.pythonhosted.org/packages/be/07/c802bc6b8e95be83b79bdf23d1aa61d68324cb1006e245d6c58e959e314d/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", size = 233739, upload-time = "2025-08-27T12:14:38.386Z" }, - { url = "https://files.pythonhosted.org/packages/c8/89/3e1b1c16d4c2d547c5717377a8df99aee8099ff050f87c45cb4d5fa70891/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", size = 223120, upload-time = "2025-08-27T12:14:39.82Z" }, - { url = "https://files.pythonhosted.org/packages/62/7e/dc7931dc2fa4a6e46b2a4fa744a9fe5c548efd70e0ba74f40b39fa4a8c10/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", size = 358944, upload-time = "2025-08-27T12:14:41.199Z" }, - { url = "https://files.pythonhosted.org/packages/e6/22/4af76ac4e9f336bfb1a5f240d18a33c6b2fcaadb7472ac7680576512b49a/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", size = 342283, upload-time = "2025-08-27T12:14:42.699Z" }, - { url = "https://files.pythonhosted.org/packages/1c/15/2a7c619b3c2272ea9feb9ade67a45c40b3eeb500d503ad4c28c395dc51b4/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", size = 380320, upload-time = "2025-08-27T12:14:44.157Z" }, - { url = "https://files.pythonhosted.org/packages/a2/7d/4c6d243ba4a3057e994bb5bedd01b5c963c12fe38dde707a52acdb3849e7/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", size = 391760, upload-time = "2025-08-27T12:14:45.845Z" }, - { url = "https://files.pythonhosted.org/packages/b4/71/b19401a909b83bcd67f90221330bc1ef11bc486fe4e04c24388d28a618ae/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", size = 522476, upload-time = "2025-08-27T12:14:47.364Z" }, - { url = "https://files.pythonhosted.org/packages/e4/44/1a3b9715c0455d2e2f0f6df5ee6d6f5afdc423d0773a8a682ed2b43c566c/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", size = 403418, upload-time = "2025-08-27T12:14:49.991Z" }, - { url = "https://files.pythonhosted.org/packages/1c/4b/fb6c4f14984eb56673bc868a66536f53417ddb13ed44b391998100a06a96/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", size = 384771, upload-time = "2025-08-27T12:14:52.159Z" }, - { url = "https://files.pythonhosted.org/packages/c0/56/d5265d2d28b7420d7b4d4d85cad8ef891760f5135102e60d5c970b976e41/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", size = 400022, upload-time = "2025-08-27T12:14:53.859Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e9/9f5fc70164a569bdd6ed9046486c3568d6926e3a49bdefeeccfb18655875/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", size = 416787, upload-time = "2025-08-27T12:14:55.673Z" }, - { url = "https://files.pythonhosted.org/packages/d4/64/56dd03430ba491db943a81dcdef115a985aac5f44f565cd39a00c766d45c/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", size = 557538, upload-time = "2025-08-27T12:14:57.245Z" }, - { url = "https://files.pythonhosted.org/packages/3f/36/92cc885a3129993b1d963a2a42ecf64e6a8e129d2c7cc980dbeba84e55fb/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", size = 588512, upload-time = "2025-08-27T12:14:58.728Z" }, - { url = "https://files.pythonhosted.org/packages/dd/10/6b283707780a81919f71625351182b4f98932ac89a09023cb61865136244/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", size = 555813, upload-time = "2025-08-27T12:15:00.334Z" }, - { url = "https://files.pythonhosted.org/packages/04/2e/30b5ea18c01379da6272a92825dd7e53dc9d15c88a19e97932d35d430ef7/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", size = 217385, upload-time = "2025-08-27T12:15:01.937Z" }, - { url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" }, - { url = "https://files.pythonhosted.org/packages/d5/63/b7cc415c345625d5e62f694ea356c58fb964861409008118f1245f8c3347/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf", size = 371360, upload-time = "2025-08-27T12:15:29.218Z" }, - { url = "https://files.pythonhosted.org/packages/e5/8c/12e1b24b560cf378b8ffbdb9dc73abd529e1adcfcf82727dfd29c4a7b88d/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3", size = 353933, upload-time = "2025-08-27T12:15:30.837Z" }, - { url = "https://files.pythonhosted.org/packages/9b/85/1bb2210c1f7a1b99e91fea486b9f0f894aa5da3a5ec7097cbad7dec6d40f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636", size = 382962, upload-time = "2025-08-27T12:15:32.348Z" }, - { url = "https://files.pythonhosted.org/packages/cc/c9/a839b9f219cf80ed65f27a7f5ddbb2809c1b85c966020ae2dff490e0b18e/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8", size = 394412, upload-time = "2025-08-27T12:15:33.839Z" }, - { url = "https://files.pythonhosted.org/packages/02/2d/b1d7f928b0b1f4fc2e0133e8051d199b01d7384875adc63b6ddadf3de7e5/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc", size = 523972, upload-time = "2025-08-27T12:15:35.377Z" }, - { url = "https://files.pythonhosted.org/packages/a9/af/2cbf56edd2d07716df1aec8a726b3159deb47cb5c27e1e42b71d705a7c2f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8", size = 403273, upload-time = "2025-08-27T12:15:37.051Z" }, - { url = "https://files.pythonhosted.org/packages/c0/93/425e32200158d44ff01da5d9612c3b6711fe69f606f06e3895511f17473b/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc", size = 385278, upload-time = "2025-08-27T12:15:38.571Z" }, - { url = "https://files.pythonhosted.org/packages/eb/1a/1a04a915ecd0551bfa9e77b7672d1937b4b72a0fc204a17deef76001cfb2/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71", size = 402084, upload-time = "2025-08-27T12:15:40.529Z" }, - { url = "https://files.pythonhosted.org/packages/51/f7/66585c0fe5714368b62951d2513b684e5215beaceab2c6629549ddb15036/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad", size = 419041, upload-time = "2025-08-27T12:15:42.191Z" }, - { url = "https://files.pythonhosted.org/packages/8e/7e/83a508f6b8e219bba2d4af077c35ba0e0cdd35a751a3be6a7cba5a55ad71/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab", size = 560084, upload-time = "2025-08-27T12:15:43.839Z" }, - { url = "https://files.pythonhosted.org/packages/66/66/bb945683b958a1b19eb0fe715594630d0f36396ebdef4d9b89c2fa09aa56/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059", size = 590115, upload-time = "2025-08-27T12:15:46.647Z" }, - { url = "https://files.pythonhosted.org/packages/12/00/ccfaafaf7db7e7adace915e5c2f2c2410e16402561801e9c7f96683002d3/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b", size = 556561, upload-time = "2025-08-27T12:15:48.219Z" }, - { url = "https://files.pythonhosted.org/packages/e1/b7/92b6ed9aad103bfe1c45df98453dfae40969eef2cb6c6239c58d7e96f1b3/rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819", size = 229125, upload-time = "2025-08-27T12:15:49.956Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ed/e1fba02de17f4f76318b834425257c8ea297e415e12c68b4361f63e8ae92/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df", size = 371402, upload-time = "2025-08-27T12:15:51.561Z" }, - { url = "https://files.pythonhosted.org/packages/af/7c/e16b959b316048b55585a697e94add55a4ae0d984434d279ea83442e460d/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3", size = 354084, upload-time = "2025-08-27T12:15:53.219Z" }, - { url = "https://files.pythonhosted.org/packages/de/c1/ade645f55de76799fdd08682d51ae6724cb46f318573f18be49b1e040428/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9", size = 383090, upload-time = "2025-08-27T12:15:55.158Z" }, - { url = "https://files.pythonhosted.org/packages/1f/27/89070ca9b856e52960da1472efcb6c20ba27cfe902f4f23ed095b9cfc61d/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc", size = 394519, upload-time = "2025-08-27T12:15:57.238Z" }, - { url = "https://files.pythonhosted.org/packages/b3/28/be120586874ef906aa5aeeae95ae8df4184bc757e5b6bd1c729ccff45ed5/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4", size = 523817, upload-time = "2025-08-27T12:15:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/70cc197bc11cfcde02a86f36ac1eed15c56667c2ebddbdb76a47e90306da/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66", size = 403240, upload-time = "2025-08-27T12:16:00.923Z" }, - { url = "https://files.pythonhosted.org/packages/cf/35/46936cca449f7f518f2f4996e0e8344db4b57e2081e752441154089d2a5f/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e", size = 385194, upload-time = "2025-08-27T12:16:02.802Z" }, - { url = "https://files.pythonhosted.org/packages/e1/62/29c0d3e5125c3270b51415af7cbff1ec587379c84f55a5761cc9efa8cd06/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c", size = 402086, upload-time = "2025-08-27T12:16:04.806Z" }, - { url = "https://files.pythonhosted.org/packages/8f/66/03e1087679227785474466fdd04157fb793b3b76e3fcf01cbf4c693c1949/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf", size = 419272, upload-time = "2025-08-27T12:16:06.471Z" }, - { url = "https://files.pythonhosted.org/packages/6a/24/e3e72d265121e00b063aef3e3501e5b2473cf1b23511d56e529531acf01e/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf", size = 560003, upload-time = "2025-08-27T12:16:08.06Z" }, - { url = "https://files.pythonhosted.org/packages/26/ca/f5a344c534214cc2d41118c0699fffbdc2c1bc7046f2a2b9609765ab9c92/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6", size = 590482, upload-time = "2025-08-27T12:16:10.137Z" }, - { url = "https://files.pythonhosted.org/packages/ce/08/4349bdd5c64d9d193c360aa9db89adeee6f6682ab8825dca0a3f535f434f/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", size = 556523, upload-time = "2025-08-27T12:16:12.188Z" }, -] - [[package]] name = "ruff" version = "0.12.12" @@ -2031,15 +1216,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/7e/61c42657f6e4614a4258f1c3b0c5b93adc4d1f8575f5229d1906b483099b/ruff-0.12.12-py3-none-win_arm64.whl", hash = "sha256:2a8199cab4ce4d72d158319b63370abf60991495fb733db96cd923a34c52d093", size = 12256762, upload-time = "2025-09-04T16:50:15.737Z" }, ] -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, -] - [[package]] name = "six" version = "1.17.0" @@ -2049,101 +1225,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, -] - -[[package]] -name = "tenacity" -version = "9.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, -] - -[[package]] -name = "textual" -version = "6.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown-it-py", extra = ["linkify", "plugins"] }, - { name = "platformdirs" }, - { name = "pygments" }, - { name = "rich" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/44/4b524b2f06e0fa6c4ede56a4e9af5edd5f3f83cf2eea5cb4fd0ce5bbe063/textual-6.1.0.tar.gz", hash = "sha256:cc89826ca2146c645563259320ca4ddc75d183c77afb7d58acdd46849df9144d", size = 1564786, upload-time = "2025-09-02T11:42:34.655Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/43/f91e041f239b54399310a99041faf33beae9a6e628671471d0fcd6276af4/textual-6.1.0-py3-none-any.whl", hash = "sha256:a3f5e6710404fcdc6385385db894699282dccf2ad50103cebc677403c1baadd5", size = 707840, upload-time = "2025-09-02T11:42:32.746Z" }, -] - -[[package]] -name = "tiktoken" -version = "0.11.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "regex" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a7/86/ad0155a37c4f310935d5ac0b1ccf9bdb635dcb906e0a9a26b616dd55825a/tiktoken-0.11.0.tar.gz", hash = "sha256:3c518641aee1c52247c2b97e74d8d07d780092af79d5911a6ab5e79359d9b06a", size = 37648, upload-time = "2025-08-08T23:58:08.495Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/4d/c6a2e7dca2b4f2e9e0bfd62b3fe4f114322e2c028cfba905a72bc76ce479/tiktoken-0.11.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:8a9b517d6331d7103f8bef29ef93b3cca95fa766e293147fe7bacddf310d5917", size = 1059937, upload-time = "2025-08-08T23:57:28.57Z" }, - { url = "https://files.pythonhosted.org/packages/41/54/3739d35b9f94cb8dc7b0db2edca7192d5571606aa2369a664fa27e811804/tiktoken-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b4ddb1849e6bf0afa6cc1c5d809fb980ca240a5fffe585a04e119519758788c0", size = 999230, upload-time = "2025-08-08T23:57:30.241Z" }, - { url = "https://files.pythonhosted.org/packages/dd/f4/ec8d43338d28d53513004ebf4cd83732a135d11011433c58bf045890cc10/tiktoken-0.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10331d08b5ecf7a780b4fe4d0281328b23ab22cdb4ff65e68d56caeda9940ecc", size = 1130076, upload-time = "2025-08-08T23:57:31.706Z" }, - { url = "https://files.pythonhosted.org/packages/94/80/fb0ada0a882cb453caf519a4bf0d117c2a3ee2e852c88775abff5413c176/tiktoken-0.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b062c82300341dc87e0258c69f79bed725f87e753c21887aea90d272816be882", size = 1183942, upload-time = "2025-08-08T23:57:33.142Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e9/6c104355b463601719582823f3ea658bc3aa7c73d1b3b7553ebdc48468ce/tiktoken-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:195d84bec46169af3b1349a1495c151d37a0ff4cba73fd08282736be7f92cc6c", size = 1244705, upload-time = "2025-08-08T23:57:34.594Z" }, - { url = "https://files.pythonhosted.org/packages/94/75/eaa6068f47e8b3f0aab9e05177cce2cf5aa2cc0ca93981792e620d4d4117/tiktoken-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe91581b0ecdd8783ce8cb6e3178f2260a3912e8724d2f2d49552b98714641a1", size = 884152, upload-time = "2025-08-08T23:57:36.18Z" }, - { url = "https://files.pythonhosted.org/packages/8a/91/912b459799a025d2842566fe1e902f7f50d54a1ce8a0f236ab36b5bd5846/tiktoken-0.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4ae374c46afadad0f501046db3da1b36cd4dfbfa52af23c998773682446097cf", size = 1059743, upload-time = "2025-08-08T23:57:37.516Z" }, - { url = "https://files.pythonhosted.org/packages/8c/e9/6faa6870489ce64f5f75dcf91512bf35af5864583aee8fcb0dcb593121f5/tiktoken-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25a512ff25dc6c85b58f5dd4f3d8c674dc05f96b02d66cdacf628d26a4e4866b", size = 999334, upload-time = "2025-08-08T23:57:38.595Z" }, - { url = "https://files.pythonhosted.org/packages/a1/3e/a05d1547cf7db9dc75d1461cfa7b556a3b48e0516ec29dfc81d984a145f6/tiktoken-0.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2130127471e293d385179c1f3f9cd445070c0772be73cdafb7cec9a3684c0458", size = 1129402, upload-time = "2025-08-08T23:57:39.627Z" }, - { url = "https://files.pythonhosted.org/packages/34/9a/db7a86b829e05a01fd4daa492086f708e0a8b53952e1dbc9d380d2b03677/tiktoken-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e43022bf2c33f733ea9b54f6a3f6b4354b909f5a73388fb1b9347ca54a069c", size = 1184046, upload-time = "2025-08-08T23:57:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/9d/bb/52edc8e078cf062ed749248f1454e9e5cfd09979baadb830b3940e522015/tiktoken-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:adb4e308eb64380dc70fa30493e21c93475eaa11669dea313b6bbf8210bfd013", size = 1244691, upload-time = "2025-08-08T23:57:42.251Z" }, - { url = "https://files.pythonhosted.org/packages/60/d9/884b6cd7ae2570ecdcaffa02b528522b18fef1cbbfdbcaa73799807d0d3b/tiktoken-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:ece6b76bfeeb61a125c44bbefdfccc279b5288e6007fbedc0d32bfec602df2f2", size = 884392, upload-time = "2025-08-08T23:57:43.628Z" }, - { url = "https://files.pythonhosted.org/packages/e7/9e/eceddeffc169fc75fe0fd4f38471309f11cb1906f9b8aa39be4f5817df65/tiktoken-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fd9e6b23e860973cf9526544e220b223c60badf5b62e80a33509d6d40e6c8f5d", size = 1055199, upload-time = "2025-08-08T23:57:45.076Z" }, - { url = "https://files.pythonhosted.org/packages/4f/cf/5f02bfefffdc6b54e5094d2897bc80efd43050e5b09b576fd85936ee54bf/tiktoken-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a76d53cee2da71ee2731c9caa747398762bda19d7f92665e882fef229cb0b5b", size = 996655, upload-time = "2025-08-08T23:57:46.304Z" }, - { url = "https://files.pythonhosted.org/packages/65/8e/c769b45ef379bc360c9978c4f6914c79fd432400a6733a8afc7ed7b0726a/tiktoken-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ef72aab3ea240646e642413cb363b73869fed4e604dcfd69eec63dc54d603e8", size = 1128867, upload-time = "2025-08-08T23:57:47.438Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2d/4d77f6feb9292bfdd23d5813e442b3bba883f42d0ac78ef5fdc56873f756/tiktoken-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f929255c705efec7a28bf515e29dc74220b2f07544a8c81b8d69e8efc4578bd", size = 1183308, upload-time = "2025-08-08T23:57:48.566Z" }, - { url = "https://files.pythonhosted.org/packages/7a/65/7ff0a65d3bb0fc5a1fb6cc71b03e0f6e71a68c5eea230d1ff1ba3fd6df49/tiktoken-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61f1d15822e4404953d499fd1dcc62817a12ae9fb1e4898033ec8fe3915fdf8e", size = 1244301, upload-time = "2025-08-08T23:57:49.642Z" }, - { url = "https://files.pythonhosted.org/packages/f5/6e/5b71578799b72e5bdcef206a214c3ce860d999d579a3b56e74a6c8989ee2/tiktoken-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:45927a71ab6643dfd3ef57d515a5db3d199137adf551f66453be098502838b0f", size = 884282, upload-time = "2025-08-08T23:57:50.759Z" }, - { url = "https://files.pythonhosted.org/packages/cc/cd/a9034bcee638716d9310443818d73c6387a6a96db93cbcb0819b77f5b206/tiktoken-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a5f3f25ffb152ee7fec78e90a5e5ea5b03b4ea240beed03305615847f7a6ace2", size = 1055339, upload-time = "2025-08-08T23:57:51.802Z" }, - { url = "https://files.pythonhosted.org/packages/f1/91/9922b345f611b4e92581f234e64e9661e1c524875c8eadd513c4b2088472/tiktoken-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dc6e9ad16a2a75b4c4be7208055a1f707c9510541d94d9cc31f7fbdc8db41d8", size = 997080, upload-time = "2025-08-08T23:57:53.442Z" }, - { url = "https://files.pythonhosted.org/packages/d0/9d/49cd047c71336bc4b4af460ac213ec1c457da67712bde59b892e84f1859f/tiktoken-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a0517634d67a8a48fd4a4ad73930c3022629a85a217d256a6e9b8b47439d1e4", size = 1128501, upload-time = "2025-08-08T23:57:54.808Z" }, - { url = "https://files.pythonhosted.org/packages/52/d5/a0dcdb40dd2ea357e83cb36258967f0ae96f5dd40c722d6e382ceee6bba9/tiktoken-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fb4effe60574675118b73c6fbfd3b5868e5d7a1f570d6cc0d18724b09ecf318", size = 1182743, upload-time = "2025-08-08T23:57:56.307Z" }, - { url = "https://files.pythonhosted.org/packages/3b/17/a0fc51aefb66b7b5261ca1314afa83df0106b033f783f9a7bcbe8e741494/tiktoken-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94f984c9831fd32688aef4348803b0905d4ae9c432303087bae370dc1381a2b8", size = 1244057, upload-time = "2025-08-08T23:57:57.628Z" }, - { url = "https://files.pythonhosted.org/packages/50/79/bcf350609f3a10f09fe4fc207f132085e497fdd3612f3925ab24d86a0ca0/tiktoken-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2177ffda31dec4023356a441793fed82f7af5291120751dee4d696414f54db0c", size = 883901, upload-time = "2025-08-08T23:57:59.359Z" }, -] - -[[package]] -name = "tokenizers" -version = "0.22.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5e/b4/c1ce3699e81977da2ace8b16d2badfd42b060e7d33d75c4ccdbf9dc920fa/tokenizers-0.22.0.tar.gz", hash = "sha256:2e33b98525be8453f355927f3cab312c36cd3e44f4d7e9e97da2fa94d0a49dcb", size = 362771, upload-time = "2025-08-29T10:25:33.914Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/b1/18c13648edabbe66baa85fe266a478a7931ddc0cd1ba618802eb7b8d9865/tokenizers-0.22.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:eaa9620122a3fb99b943f864af95ed14c8dfc0f47afa3b404ac8c16b3f2bb484", size = 3081954, upload-time = "2025-08-29T10:25:24.993Z" }, - { url = "https://files.pythonhosted.org/packages/c2/02/c3c454b641bd7c4f79e4464accfae9e7dfc913a777d2e561e168ae060362/tokenizers-0.22.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:71784b9ab5bf0ff3075bceeb198149d2c5e068549c0d18fe32d06ba0deb63f79", size = 2945644, upload-time = "2025-08-29T10:25:23.405Z" }, - { url = "https://files.pythonhosted.org/packages/55/02/d10185ba2fd8c2d111e124c9d92de398aee0264b35ce433f79fb8472f5d0/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec5b71f668a8076802b0241a42387d48289f25435b86b769ae1837cad4172a17", size = 3254764, upload-time = "2025-08-29T10:25:12.445Z" }, - { url = "https://files.pythonhosted.org/packages/13/89/17514bd7ef4bf5bfff58e2b131cec0f8d5cea2b1c8ffe1050a2c8de88dbb/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ea8562fa7498850d02a16178105b58803ea825b50dc9094d60549a7ed63654bb", size = 3161654, upload-time = "2025-08-29T10:25:15.493Z" }, - { url = "https://files.pythonhosted.org/packages/5a/d8/bac9f3a7ef6dcceec206e3857c3b61bb16c6b702ed7ae49585f5bd85c0ef/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4136e1558a9ef2e2f1de1555dcd573e1cbc4a320c1a06c4107a3d46dc8ac6e4b", size = 3511484, upload-time = "2025-08-29T10:25:20.477Z" }, - { url = "https://files.pythonhosted.org/packages/aa/27/9c9800eb6763683010a4851db4d1802d8cab9cec114c17056eccb4d4a6e0/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf5954de3962a5fd9781dc12048d24a1a6f1f5df038c6e95db328cd22964206", size = 3712829, upload-time = "2025-08-29T10:25:17.154Z" }, - { url = "https://files.pythonhosted.org/packages/10/e3/b1726dbc1f03f757260fa21752e1921445b5bc350389a8314dd3338836db/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8337ca75d0731fc4860e6204cc24bb36a67d9736142aa06ed320943b50b1e7ed", size = 3408934, upload-time = "2025-08-29T10:25:18.76Z" }, - { url = "https://files.pythonhosted.org/packages/d4/61/aeab3402c26874b74bb67a7f2c4b569dde29b51032c5384db592e7b216f4/tokenizers-0.22.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a89264e26f63c449d8cded9061adea7b5de53ba2346fc7e87311f7e4117c1cc8", size = 3345585, upload-time = "2025-08-29T10:25:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bc/d3/498b4a8a8764cce0900af1add0f176ff24f475d4413d55b760b8cdf00893/tokenizers-0.22.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:790bad50a1b59d4c21592f9c3cf5e5cf9c3c7ce7e1a23a739f13e01fb1be377a", size = 9322986, upload-time = "2025-08-29T10:25:26.607Z" }, - { url = "https://files.pythonhosted.org/packages/a2/62/92378eb1c2c565837ca3cb5f9569860d132ab9d195d7950c1ea2681dffd0/tokenizers-0.22.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:76cf6757c73a10ef10bf06fa937c0ec7393d90432f543f49adc8cab3fb6f26cb", size = 9276630, upload-time = "2025-08-29T10:25:28.349Z" }, - { url = "https://files.pythonhosted.org/packages/eb/f0/342d80457aa1cda7654327460f69db0d69405af1e4c453f4dc6ca7c4a76e/tokenizers-0.22.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1626cb186e143720c62c6c6b5371e62bbc10af60481388c0da89bc903f37ea0c", size = 9547175, upload-time = "2025-08-29T10:25:29.989Z" }, - { url = "https://files.pythonhosted.org/packages/14/84/8aa9b4adfc4fbd09381e20a5bc6aa27040c9c09caa89988c01544e008d18/tokenizers-0.22.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:da589a61cbfea18ae267723d6b029b84598dc8ca78db9951d8f5beff72d8507c", size = 9692735, upload-time = "2025-08-29T10:25:32.089Z" }, - { url = "https://files.pythonhosted.org/packages/bf/24/83ee2b1dc76bfe05c3142e7d0ccdfe69f0ad2f1ebf6c726cea7f0874c0d0/tokenizers-0.22.0-cp39-abi3-win32.whl", hash = "sha256:dbf9d6851bddae3e046fedfb166f47743c1c7bd11c640f0691dd35ef0bcad3be", size = 2471915, upload-time = "2025-08-29T10:25:36.411Z" }, - { url = "https://files.pythonhosted.org/packages/d1/9b/0e0bf82214ee20231845b127aa4a8015936ad5a46779f30865d10e404167/tokenizers-0.22.0-cp39-abi3-win_amd64.whl", hash = "sha256:c78174859eeaee96021f248a56c801e36bfb6bd5b067f2e95aa82445ca324f00", size = 2680494, upload-time = "2025-08-29T10:25:35.14Z" }, -] - [[package]] name = "tomli" version = "2.2.1" @@ -2220,18 +1301,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1f/ac/b32555d190c4440b8d2779d4a19439e5fbd5a3950f7e5a17ead7c7d30cad/tox_uv-1.28.0-py3-none-any.whl", hash = "sha256:3fbe13fa6eb6961df5512e63fc4a5cc0c8d264872674ee09164649f441839053", size = 17225, upload-time = "2025-08-14T17:53:06.299Z" }, ] -[[package]] -name = "tqdm" -version = "4.67.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, -] - [[package]] name = "ty" version = "0.0.1a20" @@ -2269,21 +1338,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl", hash = "sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e", size = 34874, upload-time = "2025-06-18T09:56:05.999Z" }, ] -[[package]] -name = "typer" -version = "0.17.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/92/e8/2a73ccf9874ec4c7638f172efc8972ceab13a0e3480b389d6ed822f7a822/typer-0.17.4.tar.gz", hash = "sha256:b77dc07d849312fd2bb5e7f20a7af8985c7ec360c45b051ed5412f64d8dc1580", size = 103734, upload-time = "2025-09-05T18:14:40.746Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/72/6b3e70d32e89a5cbb6a4513726c1ae8762165b027af569289e19ec08edd8/typer-0.17.4-py3-none-any.whl", hash = "sha256:015534a6edaa450e7007eba705d5c18c3349dcea50a6ad79a5ed530967575824", size = 46643, upload-time = "2025-09-05T18:14:39.166Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" @@ -2305,15 +1359,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, ] -[[package]] -name = "uc-micro-py" -version = "1.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/7a/146a99696aee0609e3712f2b44c6274566bc368dfe8375191278045186b8/uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a", size = 6043, upload-time = "2024-02-09T16:52:01.654Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/37/87/1f677586e8ac487e29672e4b17455758fce261de06a0d086167bb760361a/uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5", size = 6229, upload-time = "2024-02-09T16:52:00.371Z" }, -] - [[package]] name = "urllib3" version = "2.5.0" @@ -2397,111 +1442,72 @@ wheels = [ ] [[package]] -name = "wcwidth" -version = "0.2.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, -] - -[[package]] -name = "yarl" -version = "1.20.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "multidict" }, - { name = "propcache" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/65/7fed0d774abf47487c64be14e9223749468922817b5e8792b8a64792a1bb/yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4", size = 132910, upload-time = "2025-06-10T00:42:31.108Z" }, - { url = "https://files.pythonhosted.org/packages/8a/7b/988f55a52da99df9e56dc733b8e4e5a6ae2090081dc2754fc8fd34e60aa0/yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a", size = 90644, upload-time = "2025-06-10T00:42:33.851Z" }, - { url = "https://files.pythonhosted.org/packages/f7/de/30d98f03e95d30c7e3cc093759982d038c8833ec2451001d45ef4854edc1/yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed", size = 89322, upload-time = "2025-06-10T00:42:35.688Z" }, - { url = "https://files.pythonhosted.org/packages/e0/7a/f2f314f5ebfe9200724b0b748de2186b927acb334cf964fd312eb86fc286/yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e", size = 323786, upload-time = "2025-06-10T00:42:37.817Z" }, - { url = "https://files.pythonhosted.org/packages/15/3f/718d26f189db96d993d14b984ce91de52e76309d0fd1d4296f34039856aa/yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73", size = 319627, upload-time = "2025-06-10T00:42:39.937Z" }, - { url = "https://files.pythonhosted.org/packages/a5/76/8fcfbf5fa2369157b9898962a4a7d96764b287b085b5b3d9ffae69cdefd1/yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e", size = 339149, upload-time = "2025-06-10T00:42:42.627Z" }, - { url = "https://files.pythonhosted.org/packages/3c/95/d7fc301cc4661785967acc04f54a4a42d5124905e27db27bb578aac49b5c/yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8", size = 333327, upload-time = "2025-06-10T00:42:44.842Z" }, - { url = "https://files.pythonhosted.org/packages/65/94/e21269718349582eee81efc5c1c08ee71c816bfc1585b77d0ec3f58089eb/yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23", size = 326054, upload-time = "2025-06-10T00:42:47.149Z" }, - { url = "https://files.pythonhosted.org/packages/32/ae/8616d1f07853704523519f6131d21f092e567c5af93de7e3e94b38d7f065/yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70", size = 315035, upload-time = "2025-06-10T00:42:48.852Z" }, - { url = "https://files.pythonhosted.org/packages/48/aa/0ace06280861ef055855333707db5e49c6e3a08840a7ce62682259d0a6c0/yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb", size = 338962, upload-time = "2025-06-10T00:42:51.024Z" }, - { url = "https://files.pythonhosted.org/packages/20/52/1e9d0e6916f45a8fb50e6844f01cb34692455f1acd548606cbda8134cd1e/yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2", size = 335399, upload-time = "2025-06-10T00:42:53.007Z" }, - { url = "https://files.pythonhosted.org/packages/f2/65/60452df742952c630e82f394cd409de10610481d9043aa14c61bf846b7b1/yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30", size = 338649, upload-time = "2025-06-10T00:42:54.964Z" }, - { url = "https://files.pythonhosted.org/packages/7b/f5/6cd4ff38dcde57a70f23719a838665ee17079640c77087404c3d34da6727/yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309", size = 358563, upload-time = "2025-06-10T00:42:57.28Z" }, - { url = "https://files.pythonhosted.org/packages/d1/90/c42eefd79d0d8222cb3227bdd51b640c0c1d0aa33fe4cc86c36eccba77d3/yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24", size = 357609, upload-time = "2025-06-10T00:42:59.055Z" }, - { url = "https://files.pythonhosted.org/packages/03/c8/cea6b232cb4617514232e0f8a718153a95b5d82b5290711b201545825532/yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13", size = 350224, upload-time = "2025-06-10T00:43:01.248Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a3/eaa0ab9712f1f3d01faf43cf6f1f7210ce4ea4a7e9b28b489a2261ca8db9/yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8", size = 81753, upload-time = "2025-06-10T00:43:03.486Z" }, - { url = "https://files.pythonhosted.org/packages/8f/34/e4abde70a9256465fe31c88ed02c3f8502b7b5dead693a4f350a06413f28/yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16", size = 86817, upload-time = "2025-06-10T00:43:05.231Z" }, - { url = "https://files.pythonhosted.org/packages/b1/18/893b50efc2350e47a874c5c2d67e55a0ea5df91186b2a6f5ac52eff887cd/yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e", size = 133833, upload-time = "2025-06-10T00:43:07.393Z" }, - { url = "https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b", size = 91070, upload-time = "2025-06-10T00:43:09.538Z" }, - { url = "https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b", size = 89818, upload-time = "2025-06-10T00:43:11.575Z" }, - { url = "https://files.pythonhosted.org/packages/f8/77/64d8431a4d77c856eb2d82aa3de2ad6741365245a29b3a9543cd598ed8c5/yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4", size = 347003, upload-time = "2025-06-10T00:43:14.088Z" }, - { url = "https://files.pythonhosted.org/packages/8d/d2/0c7e4def093dcef0bd9fa22d4d24b023788b0a33b8d0088b51aa51e21e99/yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1", size = 336537, upload-time = "2025-06-10T00:43:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f3/fc514f4b2cf02cb59d10cbfe228691d25929ce8f72a38db07d3febc3f706/yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833", size = 362358, upload-time = "2025-06-10T00:43:18.704Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6d/a313ac8d8391381ff9006ac05f1d4331cee3b1efaa833a53d12253733255/yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d", size = 357362, upload-time = "2025-06-10T00:43:20.888Z" }, - { url = "https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8", size = 348979, upload-time = "2025-06-10T00:43:23.169Z" }, - { url = "https://files.pythonhosted.org/packages/cb/05/42773027968968f4f15143553970ee36ead27038d627f457cc44bbbeecf3/yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf", size = 337274, upload-time = "2025-06-10T00:43:27.111Z" }, - { url = "https://files.pythonhosted.org/packages/05/be/665634aa196954156741ea591d2f946f1b78ceee8bb8f28488bf28c0dd62/yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e", size = 363294, upload-time = "2025-06-10T00:43:28.96Z" }, - { url = "https://files.pythonhosted.org/packages/eb/90/73448401d36fa4e210ece5579895731f190d5119c4b66b43b52182e88cd5/yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389", size = 358169, upload-time = "2025-06-10T00:43:30.701Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b0/fce922d46dc1eb43c811f1889f7daa6001b27a4005587e94878570300881/yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f", size = 362776, upload-time = "2025-06-10T00:43:32.51Z" }, - { url = "https://files.pythonhosted.org/packages/f1/0d/b172628fce039dae8977fd22caeff3eeebffd52e86060413f5673767c427/yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845", size = 381341, upload-time = "2025-06-10T00:43:34.543Z" }, - { url = "https://files.pythonhosted.org/packages/6b/9b/5b886d7671f4580209e855974fe1cecec409aa4a89ea58b8f0560dc529b1/yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1", size = 379988, upload-time = "2025-06-10T00:43:36.489Z" }, - { url = "https://files.pythonhosted.org/packages/73/be/75ef5fd0fcd8f083a5d13f78fd3f009528132a1f2a1d7c925c39fa20aa79/yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e", size = 371113, upload-time = "2025-06-10T00:43:38.592Z" }, - { url = "https://files.pythonhosted.org/packages/50/4f/62faab3b479dfdcb741fe9e3f0323e2a7d5cd1ab2edc73221d57ad4834b2/yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773", size = 81485, upload-time = "2025-06-10T00:43:41.038Z" }, - { url = "https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e", size = 86686, upload-time = "2025-06-10T00:43:42.692Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" }, - { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" }, - { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" }, - { url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" }, - { url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" }, - { url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" }, - { url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" }, - { url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" }, - { url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" }, - { url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" }, - { url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" }, - { url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" }, - { url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" }, - { url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" }, - { url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" }, - { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, - { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, - { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" }, - { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" }, - { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" }, - { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" }, - { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" }, - { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" }, - { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" }, - { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" }, - { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" }, - { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" }, - { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" }, - { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" }, - { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" }, - { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" }, - { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, - { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, - { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" }, - { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" }, - { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" }, - { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" }, - { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" }, - { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" }, - { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" }, - { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" }, - { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" }, - { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" }, - { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" }, - { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" }, - { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, - { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/23/bb82321b86411eb51e5a5db3fb8f8032fd30bd7c2d74bfe936136b2fa1d6/wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04", size = 53482, upload-time = "2025-08-12T05:51:44.467Z" }, + { url = "https://files.pythonhosted.org/packages/45/69/f3c47642b79485a30a59c63f6d739ed779fb4cc8323205d047d741d55220/wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2", size = 38676, upload-time = "2025-08-12T05:51:32.636Z" }, + { url = "https://files.pythonhosted.org/packages/d1/71/e7e7f5670c1eafd9e990438e69d8fb46fa91a50785332e06b560c869454f/wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c", size = 38957, upload-time = "2025-08-12T05:51:54.655Z" }, + { url = "https://files.pythonhosted.org/packages/de/17/9f8f86755c191d6779d7ddead1a53c7a8aa18bccb7cea8e7e72dfa6a8a09/wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775", size = 81975, upload-time = "2025-08-12T05:52:30.109Z" }, + { url = "https://files.pythonhosted.org/packages/f2/15/dd576273491f9f43dd09fce517f6c2ce6eb4fe21681726068db0d0467096/wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd", size = 83149, upload-time = "2025-08-12T05:52:09.316Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c4/5eb4ce0d4814521fee7aa806264bf7a114e748ad05110441cd5b8a5c744b/wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05", size = 82209, upload-time = "2025-08-12T05:52:10.331Z" }, + { url = "https://files.pythonhosted.org/packages/31/4b/819e9e0eb5c8dc86f60dfc42aa4e2c0d6c3db8732bce93cc752e604bb5f5/wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418", size = 81551, upload-time = "2025-08-12T05:52:31.137Z" }, + { url = "https://files.pythonhosted.org/packages/f8/83/ed6baf89ba3a56694700139698cf703aac9f0f9eb03dab92f57551bd5385/wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390", size = 36464, upload-time = "2025-08-12T05:53:01.204Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/ee61d36862340ad7e9d15a02529df6b948676b9a5829fd5e16640156627d/wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6", size = 38748, upload-time = "2025-08-12T05:53:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c3/cefe0bd330d389c9983ced15d326f45373f4073c9f4a8c2f99b50bfea329/wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18", size = 36810, upload-time = "2025-08-12T05:52:51.906Z" }, + { url = "https://files.pythonhosted.org/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7", size = 53482, upload-time = "2025-08-12T05:51:45.79Z" }, + { url = "https://files.pythonhosted.org/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85", size = 38674, upload-time = "2025-08-12T05:51:34.629Z" }, + { url = "https://files.pythonhosted.org/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f", size = 38959, upload-time = "2025-08-12T05:51:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311", size = 82376, upload-time = "2025-08-12T05:52:32.134Z" }, + { url = "https://files.pythonhosted.org/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1", size = 83604, upload-time = "2025-08-12T05:52:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5", size = 82782, upload-time = "2025-08-12T05:52:12.626Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2", size = 82076, upload-time = "2025-08-12T05:52:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89", size = 36457, upload-time = "2025-08-12T05:53:03.936Z" }, + { url = "https://files.pythonhosted.org/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77", size = 38745, upload-time = "2025-08-12T05:53:02.885Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a", size = 36806, upload-time = "2025-08-12T05:52:53.368Z" }, + { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, + { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, + { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, + { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] [[package]]