diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ff261ba..62c2d13 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,7 +3,6 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} USER vscode -RUN curl -sSf https://rye.astral.sh/get | RYE_VERSION="0.44.0" RYE_INSTALL_OPTION="--yes" bash -ENV PATH=/home/vscode/.rye/shims:$PATH +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ RUN echo "[[ -d .venv ]] && source .venv/bin/activate || export PATH=\$PATH" >> /home/vscode/.bashrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c17fdc1..e01283d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,7 +7,7 @@ "context": ".." }, - "postStartCommand": "rye sync --all-features", + "postStartCommand": "uv sync --all-extras", "customizations": { "vscode": { @@ -20,7 +20,7 @@ "python.defaultInterpreterPath": ".venv/bin/python", "python.typeChecking": "basic", "terminal.integrated.env.linux": { - "PATH": "/home/vscode/.rye/shims:${env:PATH}" + "PATH": "${env:PATH}" } } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97fdc7a..169eadd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,16 +21,13 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install Rye - run: | - curl -sSf https://rye.astral.sh/get | bash - echo "$HOME/.rye/shims" >> $GITHUB_PATH - env: - RYE_VERSION: '0.44.0' - RYE_INSTALL_OPTION: '--yes' + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: '0.9.13' - name: Install dependencies - run: rye sync --all-features + run: uv sync --all-extras - name: Run lints run: ./scripts/lint @@ -46,19 +43,16 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install Rye - run: | - curl -sSf https://rye.astral.sh/get | bash - echo "$HOME/.rye/shims" >> $GITHUB_PATH - env: - RYE_VERSION: '0.44.0' - RYE_INSTALL_OPTION: '--yes' + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: '0.9.13' - name: Install dependencies - run: rye sync --all-features + run: uv sync --all-extras - name: Run build - run: rye build + run: uv build - name: Get GitHub OIDC Token if: github.repository == 'stainless-sdks/dedalus-sdk-python' @@ -83,13 +77,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install Rye - run: | - curl -sSf https://rye.astral.sh/get | bash - echo "$HOME/.rye/shims" >> $GITHUB_PATH - env: - RYE_VERSION: '0.44.0' - RYE_INSTALL_OPTION: '--yes' + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: '0.9.13' - name: Bootstrap run: ./scripts/bootstrap diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index a2f53d7..0523779 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -16,13 +16,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install Rye - run: | - curl -sSf https://rye.astral.sh/get | bash - echo "$HOME/.rye/shims" >> $GITHUB_PATH - env: - RYE_VERSION: '0.44.0' - RYE_INSTALL_OPTION: '--yes' + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: '0.9.13' - name: Publish to PyPI run: | diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 5547f83..10f3091 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.1" + ".": "0.2.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index becdd9e..d40b845 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 12 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/dedalus-labs--inc-dash%2Fdedalus-sdk-5ea14da15b01354db7e852ec1bb581d08b90b6e6fcdb846aacbecbe09943f484.yml -openapi_spec_hash: 46809b9015c6110f0a47acc1696d51dd -config_hash: 635871f7432aa7667937941f2a4742de +configured_endpoints: 10 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/dedalus-labs%2Fdedalus-sdk-9543ba4968eb09fe1d5ccf3bcbc0acdc614a53401893cfb15f530d51d7fe952d.yml +openapi_spec_hash: eebaaecfa11e98efa3c44d709c08cbd6 +config_hash: 1890670c4485d0ade7c70a0c8bd20423 diff --git a/Brewfile b/Brewfile index 492ca37..c43041c 100644 --- a/Brewfile +++ b/Brewfile @@ -1,2 +1,2 @@ -brew "rye" +brew "uv" diff --git a/CHANGELOG.md b/CHANGELOG.md index f1bfeb2..e940d47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,942 @@ # Changelog +## 0.2.0 (2026-01-08) + +Full Changelog: [v0.2.0...v0.2.0](https://github.com/dedalus-labs/dedalus-sdk-python/compare/v0.2.0...v0.2.0) + +### Features + +* add image edits/variation and vision format support ([f8a8c84](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f8a8c84f3379d92619de56929d6ad3048989b18c)) +* **api:** add endpoints ([c10d7b5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c10d7b55a8ec6bb82556b3efe4db20c91959131d)) +* **api:** add streaming ([745c331](https://github.com/dedalus-labs/dedalus-sdk-python/commit/745c33166a671b79a978961d576064618cc80bcb)) +* **api:** add streaming configuration ([0172ad5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0172ad5175dd15650252a084f213b16c56b8befc)) +* **api:** api update ([280a595](https://github.com/dedalus-labs/dedalus-sdk-python/commit/280a595b3d3900625cfdf26be12027a88eff9618)) +* **api:** auto exec tools ([780162b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/780162b01d27703bb873488702ebede232791ed2)) +* **api:** config update for dedalus-ai/dev ([34e7a71](https://github.com/dedalus-labs/dedalus-sdk-python/commit/34e7a7167f6db94bb7c8c10a7c11746656aec5a6)) +* **api:** config update for dedalus-ai/dev ([197d11b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/197d11bf57da500e05cde2861146da9e3ec278f3)) +* **api:** convenient bug reporting ux ([5aa032f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5aa032f24a9fe44d23cfbf83f12fc79104529c8d)) +* **api:** image support ([ca28133](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ca281334db05ac2e939436050d1cf70ca5359ab4)) +* **api:** improve types ([62cf7e1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/62cf7e1a643cda58ee5112a1a8fc35253fe50f07)) +* **api:** manual updates ([e25dc9b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e25dc9b93f04021750736d60a6f2b455c330220b)) +* **api:** manual updates ([9bb6d0d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9bb6d0d0433111d177410f7ff21d3de254d899a0)) +* **api:** manual updates ([9b2851a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b2851a6bdbf861c0db0b01aa3e7a8f5a45bfa77)) +* **api:** mcp server params ([bf78aad](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bf78aad94d159622460b3a9ecddb9c5d1e1c82bb)) +* **api:** messages param nullable ([e905235](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e9052357b7efa9d49b4f8b8d4c7dfc026d69414b)) +* **api:** response format ([660ac29](https://github.com/dedalus-labs/dedalus-sdk-python/commit/660ac2954efc08eaed5212da934203b5b80f522e)) +* **api:** revert streaming for now ([56d57d5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/56d57d5a19034eec13d5a98a86d133d36ac2830a)) +* **api:** schema compiler landed ([9aeb7a7](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9aeb7a78bfaa81c07e920268afcacbba4a4ff9c9)) +* **api:** standardize name casing with stainless initialism ([ba1e188](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ba1e188beb62f6def79720d7d2ec8e22853fadaf)) +* **api:** stream helper for pydantic ([c4ab8b0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c4ab8b0d911b92afe76de99143567e6898e9e95c)) +* **api:** streaming support for structured output ([48ddd0a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/48ddd0a996e99b65fb4635f276a5562ef567ed26)) +* **api:** update via SDK Studio ([99693c9](https://github.com/dedalus-labs/dedalus-sdk-python/commit/99693c97a916e9ef3f9bde285e7c478aebf89991)) +* **api:** update via SDK Studio ([9407b44](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9407b44fa8dbd4df7c18c36eab95a5573399810a)) +* **client:** support file upload requests ([caadecd](https://github.com/dedalus-labs/dedalus-sdk-python/commit/caadecdf5c75297819cd41fe3adcc5f7af3de772)) +* encryption ([d72d130](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d72d130279b78fb4f946199a003032001b9c162e)) +* flexible input params for .parse() ([b208fbe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b208fbed8300526b323ac7c935d6d50bb652f0d3)) +* structured outputs for tools ([b0434ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b0434ca32e43dc5ef254e3fecb5493a2d3896384)) + + +### Bug Fixes + +* **api:** add shared DedalusModel type ([8855a07](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8855a07e4ea638102e71a049e182891e76e3d34d)) +* **api:** add thought signature ([d2203b1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d2203b129e6b64bbae0b1966654723c2f1ca1159)) +* **api:** docstring truncation ([699f8b9](https://github.com/dedalus-labs/dedalus-sdk-python/commit/699f8b962472ddb4ff2514cc1e515f76ccae5c21)) +* **api:** hardened _compat types ([312b628](https://github.com/dedalus-labs/dedalus-sdk-python/commit/312b628b48d15aaae5b4d2765a75d2b6b830e318)) +* **api:** improve types ([e3a3293](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e3a32933f4d8a568502dc6896ec6ea4ef9054bf4)) +* **api:** mcp credential types ([3cdef11](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3cdef112c7f647bcdcca903be56cf89115d47c0a)) +* **api:** relocate parts of auth logic ([a0d8615](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a0d8615f237331f8b79012e9cb5991dc929711d1)) +* **api:** standardize to use automatic_tool_execution ([731f753](https://github.com/dedalus-labs/dedalus-sdk-python/commit/731f753421de4a90b775fff8bee210d3aaf29a21)) +* **api:** syntactical sugar for json types ([6908b05](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6908b05580ce26b04590170dd24ccef0196cb900)) +* **api:** syntactical sugar for json types ([#37](https://github.com/dedalus-labs/dedalus-sdk-python/issues/37)) ([8f29baf](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8f29baf09cc732459f2d14335e39caa0084513c9)) +* **api:** typed json objects ([db161b2](https://github.com/dedalus-labs/dedalus-sdk-python/commit/db161b27ca2608d08a915323ced9d76fab6263bb)) +* **api:** typed json objects ([6dbc75d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6dbc75dc331cd2542e19aa7d2cc530dd091d7e65)) +* **api:** update types/docstrings ([8c0b864](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8c0b864a31673bdd62166eaffcceb911166054d3)) +* **client:** close streams without requiring full consumption ([24c4190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/24c4190ccb71d2c369b3d79f5764be31f2e8ead7)) +* **client:** loosen auth header validation ([05a96bd](https://github.com/dedalus-labs/dedalus-sdk-python/commit/05a96bd37827782c98899db95e674e1ddbab2110)) +* compat with Python 3.14 ([aacb192](https://github.com/dedalus-labs/dedalus-sdk-python/commit/aacb192910f8bdd09625f098874cf54d3c0c0971)) +* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([bd1df12](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bd1df12d6c26ad35101bd7b181f33ee8f7fe75ce)) +* ensure streams are always closed ([e0e6406](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e0e6406dc5faeaae21286324d4d247e2706481e1)) +* import paths and tests ([70c967f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/70c967f97d9479307128d6215731a8296bf3fa18)) +* mcp auth types ([7eca858](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7eca858a6967eb789ef1282b28ec63971c2975dd)) +* **mcp:** correct wire format for URL-based servers ([9e3c2f2](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9e3c2f21bc937ab420c5b1ef93a205295ccbc7b5)) +* **mcp:** correct wire format for URL-based servers ([#41](https://github.com/dedalus-labs/dedalus-sdk-python/issues/41)) ([660af64](https://github.com/dedalus-labs/dedalus-sdk-python/commit/660af643af019afbe4a2ff1684fe07ed5d0d6ddf)) +* **parsing:** handle LengthFinishReasonError and ContentFilterFinishReasonError ([8f8be5b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8f8be5b01fa84c4c06716b8f7d39ba5adf23ea56)) +* reconcile generated and integrated SDK branches ([de73670](https://github.com/dedalus-labs/dedalus-sdk-python/commit/de736702cef6a807c464c69153e4cd878a847c47)) +* remove rootmodel hack ([6bae04e](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6bae04eacd8f4ccd87dfbaf57223147ff6f2896e)) +* runner tool calling mechanics ([a07f8eb](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a07f8ebd7d65d9a054bba7838443da90f396762d)) +* **runner:** import and dict issues ([fd500d6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd500d6c9ba7c503bfecbf7a3c53b8eaef149d59)) +* **runner:** narrow mcp types ([172b56b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/172b56b0ff518004f1bfb65fd0c28dca3215b026)) +* **runner:** use TypeAlias from typing_extensions for py3.9+ support ([0625b2c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0625b2c9cd7569140fb81848b9d77bbbfbe256b9)) +* **streaming:** add exception classes for structured output streaming ([18e861f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/18e861f8721b9624927b96288eaeb2c90bcb43ac)) +* **streaming:** correct stream type detection ([7b6576c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7b6576c23400bca2420e6782defa633b0f3dbff9)) +* **streaming:** use _post() in stream() to support Responses API aliases ([758c5c3](https://github.com/dedalus-labs/dedalus-sdk-python/commit/758c5c3e86b51e870bd6b742399b80e5b30f5847)) +* **tests:** update bug reporting parameters/paths ([3838ebe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3838ebe4db440a852c233cd2a96c2b7a4f0c4082)) +* tool call format and custom_auth validation ([b822aa2](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b822aa214cbf0f735c4480a156639888a80956b1)) +* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([624e2b6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/624e2b67c3b784d64548fb6a3ebde7283b593279)) +* **types:** remove manual DedalusModel ([e1ce236](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e1ce236b931b0715b9fa280ef329bfa451eb05c1)) +* **types:** resolve merge conflicts in streaming types ([5e5953a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5e5953a22c0140648b96777bc68f392c5b80b243)) +* use async_to_httpx_files in patch method ([056ef78](https://github.com/dedalus-labs/dedalus-sdk-python/commit/056ef7802bda00b3037af8d745776e4aa836ba8c)) +* use typealiastype to prevent recursive type issues ([0aeaf80](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0aeaf80d4b51a28a60dbb9f7527e46c5881bd837)) + + +### Chores + +* add missing docstrings ([52ded59](https://github.com/dedalus-labs/dedalus-sdk-python/commit/52ded5987a0cd141499157a3bf9804fe342e5494)) +* **api:** point local dev to 4010 port for prism ([ad0ec37](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ad0ec3766423c5e6374a96afe55bf415a9c2792c)) +* **api:** rename MCPToolExecution -> MCPToolResult ([973611a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/973611a9f411002a00f5b703b30d44c3b2de450a)) +* **auth:** add minor auth params ([c39bcfc](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c39bcfc6366411658cc12e81b5f473176a26fad3)) +* bump `httpx-aiohttp` version to 0.1.9 ([6b5f606](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6b5f60653d76bfe6b4a85d841f115d59c5eba976)) +* bump required `uv` version ([cb3f674](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb3f674dbc9f6392493de7984bcd5fee385adfa0)) +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([cb4d323](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb4d3232c845b60eeccd23efa06057c8408085f5)) +* **docs:** use environment variables for authentication in code snippets ([e4330c0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e4330c0db0f371c49f320ffb6f8238da0229f890)) +* **internal/tests:** avoid race condition with implicit client cleanup ([0854f1d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0854f1d8f52ad5d75c2da1781358f96543793c02)) +* **internal:** add `--fix` argument to lint script ([b25908b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b25908bef7007756e7596fda321ab986366f39dc)) +* **internal:** add missing files argument to base client ([f3a6008](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f3a60083b1a37c3bb798e8c07b97af5f04c16b0d)) +* **internal:** avoid using unstable Python versions in tests ([5905b55](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5905b553aaacf8ef4512d851d68b0e0eb7bc647c)) +* **internal:** codegen related update ([f8be312](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f8be3120a918cddd164f5ea185fb2da3058c8f9e)) +* **internal:** codegen related update ([d288b0b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d288b0b4f8098c0aab0d79d45f27fd7b7cd8d127)) +* **internal:** detect missing future annotations with ruff ([6909c09](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6909c09996be7fe019ec6737a18b7e330b325c4a)) +* **internal:** grammar fix (it's -> its) ([f0c5880](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f0c58800e495c0cd5c1f13a9799e8e2025154a1c)) +* **package:** drop Python 3.8 support ([ef5e794](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ef5e794d49c800706eeb693170ea4a3ac0245290)) +* remove custom code ([81f922b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/81f922b8eabc571abf4cfd1b87e08517b4564128)) +* **repo:** update contributing doc and mock test script ([fd52f5a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd52f5acd095279bd26d647ee3f93c2daf7977df)) +* tidy for regen ([4e9f631](https://github.com/dedalus-labs/dedalus-sdk-python/commit/4e9f6316e7a66c5902dadbbdec43664882a85168)) +* tidy protocol docstrings ([99c65f6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/99c65f6f3f89f1958a94f5aaa0daf9511aafbcd1)) +* tidying ([354f95b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/354f95b1efde6b3df27275b3b8a36510f28d1858)) +* update lockfile ([46f1379](https://github.com/dedalus-labs/dedalus-sdk-python/commit/46f13790651a2c284d50c414556fee925c7ff089)) + + +### Documentation + +* prominently feature MCP server setup in root SDK readmes ([2889d94](https://github.com/dedalus-labs/dedalus-sdk-python/commit/2889d94d916e466d673e55cf59a3aca7efec08ef)) + + +### Styles + +* enforce double quotes via ruff ([eda6f77](https://github.com/dedalus-labs/dedalus-sdk-python/commit/eda6f77db38c0a02aec66c81aebc30aab2e33436)) + + +### Refactors + +* **api:** types for mcp server serialization ([2f614ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/2f614ca5493c9d57807aea9105e77a66c4703130)) +* **api:** update auth types ([9b17190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b17190b28400652ccbdb3f60499a163168e0b80)) +* **api:** update types ([584fe2a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/584fe2aa544ce99955d1d0a712cb51267e06357d)) + +## 0.2.0 (2026-01-07) + +Full Changelog: [v0.2.0...v0.2.0](https://github.com/dedalus-labs/dedalus-sdk-python/compare/v0.2.0...v0.2.0) + +### Features + +* add image edits/variation and vision format support ([f8a8c84](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f8a8c84f3379d92619de56929d6ad3048989b18c)) +* **api:** add endpoints ([c10d7b5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c10d7b55a8ec6bb82556b3efe4db20c91959131d)) +* **api:** add streaming ([745c331](https://github.com/dedalus-labs/dedalus-sdk-python/commit/745c33166a671b79a978961d576064618cc80bcb)) +* **api:** add streaming configuration ([0172ad5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0172ad5175dd15650252a084f213b16c56b8befc)) +* **api:** api update ([280a595](https://github.com/dedalus-labs/dedalus-sdk-python/commit/280a595b3d3900625cfdf26be12027a88eff9618)) +* **api:** auto exec tools ([780162b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/780162b01d27703bb873488702ebede232791ed2)) +* **api:** config update for dedalus-ai/dev ([34e7a71](https://github.com/dedalus-labs/dedalus-sdk-python/commit/34e7a7167f6db94bb7c8c10a7c11746656aec5a6)) +* **api:** config update for dedalus-ai/dev ([197d11b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/197d11bf57da500e05cde2861146da9e3ec278f3)) +* **api:** convenient bug reporting ux ([5aa032f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5aa032f24a9fe44d23cfbf83f12fc79104529c8d)) +* **api:** image support ([ca28133](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ca281334db05ac2e939436050d1cf70ca5359ab4)) +* **api:** improve types ([62cf7e1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/62cf7e1a643cda58ee5112a1a8fc35253fe50f07)) +* **api:** manual updates ([e25dc9b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e25dc9b93f04021750736d60a6f2b455c330220b)) +* **api:** manual updates ([9bb6d0d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9bb6d0d0433111d177410f7ff21d3de254d899a0)) +* **api:** manual updates ([9b2851a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b2851a6bdbf861c0db0b01aa3e7a8f5a45bfa77)) +* **api:** mcp server params ([bf78aad](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bf78aad94d159622460b3a9ecddb9c5d1e1c82bb)) +* **api:** messages param nullable ([e905235](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e9052357b7efa9d49b4f8b8d4c7dfc026d69414b)) +* **api:** response format ([660ac29](https://github.com/dedalus-labs/dedalus-sdk-python/commit/660ac2954efc08eaed5212da934203b5b80f522e)) +* **api:** revert streaming for now ([56d57d5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/56d57d5a19034eec13d5a98a86d133d36ac2830a)) +* **api:** schema compiler landed ([9aeb7a7](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9aeb7a78bfaa81c07e920268afcacbba4a4ff9c9)) +* **api:** standardize name casing with stainless initialism ([ba1e188](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ba1e188beb62f6def79720d7d2ec8e22853fadaf)) +* **api:** stream helper for pydantic ([c4ab8b0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c4ab8b0d911b92afe76de99143567e6898e9e95c)) +* **api:** streaming support for structured output ([48ddd0a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/48ddd0a996e99b65fb4635f276a5562ef567ed26)) +* **api:** update via SDK Studio ([99693c9](https://github.com/dedalus-labs/dedalus-sdk-python/commit/99693c97a916e9ef3f9bde285e7c478aebf89991)) +* **api:** update via SDK Studio ([9407b44](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9407b44fa8dbd4df7c18c36eab95a5573399810a)) +* **client:** support file upload requests ([caadecd](https://github.com/dedalus-labs/dedalus-sdk-python/commit/caadecdf5c75297819cd41fe3adcc5f7af3de772)) +* encryption ([d72d130](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d72d130279b78fb4f946199a003032001b9c162e)) +* flexible input params for .parse() ([b208fbe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b208fbed8300526b323ac7c935d6d50bb652f0d3)) +* structured outputs for tools ([b0434ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b0434ca32e43dc5ef254e3fecb5493a2d3896384)) + + +### Bug Fixes + +* **api:** add shared DedalusModel type ([8855a07](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8855a07e4ea638102e71a049e182891e76e3d34d)) +* **api:** add thought signature ([d2203b1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d2203b129e6b64bbae0b1966654723c2f1ca1159)) +* **api:** docstring truncation ([699f8b9](https://github.com/dedalus-labs/dedalus-sdk-python/commit/699f8b962472ddb4ff2514cc1e515f76ccae5c21)) +* **api:** hardened _compat types ([312b628](https://github.com/dedalus-labs/dedalus-sdk-python/commit/312b628b48d15aaae5b4d2765a75d2b6b830e318)) +* **api:** improve types ([e3a3293](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e3a32933f4d8a568502dc6896ec6ea4ef9054bf4)) +* **api:** mcp credential types ([3cdef11](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3cdef112c7f647bcdcca903be56cf89115d47c0a)) +* **api:** relocate parts of auth logic ([a0d8615](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a0d8615f237331f8b79012e9cb5991dc929711d1)) +* **api:** standardize to use automatic_tool_execution ([731f753](https://github.com/dedalus-labs/dedalus-sdk-python/commit/731f753421de4a90b775fff8bee210d3aaf29a21)) +* **api:** syntactical sugar for json types ([6908b05](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6908b05580ce26b04590170dd24ccef0196cb900)) +* **api:** syntactical sugar for json types ([#37](https://github.com/dedalus-labs/dedalus-sdk-python/issues/37)) ([8f29baf](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8f29baf09cc732459f2d14335e39caa0084513c9)) +* **api:** typed json objects ([db161b2](https://github.com/dedalus-labs/dedalus-sdk-python/commit/db161b27ca2608d08a915323ced9d76fab6263bb)) +* **api:** typed json objects ([6dbc75d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6dbc75dc331cd2542e19aa7d2cc530dd091d7e65)) +* **api:** update types/docstrings ([8c0b864](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8c0b864a31673bdd62166eaffcceb911166054d3)) +* **client:** close streams without requiring full consumption ([24c4190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/24c4190ccb71d2c369b3d79f5764be31f2e8ead7)) +* **client:** loosen auth header validation ([05a96bd](https://github.com/dedalus-labs/dedalus-sdk-python/commit/05a96bd37827782c98899db95e674e1ddbab2110)) +* compat with Python 3.14 ([aacb192](https://github.com/dedalus-labs/dedalus-sdk-python/commit/aacb192910f8bdd09625f098874cf54d3c0c0971)) +* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([bd1df12](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bd1df12d6c26ad35101bd7b181f33ee8f7fe75ce)) +* ensure streams are always closed ([e0e6406](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e0e6406dc5faeaae21286324d4d247e2706481e1)) +* import paths and tests ([70c967f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/70c967f97d9479307128d6215731a8296bf3fa18)) +* mcp auth types ([7eca858](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7eca858a6967eb789ef1282b28ec63971c2975dd)) +* reconcile generated and integrated SDK branches ([de73670](https://github.com/dedalus-labs/dedalus-sdk-python/commit/de736702cef6a807c464c69153e4cd878a847c47)) +* remove rootmodel hack ([6bae04e](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6bae04eacd8f4ccd87dfbaf57223147ff6f2896e)) +* runner tool calling mechanics ([a07f8eb](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a07f8ebd7d65d9a054bba7838443da90f396762d)) +* **runner:** import and dict issues ([fd500d6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd500d6c9ba7c503bfecbf7a3c53b8eaef149d59)) +* **runner:** narrow mcp types ([172b56b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/172b56b0ff518004f1bfb65fd0c28dca3215b026)) +* **runner:** use TypeAlias from typing_extensions for py3.9+ support ([0625b2c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0625b2c9cd7569140fb81848b9d77bbbfbe256b9)) +* **streaming:** add exception classes for structured output streaming ([18e861f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/18e861f8721b9624927b96288eaeb2c90bcb43ac)) +* **streaming:** correct stream type detection ([7b6576c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7b6576c23400bca2420e6782defa633b0f3dbff9)) +* **streaming:** use _post() in stream() to support Responses API aliases ([758c5c3](https://github.com/dedalus-labs/dedalus-sdk-python/commit/758c5c3e86b51e870bd6b742399b80e5b30f5847)) +* **tests:** update bug reporting parameters/paths ([3838ebe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3838ebe4db440a852c233cd2a96c2b7a4f0c4082)) +* tool call format and custom_auth validation ([b822aa2](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b822aa214cbf0f735c4480a156639888a80956b1)) +* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([624e2b6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/624e2b67c3b784d64548fb6a3ebde7283b593279)) +* **types:** remove manual DedalusModel ([e1ce236](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e1ce236b931b0715b9fa280ef329bfa451eb05c1)) +* use async_to_httpx_files in patch method ([056ef78](https://github.com/dedalus-labs/dedalus-sdk-python/commit/056ef7802bda00b3037af8d745776e4aa836ba8c)) +* use typealiastype to prevent recursive type issues ([0aeaf80](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0aeaf80d4b51a28a60dbb9f7527e46c5881bd837)) + + +### Chores + +* add missing docstrings ([52ded59](https://github.com/dedalus-labs/dedalus-sdk-python/commit/52ded5987a0cd141499157a3bf9804fe342e5494)) +* **api:** point local dev to 4010 port for prism ([ad0ec37](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ad0ec3766423c5e6374a96afe55bf415a9c2792c)) +* **api:** rename MCPToolExecution -> MCPToolResult ([973611a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/973611a9f411002a00f5b703b30d44c3b2de450a)) +* **auth:** add minor auth params ([c39bcfc](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c39bcfc6366411658cc12e81b5f473176a26fad3)) +* bump `httpx-aiohttp` version to 0.1.9 ([6b5f606](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6b5f60653d76bfe6b4a85d841f115d59c5eba976)) +* bump required `uv` version ([cb3f674](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb3f674dbc9f6392493de7984bcd5fee385adfa0)) +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([cb4d323](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb4d3232c845b60eeccd23efa06057c8408085f5)) +* **docs:** use environment variables for authentication in code snippets ([e4330c0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e4330c0db0f371c49f320ffb6f8238da0229f890)) +* **internal/tests:** avoid race condition with implicit client cleanup ([0854f1d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0854f1d8f52ad5d75c2da1781358f96543793c02)) +* **internal:** add `--fix` argument to lint script ([b25908b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b25908bef7007756e7596fda321ab986366f39dc)) +* **internal:** add missing files argument to base client ([f3a6008](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f3a60083b1a37c3bb798e8c07b97af5f04c16b0d)) +* **internal:** avoid using unstable Python versions in tests ([5905b55](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5905b553aaacf8ef4512d851d68b0e0eb7bc647c)) +* **internal:** codegen related update ([f8be312](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f8be3120a918cddd164f5ea185fb2da3058c8f9e)) +* **internal:** codegen related update ([d288b0b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d288b0b4f8098c0aab0d79d45f27fd7b7cd8d127)) +* **internal:** detect missing future annotations with ruff ([6909c09](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6909c09996be7fe019ec6737a18b7e330b325c4a)) +* **internal:** grammar fix (it's -> its) ([f0c5880](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f0c58800e495c0cd5c1f13a9799e8e2025154a1c)) +* **package:** drop Python 3.8 support ([ef5e794](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ef5e794d49c800706eeb693170ea4a3ac0245290)) +* remove custom code ([81f922b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/81f922b8eabc571abf4cfd1b87e08517b4564128)) +* **repo:** update contributing doc and mock test script ([fd52f5a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd52f5acd095279bd26d647ee3f93c2daf7977df)) +* tidy for regen ([4e9f631](https://github.com/dedalus-labs/dedalus-sdk-python/commit/4e9f6316e7a66c5902dadbbdec43664882a85168)) +* tidy protocol docstrings ([99c65f6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/99c65f6f3f89f1958a94f5aaa0daf9511aafbcd1)) +* tidying ([354f95b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/354f95b1efde6b3df27275b3b8a36510f28d1858)) +* update lockfile ([46f1379](https://github.com/dedalus-labs/dedalus-sdk-python/commit/46f13790651a2c284d50c414556fee925c7ff089)) + + +### Documentation + +* prominently feature MCP server setup in root SDK readmes ([2889d94](https://github.com/dedalus-labs/dedalus-sdk-python/commit/2889d94d916e466d673e55cf59a3aca7efec08ef)) + + +### Styles + +* enforce double quotes via ruff ([eda6f77](https://github.com/dedalus-labs/dedalus-sdk-python/commit/eda6f77db38c0a02aec66c81aebc30aab2e33436)) + + +### Refactors + +* **api:** types for mcp server serialization ([2f614ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/2f614ca5493c9d57807aea9105e77a66c4703130)) +* **api:** update auth types ([9b17190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b17190b28400652ccbdb3f60499a163168e0b80)) +* **api:** update types ([584fe2a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/584fe2aa544ce99955d1d0a712cb51267e06357d)) + +## 0.2.0 (2025-12-19) + +Full Changelog: [v0.2.0...v0.2.0](https://github.com/dedalus-labs/dedalus-sdk-python/compare/v0.2.0...v0.2.0) + +### Features + +* add image edits/variation and vision format support ([f8a8c84](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f8a8c84f3379d92619de56929d6ad3048989b18c)) +* **api:** add endpoints ([c10d7b5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c10d7b55a8ec6bb82556b3efe4db20c91959131d)) +* **api:** add streaming ([745c331](https://github.com/dedalus-labs/dedalus-sdk-python/commit/745c33166a671b79a978961d576064618cc80bcb)) +* **api:** add streaming configuration ([0172ad5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0172ad5175dd15650252a084f213b16c56b8befc)) +* **api:** api update ([280a595](https://github.com/dedalus-labs/dedalus-sdk-python/commit/280a595b3d3900625cfdf26be12027a88eff9618)) +* **api:** auto exec tools ([780162b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/780162b01d27703bb873488702ebede232791ed2)) +* **api:** config update for dedalus-ai/dev ([34e7a71](https://github.com/dedalus-labs/dedalus-sdk-python/commit/34e7a7167f6db94bb7c8c10a7c11746656aec5a6)) +* **api:** config update for dedalus-ai/dev ([197d11b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/197d11bf57da500e05cde2861146da9e3ec278f3)) +* **api:** convenient bug reporting ux ([5aa032f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5aa032f24a9fe44d23cfbf83f12fc79104529c8d)) +* **api:** image support ([ca28133](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ca281334db05ac2e939436050d1cf70ca5359ab4)) +* **api:** improve types ([62cf7e1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/62cf7e1a643cda58ee5112a1a8fc35253fe50f07)) +* **api:** manual updates ([9bb6d0d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9bb6d0d0433111d177410f7ff21d3de254d899a0)) +* **api:** manual updates ([9b2851a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b2851a6bdbf861c0db0b01aa3e7a8f5a45bfa77)) +* **api:** mcp server params ([bf78aad](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bf78aad94d159622460b3a9ecddb9c5d1e1c82bb)) +* **api:** messages param nullable ([e905235](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e9052357b7efa9d49b4f8b8d4c7dfc026d69414b)) +* **api:** response format ([660ac29](https://github.com/dedalus-labs/dedalus-sdk-python/commit/660ac2954efc08eaed5212da934203b5b80f522e)) +* **api:** revert streaming for now ([56d57d5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/56d57d5a19034eec13d5a98a86d133d36ac2830a)) +* **api:** schema compiler landed ([9aeb7a7](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9aeb7a78bfaa81c07e920268afcacbba4a4ff9c9)) +* **api:** standardize name casing with stainless initialism ([ba1e188](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ba1e188beb62f6def79720d7d2ec8e22853fadaf)) +* **api:** stream helper for pydantic ([c4ab8b0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c4ab8b0d911b92afe76de99143567e6898e9e95c)) +* **api:** streaming support for structured output ([48ddd0a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/48ddd0a996e99b65fb4635f276a5562ef567ed26)) +* **api:** update via SDK Studio ([9407b44](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9407b44fa8dbd4df7c18c36eab95a5573399810a)) +* **client:** support file upload requests ([caadecd](https://github.com/dedalus-labs/dedalus-sdk-python/commit/caadecdf5c75297819cd41fe3adcc5f7af3de772)) +* encryption ([d72d130](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d72d130279b78fb4f946199a003032001b9c162e)) +* flexible input params for .parse() ([b208fbe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b208fbed8300526b323ac7c935d6d50bb652f0d3)) +* structured outputs for tools ([b0434ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b0434ca32e43dc5ef254e3fecb5493a2d3896384)) + + +### Bug Fixes + +* **api:** add shared DedalusModel type ([8855a07](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8855a07e4ea638102e71a049e182891e76e3d34d)) +* **api:** add thought signature ([d2203b1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d2203b129e6b64bbae0b1966654723c2f1ca1159)) +* **api:** docstring truncation ([699f8b9](https://github.com/dedalus-labs/dedalus-sdk-python/commit/699f8b962472ddb4ff2514cc1e515f76ccae5c21)) +* **api:** hardened _compat types ([312b628](https://github.com/dedalus-labs/dedalus-sdk-python/commit/312b628b48d15aaae5b4d2765a75d2b6b830e318)) +* **api:** improve types ([e3a3293](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e3a32933f4d8a568502dc6896ec6ea4ef9054bf4)) +* **api:** mcp credential types ([3cdef11](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3cdef112c7f647bcdcca903be56cf89115d47c0a)) +* **api:** relocate parts of auth logic ([a0d8615](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a0d8615f237331f8b79012e9cb5991dc929711d1)) +* **api:** standardize to use automatic_tool_execution ([731f753](https://github.com/dedalus-labs/dedalus-sdk-python/commit/731f753421de4a90b775fff8bee210d3aaf29a21)) +* **api:** syntactical sugar for json types ([6908b05](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6908b05580ce26b04590170dd24ccef0196cb900)) +* **api:** syntactical sugar for json types ([#37](https://github.com/dedalus-labs/dedalus-sdk-python/issues/37)) ([8f29baf](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8f29baf09cc732459f2d14335e39caa0084513c9)) +* **api:** typed json objects ([db161b2](https://github.com/dedalus-labs/dedalus-sdk-python/commit/db161b27ca2608d08a915323ced9d76fab6263bb)) +* **api:** typed json objects ([6dbc75d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6dbc75dc331cd2542e19aa7d2cc530dd091d7e65)) +* **api:** update types/docstrings ([8c0b864](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8c0b864a31673bdd62166eaffcceb911166054d3)) +* **client:** close streams without requiring full consumption ([24c4190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/24c4190ccb71d2c369b3d79f5764be31f2e8ead7)) +* compat with Python 3.14 ([aacb192](https://github.com/dedalus-labs/dedalus-sdk-python/commit/aacb192910f8bdd09625f098874cf54d3c0c0971)) +* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([bd1df12](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bd1df12d6c26ad35101bd7b181f33ee8f7fe75ce)) +* ensure streams are always closed ([e0e6406](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e0e6406dc5faeaae21286324d4d247e2706481e1)) +* import paths and tests ([70c967f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/70c967f97d9479307128d6215731a8296bf3fa18)) +* mcp auth types ([7eca858](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7eca858a6967eb789ef1282b28ec63971c2975dd)) +* remove rootmodel hack ([6bae04e](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6bae04eacd8f4ccd87dfbaf57223147ff6f2896e)) +* runner tool calling mechanics ([a07f8eb](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a07f8ebd7d65d9a054bba7838443da90f396762d)) +* **runner:** import and dict issues ([fd500d6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd500d6c9ba7c503bfecbf7a3c53b8eaef149d59)) +* **runner:** narrow mcp types ([172b56b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/172b56b0ff518004f1bfb65fd0c28dca3215b026)) +* **runner:** use TypeAlias from typing_extensions for py3.9+ support ([0625b2c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0625b2c9cd7569140fb81848b9d77bbbfbe256b9)) +* **streaming:** correct stream type detection ([7b6576c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7b6576c23400bca2420e6782defa633b0f3dbff9)) +* **tests:** update bug reporting parameters/paths ([3838ebe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3838ebe4db440a852c233cd2a96c2b7a4f0c4082)) +* tool call format and custom_auth validation ([b822aa2](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b822aa214cbf0f735c4480a156639888a80956b1)) +* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([624e2b6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/624e2b67c3b784d64548fb6a3ebde7283b593279)) +* **types:** remove manual DedalusModel ([e1ce236](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e1ce236b931b0715b9fa280ef329bfa451eb05c1)) +* use async_to_httpx_files in patch method ([056ef78](https://github.com/dedalus-labs/dedalus-sdk-python/commit/056ef7802bda00b3037af8d745776e4aa836ba8c)) +* use typealiastype to prevent recursive type issues ([0aeaf80](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0aeaf80d4b51a28a60dbb9f7527e46c5881bd837)) + + +### Chores + +* add missing docstrings ([52ded59](https://github.com/dedalus-labs/dedalus-sdk-python/commit/52ded5987a0cd141499157a3bf9804fe342e5494)) +* **api:** point local dev to 4010 port for prism ([ad0ec37](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ad0ec3766423c5e6374a96afe55bf415a9c2792c)) +* **api:** rename MCPToolExecution -> MCPToolResult ([973611a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/973611a9f411002a00f5b703b30d44c3b2de450a)) +* **auth:** add minor auth params ([c39bcfc](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c39bcfc6366411658cc12e81b5f473176a26fad3)) +* bump `httpx-aiohttp` version to 0.1.9 ([6b5f606](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6b5f60653d76bfe6b4a85d841f115d59c5eba976)) +* bump required `uv` version ([cb3f674](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb3f674dbc9f6392493de7984bcd5fee385adfa0)) +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([cb4d323](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb4d3232c845b60eeccd23efa06057c8408085f5)) +* **docs:** use environment variables for authentication in code snippets ([e4330c0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e4330c0db0f371c49f320ffb6f8238da0229f890)) +* **internal/tests:** avoid race condition with implicit client cleanup ([0854f1d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0854f1d8f52ad5d75c2da1781358f96543793c02)) +* **internal:** add `--fix` argument to lint script ([b25908b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b25908bef7007756e7596fda321ab986366f39dc)) +* **internal:** add missing files argument to base client ([f3a6008](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f3a60083b1a37c3bb798e8c07b97af5f04c16b0d)) +* **internal:** avoid using unstable Python versions in tests ([5905b55](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5905b553aaacf8ef4512d851d68b0e0eb7bc647c)) +* **internal:** codegen related update ([d288b0b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d288b0b4f8098c0aab0d79d45f27fd7b7cd8d127)) +* **internal:** detect missing future annotations with ruff ([6909c09](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6909c09996be7fe019ec6737a18b7e330b325c4a)) +* **internal:** grammar fix (it's -> its) ([f0c5880](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f0c58800e495c0cd5c1f13a9799e8e2025154a1c)) +* **package:** drop Python 3.8 support ([ef5e794](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ef5e794d49c800706eeb693170ea4a3ac0245290)) +* remove custom code ([81f922b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/81f922b8eabc571abf4cfd1b87e08517b4564128)) +* **repo:** update contributing doc and mock test script ([fd52f5a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd52f5acd095279bd26d647ee3f93c2daf7977df)) +* tidy protocol docstrings ([99c65f6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/99c65f6f3f89f1958a94f5aaa0daf9511aafbcd1)) +* tidying ([354f95b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/354f95b1efde6b3df27275b3b8a36510f28d1858)) +* update lockfile ([46f1379](https://github.com/dedalus-labs/dedalus-sdk-python/commit/46f13790651a2c284d50c414556fee925c7ff089)) + + +### Refactors + +* **api:** types for mcp server serialization ([2f614ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/2f614ca5493c9d57807aea9105e77a66c4703130)) +* **api:** update auth types ([9b17190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b17190b28400652ccbdb3f60499a163168e0b80)) +* **api:** update types ([584fe2a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/584fe2aa544ce99955d1d0a712cb51267e06357d)) + +## 0.2.0 (2025-12-19) + +Full Changelog: [v0.2.0...v0.2.0](https://github.com/dedalus-labs/dedalus-sdk-python/compare/v0.2.0...v0.2.0) + +### Features + +* add image edits/variation and vision format support ([f8a8c84](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f8a8c84f3379d92619de56929d6ad3048989b18c)) +* **api:** add endpoints ([c10d7b5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c10d7b55a8ec6bb82556b3efe4db20c91959131d)) +* **api:** add streaming ([745c331](https://github.com/dedalus-labs/dedalus-sdk-python/commit/745c33166a671b79a978961d576064618cc80bcb)) +* **api:** add streaming configuration ([0172ad5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0172ad5175dd15650252a084f213b16c56b8befc)) +* **api:** api update ([280a595](https://github.com/dedalus-labs/dedalus-sdk-python/commit/280a595b3d3900625cfdf26be12027a88eff9618)) +* **api:** auto exec tools ([780162b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/780162b01d27703bb873488702ebede232791ed2)) +* **api:** config update for dedalus-ai/dev ([34e7a71](https://github.com/dedalus-labs/dedalus-sdk-python/commit/34e7a7167f6db94bb7c8c10a7c11746656aec5a6)) +* **api:** config update for dedalus-ai/dev ([197d11b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/197d11bf57da500e05cde2861146da9e3ec278f3)) +* **api:** convenient bug reporting ux ([5aa032f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5aa032f24a9fe44d23cfbf83f12fc79104529c8d)) +* **api:** image support ([ca28133](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ca281334db05ac2e939436050d1cf70ca5359ab4)) +* **api:** improve types ([62cf7e1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/62cf7e1a643cda58ee5112a1a8fc35253fe50f07)) +* **api:** manual updates ([9bb6d0d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9bb6d0d0433111d177410f7ff21d3de254d899a0)) +* **api:** manual updates ([9b2851a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b2851a6bdbf861c0db0b01aa3e7a8f5a45bfa77)) +* **api:** mcp server params ([bf78aad](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bf78aad94d159622460b3a9ecddb9c5d1e1c82bb)) +* **api:** messages param nullable ([e905235](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e9052357b7efa9d49b4f8b8d4c7dfc026d69414b)) +* **api:** response format ([660ac29](https://github.com/dedalus-labs/dedalus-sdk-python/commit/660ac2954efc08eaed5212da934203b5b80f522e)) +* **api:** revert streaming for now ([56d57d5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/56d57d5a19034eec13d5a98a86d133d36ac2830a)) +* **api:** schema compiler landed ([9aeb7a7](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9aeb7a78bfaa81c07e920268afcacbba4a4ff9c9)) +* **api:** standardize name casing with stainless initialism ([ba1e188](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ba1e188beb62f6def79720d7d2ec8e22853fadaf)) +* **api:** stream helper for pydantic ([c4ab8b0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c4ab8b0d911b92afe76de99143567e6898e9e95c)) +* **api:** streaming support for structured output ([48ddd0a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/48ddd0a996e99b65fb4635f276a5562ef567ed26)) +* **api:** update via SDK Studio ([9407b44](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9407b44fa8dbd4df7c18c36eab95a5573399810a)) +* **client:** support file upload requests ([caadecd](https://github.com/dedalus-labs/dedalus-sdk-python/commit/caadecdf5c75297819cd41fe3adcc5f7af3de772)) +* encryption ([d72d130](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d72d130279b78fb4f946199a003032001b9c162e)) +* flexible input params for .parse() ([b208fbe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b208fbed8300526b323ac7c935d6d50bb652f0d3)) +* structured outputs for tools ([b0434ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b0434ca32e43dc5ef254e3fecb5493a2d3896384)) + + +### Bug Fixes + +* **api:** add shared DedalusModel type ([8855a07](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8855a07e4ea638102e71a049e182891e76e3d34d)) +* **api:** add thought signature ([d2203b1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d2203b129e6b64bbae0b1966654723c2f1ca1159)) +* **api:** docstring truncation ([699f8b9](https://github.com/dedalus-labs/dedalus-sdk-python/commit/699f8b962472ddb4ff2514cc1e515f76ccae5c21)) +* **api:** hardened _compat types ([312b628](https://github.com/dedalus-labs/dedalus-sdk-python/commit/312b628b48d15aaae5b4d2765a75d2b6b830e318)) +* **api:** improve types ([e3a3293](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e3a32933f4d8a568502dc6896ec6ea4ef9054bf4)) +* **api:** mcp credential types ([3cdef11](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3cdef112c7f647bcdcca903be56cf89115d47c0a)) +* **api:** relocate parts of auth logic ([a0d8615](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a0d8615f237331f8b79012e9cb5991dc929711d1)) +* **api:** standardize to use automatic_tool_execution ([731f753](https://github.com/dedalus-labs/dedalus-sdk-python/commit/731f753421de4a90b775fff8bee210d3aaf29a21)) +* **api:** syntactical sugar for json types ([6908b05](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6908b05580ce26b04590170dd24ccef0196cb900)) +* **api:** syntactical sugar for json types ([#37](https://github.com/dedalus-labs/dedalus-sdk-python/issues/37)) ([8f29baf](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8f29baf09cc732459f2d14335e39caa0084513c9)) +* **api:** typed json objects ([db161b2](https://github.com/dedalus-labs/dedalus-sdk-python/commit/db161b27ca2608d08a915323ced9d76fab6263bb)) +* **api:** typed json objects ([6dbc75d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6dbc75dc331cd2542e19aa7d2cc530dd091d7e65)) +* **api:** update types/docstrings ([8c0b864](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8c0b864a31673bdd62166eaffcceb911166054d3)) +* **client:** close streams without requiring full consumption ([24c4190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/24c4190ccb71d2c369b3d79f5764be31f2e8ead7)) +* compat with Python 3.14 ([aacb192](https://github.com/dedalus-labs/dedalus-sdk-python/commit/aacb192910f8bdd09625f098874cf54d3c0c0971)) +* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([bd1df12](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bd1df12d6c26ad35101bd7b181f33ee8f7fe75ce)) +* ensure streams are always closed ([e0e6406](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e0e6406dc5faeaae21286324d4d247e2706481e1)) +* import paths and tests ([70c967f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/70c967f97d9479307128d6215731a8296bf3fa18)) +* mcp auth types ([7eca858](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7eca858a6967eb789ef1282b28ec63971c2975dd)) +* runner tool calling mechanics ([a07f8eb](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a07f8ebd7d65d9a054bba7838443da90f396762d)) +* **runner:** import and dict issues ([fd500d6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd500d6c9ba7c503bfecbf7a3c53b8eaef149d59)) +* **runner:** narrow mcp types ([172b56b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/172b56b0ff518004f1bfb65fd0c28dca3215b026)) +* **runner:** use TypeAlias from typing_extensions for py3.9+ support ([0625b2c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0625b2c9cd7569140fb81848b9d77bbbfbe256b9)) +* **streaming:** correct stream type detection ([7b6576c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7b6576c23400bca2420e6782defa633b0f3dbff9)) +* **tests:** update bug reporting parameters/paths ([3838ebe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3838ebe4db440a852c233cd2a96c2b7a4f0c4082)) +* tool call format and custom_auth validation ([b822aa2](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b822aa214cbf0f735c4480a156639888a80956b1)) +* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([624e2b6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/624e2b67c3b784d64548fb6a3ebde7283b593279)) +* **types:** remove manual DedalusModel ([e1ce236](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e1ce236b931b0715b9fa280ef329bfa451eb05c1)) +* use async_to_httpx_files in patch method ([056ef78](https://github.com/dedalus-labs/dedalus-sdk-python/commit/056ef7802bda00b3037af8d745776e4aa836ba8c)) + + +### Chores + +* add missing docstrings ([52ded59](https://github.com/dedalus-labs/dedalus-sdk-python/commit/52ded5987a0cd141499157a3bf9804fe342e5494)) +* **api:** point local dev to 4010 port for prism ([ad0ec37](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ad0ec3766423c5e6374a96afe55bf415a9c2792c)) +* **api:** rename MCPToolExecution -> MCPToolResult ([973611a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/973611a9f411002a00f5b703b30d44c3b2de450a)) +* **auth:** add minor auth params ([c39bcfc](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c39bcfc6366411658cc12e81b5f473176a26fad3)) +* bump `httpx-aiohttp` version to 0.1.9 ([6b5f606](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6b5f60653d76bfe6b4a85d841f115d59c5eba976)) +* bump required `uv` version ([cb3f674](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb3f674dbc9f6392493de7984bcd5fee385adfa0)) +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([cb4d323](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb4d3232c845b60eeccd23efa06057c8408085f5)) +* **docs:** use environment variables for authentication in code snippets ([e4330c0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e4330c0db0f371c49f320ffb6f8238da0229f890)) +* **internal/tests:** avoid race condition with implicit client cleanup ([0854f1d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0854f1d8f52ad5d75c2da1781358f96543793c02)) +* **internal:** add `--fix` argument to lint script ([b25908b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b25908bef7007756e7596fda321ab986366f39dc)) +* **internal:** add missing files argument to base client ([f3a6008](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f3a60083b1a37c3bb798e8c07b97af5f04c16b0d)) +* **internal:** avoid using unstable Python versions in tests ([5905b55](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5905b553aaacf8ef4512d851d68b0e0eb7bc647c)) +* **internal:** codegen related update ([d288b0b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d288b0b4f8098c0aab0d79d45f27fd7b7cd8d127)) +* **internal:** detect missing future annotations with ruff ([6909c09](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6909c09996be7fe019ec6737a18b7e330b325c4a)) +* **internal:** grammar fix (it's -> its) ([f0c5880](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f0c58800e495c0cd5c1f13a9799e8e2025154a1c)) +* **package:** drop Python 3.8 support ([ef5e794](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ef5e794d49c800706eeb693170ea4a3ac0245290)) +* remove custom code ([81f922b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/81f922b8eabc571abf4cfd1b87e08517b4564128)) +* **repo:** update contributing doc and mock test script ([fd52f5a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd52f5acd095279bd26d647ee3f93c2daf7977df)) +* tidy protocol docstrings ([99c65f6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/99c65f6f3f89f1958a94f5aaa0daf9511aafbcd1)) +* tidying ([354f95b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/354f95b1efde6b3df27275b3b8a36510f28d1858)) +* update lockfile ([46f1379](https://github.com/dedalus-labs/dedalus-sdk-python/commit/46f13790651a2c284d50c414556fee925c7ff089)) + + +### Refactors + +* **api:** types for mcp server serialization ([2f614ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/2f614ca5493c9d57807aea9105e77a66c4703130)) +* **api:** update auth types ([9b17190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b17190b28400652ccbdb3f60499a163168e0b80)) +* **api:** update types ([584fe2a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/584fe2aa544ce99955d1d0a712cb51267e06357d)) + +## 0.2.0 (2025-12-18) + +Full Changelog: [v0.2.0...v0.2.0](https://github.com/dedalus-labs/dedalus-sdk-python/compare/v0.2.0...v0.2.0) + +### Features + +* add image edits/variation and vision format support ([f8a8c84](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f8a8c84f3379d92619de56929d6ad3048989b18c)) +* **api:** add endpoints ([c10d7b5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c10d7b55a8ec6bb82556b3efe4db20c91959131d)) +* **api:** add streaming ([745c331](https://github.com/dedalus-labs/dedalus-sdk-python/commit/745c33166a671b79a978961d576064618cc80bcb)) +* **api:** add streaming configuration ([0172ad5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0172ad5175dd15650252a084f213b16c56b8befc)) +* **api:** api update ([280a595](https://github.com/dedalus-labs/dedalus-sdk-python/commit/280a595b3d3900625cfdf26be12027a88eff9618)) +* **api:** auto exec tools ([780162b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/780162b01d27703bb873488702ebede232791ed2)) +* **api:** config update for dedalus-ai/dev ([34e7a71](https://github.com/dedalus-labs/dedalus-sdk-python/commit/34e7a7167f6db94bb7c8c10a7c11746656aec5a6)) +* **api:** config update for dedalus-ai/dev ([197d11b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/197d11bf57da500e05cde2861146da9e3ec278f3)) +* **api:** convenient bug reporting ux ([5aa032f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5aa032f24a9fe44d23cfbf83f12fc79104529c8d)) +* **api:** image support ([ca28133](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ca281334db05ac2e939436050d1cf70ca5359ab4)) +* **api:** improve types ([62cf7e1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/62cf7e1a643cda58ee5112a1a8fc35253fe50f07)) +* **api:** manual updates ([9bb6d0d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9bb6d0d0433111d177410f7ff21d3de254d899a0)) +* **api:** manual updates ([9b2851a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b2851a6bdbf861c0db0b01aa3e7a8f5a45bfa77)) +* **api:** mcp server params ([bf78aad](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bf78aad94d159622460b3a9ecddb9c5d1e1c82bb)) +* **api:** messages param nullable ([e905235](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e9052357b7efa9d49b4f8b8d4c7dfc026d69414b)) +* **api:** response format ([660ac29](https://github.com/dedalus-labs/dedalus-sdk-python/commit/660ac2954efc08eaed5212da934203b5b80f522e)) +* **api:** revert streaming for now ([56d57d5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/56d57d5a19034eec13d5a98a86d133d36ac2830a)) +* **api:** schema compiler landed ([9aeb7a7](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9aeb7a78bfaa81c07e920268afcacbba4a4ff9c9)) +* **api:** standardize name casing with stainless initialism ([ba1e188](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ba1e188beb62f6def79720d7d2ec8e22853fadaf)) +* **api:** stream helper for pydantic ([c4ab8b0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c4ab8b0d911b92afe76de99143567e6898e9e95c)) +* **api:** streaming support for structured output ([48ddd0a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/48ddd0a996e99b65fb4635f276a5562ef567ed26)) +* **api:** update via SDK Studio ([9407b44](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9407b44fa8dbd4df7c18c36eab95a5573399810a)) +* **client:** support file upload requests ([caadecd](https://github.com/dedalus-labs/dedalus-sdk-python/commit/caadecdf5c75297819cd41fe3adcc5f7af3de772)) +* encryption ([d72d130](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d72d130279b78fb4f946199a003032001b9c162e)) +* flexible input params for .parse() ([b208fbe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b208fbed8300526b323ac7c935d6d50bb652f0d3)) +* structured outputs for tools ([b0434ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b0434ca32e43dc5ef254e3fecb5493a2d3896384)) + + +### Bug Fixes + +* **api:** add shared DedalusModel type ([8855a07](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8855a07e4ea638102e71a049e182891e76e3d34d)) +* **api:** add thought signature ([d2203b1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d2203b129e6b64bbae0b1966654723c2f1ca1159)) +* **api:** docstring truncation ([699f8b9](https://github.com/dedalus-labs/dedalus-sdk-python/commit/699f8b962472ddb4ff2514cc1e515f76ccae5c21)) +* **api:** hardened _compat types ([312b628](https://github.com/dedalus-labs/dedalus-sdk-python/commit/312b628b48d15aaae5b4d2765a75d2b6b830e318)) +* **api:** mcp credential types ([3cdef11](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3cdef112c7f647bcdcca903be56cf89115d47c0a)) +* **api:** relocate parts of auth logic ([a0d8615](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a0d8615f237331f8b79012e9cb5991dc929711d1)) +* **api:** standardize to use automatic_tool_execution ([731f753](https://github.com/dedalus-labs/dedalus-sdk-python/commit/731f753421de4a90b775fff8bee210d3aaf29a21)) +* **api:** typed json objects ([db161b2](https://github.com/dedalus-labs/dedalus-sdk-python/commit/db161b27ca2608d08a915323ced9d76fab6263bb)) +* **api:** typed json objects ([6dbc75d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6dbc75dc331cd2542e19aa7d2cc530dd091d7e65)) +* **client:** close streams without requiring full consumption ([24c4190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/24c4190ccb71d2c369b3d79f5764be31f2e8ead7)) +* compat with Python 3.14 ([aacb192](https://github.com/dedalus-labs/dedalus-sdk-python/commit/aacb192910f8bdd09625f098874cf54d3c0c0971)) +* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([bd1df12](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bd1df12d6c26ad35101bd7b181f33ee8f7fe75ce)) +* ensure streams are always closed ([e0e6406](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e0e6406dc5faeaae21286324d4d247e2706481e1)) +* import paths and tests ([70c967f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/70c967f97d9479307128d6215731a8296bf3fa18)) +* mcp auth types ([7eca858](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7eca858a6967eb789ef1282b28ec63971c2975dd)) +* runner tool calling mechanics ([a07f8eb](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a07f8ebd7d65d9a054bba7838443da90f396762d)) +* **runner:** import and dict issues ([fd500d6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd500d6c9ba7c503bfecbf7a3c53b8eaef149d59)) +* **runner:** narrow mcp types ([172b56b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/172b56b0ff518004f1bfb65fd0c28dca3215b026)) +* **runner:** use TypeAlias from typing_extensions for py3.9+ support ([0625b2c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0625b2c9cd7569140fb81848b9d77bbbfbe256b9)) +* **streaming:** correct stream type detection ([7b6576c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7b6576c23400bca2420e6782defa633b0f3dbff9)) +* **tests:** update bug reporting parameters/paths ([3838ebe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3838ebe4db440a852c233cd2a96c2b7a4f0c4082)) +* tool call format and custom_auth validation ([b822aa2](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b822aa214cbf0f735c4480a156639888a80956b1)) +* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([624e2b6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/624e2b67c3b784d64548fb6a3ebde7283b593279)) +* **types:** remove manual DedalusModel ([e1ce236](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e1ce236b931b0715b9fa280ef329bfa451eb05c1)) +* use async_to_httpx_files in patch method ([056ef78](https://github.com/dedalus-labs/dedalus-sdk-python/commit/056ef7802bda00b3037af8d745776e4aa836ba8c)) + + +### Chores + +* add missing docstrings ([52ded59](https://github.com/dedalus-labs/dedalus-sdk-python/commit/52ded5987a0cd141499157a3bf9804fe342e5494)) +* **api:** point local dev to 4010 port for prism ([ad0ec37](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ad0ec3766423c5e6374a96afe55bf415a9c2792c)) +* **api:** rename MCPToolExecution -> MCPToolResult ([973611a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/973611a9f411002a00f5b703b30d44c3b2de450a)) +* **auth:** add minor auth params ([c39bcfc](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c39bcfc6366411658cc12e81b5f473176a26fad3)) +* bump `httpx-aiohttp` version to 0.1.9 ([6b5f606](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6b5f60653d76bfe6b4a85d841f115d59c5eba976)) +* bump required `uv` version ([cb3f674](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb3f674dbc9f6392493de7984bcd5fee385adfa0)) +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([cb4d323](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb4d3232c845b60eeccd23efa06057c8408085f5)) +* **docs:** use environment variables for authentication in code snippets ([e4330c0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e4330c0db0f371c49f320ffb6f8238da0229f890)) +* **internal/tests:** avoid race condition with implicit client cleanup ([0854f1d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0854f1d8f52ad5d75c2da1781358f96543793c02)) +* **internal:** add missing files argument to base client ([f3a6008](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f3a60083b1a37c3bb798e8c07b97af5f04c16b0d)) +* **internal:** avoid using unstable Python versions in tests ([5905b55](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5905b553aaacf8ef4512d851d68b0e0eb7bc647c)) +* **internal:** codegen related update ([d288b0b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d288b0b4f8098c0aab0d79d45f27fd7b7cd8d127)) +* **internal:** detect missing future annotations with ruff ([6909c09](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6909c09996be7fe019ec6737a18b7e330b325c4a)) +* **internal:** grammar fix (it's -> its) ([f0c5880](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f0c58800e495c0cd5c1f13a9799e8e2025154a1c)) +* **package:** drop Python 3.8 support ([ef5e794](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ef5e794d49c800706eeb693170ea4a3ac0245290)) +* remove custom code ([81f922b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/81f922b8eabc571abf4cfd1b87e08517b4564128)) +* **repo:** update contributing doc and mock test script ([fd52f5a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd52f5acd095279bd26d647ee3f93c2daf7977df)) +* tidy protocol docstrings ([99c65f6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/99c65f6f3f89f1958a94f5aaa0daf9511aafbcd1)) +* tidying ([354f95b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/354f95b1efde6b3df27275b3b8a36510f28d1858)) +* update lockfile ([46f1379](https://github.com/dedalus-labs/dedalus-sdk-python/commit/46f13790651a2c284d50c414556fee925c7ff089)) + + +### Refactors + +* **api:** types for mcp server serialization ([2f614ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/2f614ca5493c9d57807aea9105e77a66c4703130)) +* **api:** update auth types ([9b17190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b17190b28400652ccbdb3f60499a163168e0b80)) +* **api:** update types ([584fe2a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/584fe2aa544ce99955d1d0a712cb51267e06357d)) + +## 0.2.0 (2025-12-12) + +Full Changelog: [v0.2.0...v0.2.0](https://github.com/dedalus-labs/dedalus-sdk-python/compare/v0.2.0...v0.2.0) + +### Features + +* add image edits/variation and vision format support ([f8a8c84](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f8a8c84f3379d92619de56929d6ad3048989b18c)) +* **api:** add endpoints ([c10d7b5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c10d7b55a8ec6bb82556b3efe4db20c91959131d)) +* **api:** add streaming ([745c331](https://github.com/dedalus-labs/dedalus-sdk-python/commit/745c33166a671b79a978961d576064618cc80bcb)) +* **api:** add streaming configuration ([0172ad5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0172ad5175dd15650252a084f213b16c56b8befc)) +* **api:** api update ([280a595](https://github.com/dedalus-labs/dedalus-sdk-python/commit/280a595b3d3900625cfdf26be12027a88eff9618)) +* **api:** auto exec tools ([780162b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/780162b01d27703bb873488702ebede232791ed2)) +* **api:** config update for dedalus-ai/dev ([34e7a71](https://github.com/dedalus-labs/dedalus-sdk-python/commit/34e7a7167f6db94bb7c8c10a7c11746656aec5a6)) +* **api:** config update for dedalus-ai/dev ([197d11b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/197d11bf57da500e05cde2861146da9e3ec278f3)) +* **api:** convenient bug reporting ux ([5aa032f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5aa032f24a9fe44d23cfbf83f12fc79104529c8d)) +* **api:** image support ([ca28133](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ca281334db05ac2e939436050d1cf70ca5359ab4)) +* **api:** improve types ([62cf7e1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/62cf7e1a643cda58ee5112a1a8fc35253fe50f07)) +* **api:** manual updates ([9bb6d0d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9bb6d0d0433111d177410f7ff21d3de254d899a0)) +* **api:** manual updates ([9b2851a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b2851a6bdbf861c0db0b01aa3e7a8f5a45bfa77)) +* **api:** mcp server params ([bf78aad](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bf78aad94d159622460b3a9ecddb9c5d1e1c82bb)) +* **api:** messages param nullable ([e905235](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e9052357b7efa9d49b4f8b8d4c7dfc026d69414b)) +* **api:** response format ([660ac29](https://github.com/dedalus-labs/dedalus-sdk-python/commit/660ac2954efc08eaed5212da934203b5b80f522e)) +* **api:** revert streaming for now ([56d57d5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/56d57d5a19034eec13d5a98a86d133d36ac2830a)) +* **api:** schema compiler landed ([9aeb7a7](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9aeb7a78bfaa81c07e920268afcacbba4a4ff9c9)) +* **api:** standardize name casing with stainless initialism ([ba1e188](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ba1e188beb62f6def79720d7d2ec8e22853fadaf)) +* **api:** stream helper for pydantic ([c4ab8b0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c4ab8b0d911b92afe76de99143567e6898e9e95c)) +* **api:** streaming support for structured output ([48ddd0a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/48ddd0a996e99b65fb4635f276a5562ef567ed26)) +* **api:** update via SDK Studio ([9407b44](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9407b44fa8dbd4df7c18c36eab95a5573399810a)) +* **client:** support file upload requests ([caadecd](https://github.com/dedalus-labs/dedalus-sdk-python/commit/caadecdf5c75297819cd41fe3adcc5f7af3de772)) +* encryption ([d72d130](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d72d130279b78fb4f946199a003032001b9c162e)) +* flexible input params for .parse() ([b208fbe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b208fbed8300526b323ac7c935d6d50bb652f0d3)) +* structured outputs for tools ([b0434ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b0434ca32e43dc5ef254e3fecb5493a2d3896384)) + + +### Bug Fixes + +* **api:** add shared DedalusModel type ([8855a07](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8855a07e4ea638102e71a049e182891e76e3d34d)) +* **api:** add thought signature ([d2203b1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d2203b129e6b64bbae0b1966654723c2f1ca1159)) +* **api:** hardened _compat types ([312b628](https://github.com/dedalus-labs/dedalus-sdk-python/commit/312b628b48d15aaae5b4d2765a75d2b6b830e318)) +* **api:** mcp credential types ([3cdef11](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3cdef112c7f647bcdcca903be56cf89115d47c0a)) +* **api:** relocate parts of auth logic ([a0d8615](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a0d8615f237331f8b79012e9cb5991dc929711d1)) +* **api:** standardize to use automatic_tool_execution ([731f753](https://github.com/dedalus-labs/dedalus-sdk-python/commit/731f753421de4a90b775fff8bee210d3aaf29a21)) +* **client:** close streams without requiring full consumption ([24c4190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/24c4190ccb71d2c369b3d79f5764be31f2e8ead7)) +* compat with Python 3.14 ([aacb192](https://github.com/dedalus-labs/dedalus-sdk-python/commit/aacb192910f8bdd09625f098874cf54d3c0c0971)) +* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([bd1df12](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bd1df12d6c26ad35101bd7b181f33ee8f7fe75ce)) +* ensure streams are always closed ([e0e6406](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e0e6406dc5faeaae21286324d4d247e2706481e1)) +* import paths and tests ([70c967f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/70c967f97d9479307128d6215731a8296bf3fa18)) +* runner tool calling mechanics ([a07f8eb](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a07f8ebd7d65d9a054bba7838443da90f396762d)) +* **runner:** import and dict issues ([fd500d6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd500d6c9ba7c503bfecbf7a3c53b8eaef149d59)) +* **runner:** narrow mcp types ([172b56b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/172b56b0ff518004f1bfb65fd0c28dca3215b026)) +* **runner:** use TypeAlias from typing_extensions for py3.9+ support ([0625b2c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0625b2c9cd7569140fb81848b9d77bbbfbe256b9)) +* **streaming:** correct stream type detection ([7b6576c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7b6576c23400bca2420e6782defa633b0f3dbff9)) +* **tests:** update bug reporting parameters/paths ([3838ebe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3838ebe4db440a852c233cd2a96c2b7a4f0c4082)) +* tool call format and custom_auth validation ([b822aa2](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b822aa214cbf0f735c4480a156639888a80956b1)) +* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([624e2b6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/624e2b67c3b784d64548fb6a3ebde7283b593279)) +* **types:** remove manual DedalusModel ([e1ce236](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e1ce236b931b0715b9fa280ef329bfa451eb05c1)) + + +### Chores + +* add missing docstrings ([52ded59](https://github.com/dedalus-labs/dedalus-sdk-python/commit/52ded5987a0cd141499157a3bf9804fe342e5494)) +* **api:** point local dev to 4010 port for prism ([ad0ec37](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ad0ec3766423c5e6374a96afe55bf415a9c2792c)) +* **auth:** add minor auth params ([c39bcfc](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c39bcfc6366411658cc12e81b5f473176a26fad3)) +* bump `httpx-aiohttp` version to 0.1.9 ([6b5f606](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6b5f60653d76bfe6b4a85d841f115d59c5eba976)) +* bump required `uv` version ([cb3f674](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb3f674dbc9f6392493de7984bcd5fee385adfa0)) +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([cb4d323](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb4d3232c845b60eeccd23efa06057c8408085f5)) +* **docs:** use environment variables for authentication in code snippets ([e4330c0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e4330c0db0f371c49f320ffb6f8238da0229f890)) +* **internal/tests:** avoid race condition with implicit client cleanup ([0854f1d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0854f1d8f52ad5d75c2da1781358f96543793c02)) +* **internal:** avoid using unstable Python versions in tests ([5905b55](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5905b553aaacf8ef4512d851d68b0e0eb7bc647c)) +* **internal:** codegen related update ([d288b0b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d288b0b4f8098c0aab0d79d45f27fd7b7cd8d127)) +* **internal:** detect missing future annotations with ruff ([6909c09](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6909c09996be7fe019ec6737a18b7e330b325c4a)) +* **internal:** grammar fix (it's -> its) ([f0c5880](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f0c58800e495c0cd5c1f13a9799e8e2025154a1c)) +* **package:** drop Python 3.8 support ([ef5e794](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ef5e794d49c800706eeb693170ea4a3ac0245290)) +* remove custom code ([81f922b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/81f922b8eabc571abf4cfd1b87e08517b4564128)) +* **repo:** update contributing doc and mock test script ([fd52f5a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd52f5acd095279bd26d647ee3f93c2daf7977df)) +* tidy protocol docstrings ([99c65f6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/99c65f6f3f89f1958a94f5aaa0daf9511aafbcd1)) +* tidying ([354f95b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/354f95b1efde6b3df27275b3b8a36510f28d1858)) +* update lockfile ([46f1379](https://github.com/dedalus-labs/dedalus-sdk-python/commit/46f13790651a2c284d50c414556fee925c7ff089)) + + +### Refactors + +* **api:** types for mcp server serialization ([2f614ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/2f614ca5493c9d57807aea9105e77a66c4703130)) +* **api:** update auth types ([9b17190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b17190b28400652ccbdb3f60499a163168e0b80)) +* **api:** update types ([584fe2a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/584fe2aa544ce99955d1d0a712cb51267e06357d)) + +## 0.2.0 (2025-12-09) + +Full Changelog: [v0.2.0...v0.2.0](https://github.com/dedalus-labs/dedalus-sdk-python/compare/v0.2.0...v0.2.0) + +### Features + +* add image edits/variation and vision format support ([f8a8c84](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f8a8c84f3379d92619de56929d6ad3048989b18c)) +* **api:** add endpoints ([c10d7b5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c10d7b55a8ec6bb82556b3efe4db20c91959131d)) +* **api:** add streaming ([745c331](https://github.com/dedalus-labs/dedalus-sdk-python/commit/745c33166a671b79a978961d576064618cc80bcb)) +* **api:** add streaming configuration ([0172ad5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0172ad5175dd15650252a084f213b16c56b8befc)) +* **api:** api update ([280a595](https://github.com/dedalus-labs/dedalus-sdk-python/commit/280a595b3d3900625cfdf26be12027a88eff9618)) +* **api:** auto exec tools ([780162b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/780162b01d27703bb873488702ebede232791ed2)) +* **api:** config update for dedalus-ai/dev ([34e7a71](https://github.com/dedalus-labs/dedalus-sdk-python/commit/34e7a7167f6db94bb7c8c10a7c11746656aec5a6)) +* **api:** config update for dedalus-ai/dev ([197d11b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/197d11bf57da500e05cde2861146da9e3ec278f3)) +* **api:** convenient bug reporting ux ([5aa032f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5aa032f24a9fe44d23cfbf83f12fc79104529c8d)) +* **api:** image support ([ca28133](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ca281334db05ac2e939436050d1cf70ca5359ab4)) +* **api:** improve types ([62cf7e1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/62cf7e1a643cda58ee5112a1a8fc35253fe50f07)) +* **api:** manual updates ([9bb6d0d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9bb6d0d0433111d177410f7ff21d3de254d899a0)) +* **api:** manual updates ([9b2851a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b2851a6bdbf861c0db0b01aa3e7a8f5a45bfa77)) +* **api:** mcp server params ([bf78aad](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bf78aad94d159622460b3a9ecddb9c5d1e1c82bb)) +* **api:** messages param nullable ([e905235](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e9052357b7efa9d49b4f8b8d4c7dfc026d69414b)) +* **api:** response format ([660ac29](https://github.com/dedalus-labs/dedalus-sdk-python/commit/660ac2954efc08eaed5212da934203b5b80f522e)) +* **api:** revert streaming for now ([56d57d5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/56d57d5a19034eec13d5a98a86d133d36ac2830a)) +* **api:** schema compiler landed ([9aeb7a7](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9aeb7a78bfaa81c07e920268afcacbba4a4ff9c9)) +* **api:** standardize name casing with stainless initialism ([ba1e188](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ba1e188beb62f6def79720d7d2ec8e22853fadaf)) +* **api:** stream helper for pydantic ([c4ab8b0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c4ab8b0d911b92afe76de99143567e6898e9e95c)) +* **api:** streaming support for structured output ([48ddd0a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/48ddd0a996e99b65fb4635f276a5562ef567ed26)) +* **api:** update via SDK Studio ([9407b44](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9407b44fa8dbd4df7c18c36eab95a5573399810a)) +* **client:** support file upload requests ([caadecd](https://github.com/dedalus-labs/dedalus-sdk-python/commit/caadecdf5c75297819cd41fe3adcc5f7af3de772)) +* encryption ([d72d130](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d72d130279b78fb4f946199a003032001b9c162e)) +* flexible input params for .parse() ([b208fbe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b208fbed8300526b323ac7c935d6d50bb652f0d3)) +* structured outputs for tools ([b0434ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b0434ca32e43dc5ef254e3fecb5493a2d3896384)) + + +### Bug Fixes + +* **api:** add shared DedalusModel type ([8855a07](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8855a07e4ea638102e71a049e182891e76e3d34d)) +* **api:** add thought signature ([d2203b1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d2203b129e6b64bbae0b1966654723c2f1ca1159)) +* **api:** hardened _compat types ([312b628](https://github.com/dedalus-labs/dedalus-sdk-python/commit/312b628b48d15aaae5b4d2765a75d2b6b830e318)) +* **client:** close streams without requiring full consumption ([24c4190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/24c4190ccb71d2c369b3d79f5764be31f2e8ead7)) +* compat with Python 3.14 ([aacb192](https://github.com/dedalus-labs/dedalus-sdk-python/commit/aacb192910f8bdd09625f098874cf54d3c0c0971)) +* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([bd1df12](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bd1df12d6c26ad35101bd7b181f33ee8f7fe75ce)) +* ensure streams are always closed ([e0e6406](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e0e6406dc5faeaae21286324d4d247e2706481e1)) +* import paths and tests ([70c967f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/70c967f97d9479307128d6215731a8296bf3fa18)) +* runner tool calling mechanics ([a07f8eb](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a07f8ebd7d65d9a054bba7838443da90f396762d)) +* **runner:** import and dict issues ([fd500d6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd500d6c9ba7c503bfecbf7a3c53b8eaef149d59)) +* **runner:** narrow mcp types ([172b56b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/172b56b0ff518004f1bfb65fd0c28dca3215b026)) +* **runner:** use TypeAlias from typing_extensions for py3.9+ support ([0625b2c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0625b2c9cd7569140fb81848b9d77bbbfbe256b9)) +* **streaming:** correct stream type detection ([7b6576c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7b6576c23400bca2420e6782defa633b0f3dbff9)) +* **tests:** update bug reporting parameters/paths ([3838ebe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3838ebe4db440a852c233cd2a96c2b7a4f0c4082)) +* tool call format and custom_auth validation ([b822aa2](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b822aa214cbf0f735c4480a156639888a80956b1)) +* **types:** allow pyright to infer TypedDict types within SequenceNotStr ([624e2b6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/624e2b67c3b784d64548fb6a3ebde7283b593279)) +* **types:** remove manual DedalusModel ([e1ce236](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e1ce236b931b0715b9fa280ef329bfa451eb05c1)) + + +### Chores + +* add missing docstrings ([52ded59](https://github.com/dedalus-labs/dedalus-sdk-python/commit/52ded5987a0cd141499157a3bf9804fe342e5494)) +* **api:** point local dev to 4010 port for prism ([ad0ec37](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ad0ec3766423c5e6374a96afe55bf415a9c2792c)) +* **auth:** add minor auth params ([c39bcfc](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c39bcfc6366411658cc12e81b5f473176a26fad3)) +* bump `httpx-aiohttp` version to 0.1.9 ([6b5f606](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6b5f60653d76bfe6b4a85d841f115d59c5eba976)) +* bump required `uv` version ([cb3f674](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb3f674dbc9f6392493de7984bcd5fee385adfa0)) +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([cb4d323](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb4d3232c845b60eeccd23efa06057c8408085f5)) +* **docs:** use environment variables for authentication in code snippets ([e4330c0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e4330c0db0f371c49f320ffb6f8238da0229f890)) +* **internal/tests:** avoid race condition with implicit client cleanup ([0854f1d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0854f1d8f52ad5d75c2da1781358f96543793c02)) +* **internal:** avoid using unstable Python versions in tests ([5905b55](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5905b553aaacf8ef4512d851d68b0e0eb7bc647c)) +* **internal:** codegen related update ([d288b0b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d288b0b4f8098c0aab0d79d45f27fd7b7cd8d127)) +* **internal:** detect missing future annotations with ruff ([6909c09](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6909c09996be7fe019ec6737a18b7e330b325c4a)) +* **internal:** grammar fix (it's -> its) ([f0c5880](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f0c58800e495c0cd5c1f13a9799e8e2025154a1c)) +* **package:** drop Python 3.8 support ([ef5e794](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ef5e794d49c800706eeb693170ea4a3ac0245290)) +* remove custom code ([81f922b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/81f922b8eabc571abf4cfd1b87e08517b4564128)) +* **repo:** update contributing doc and mock test script ([fd52f5a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd52f5acd095279bd26d647ee3f93c2daf7977df)) +* tidying ([354f95b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/354f95b1efde6b3df27275b3b8a36510f28d1858)) +* update lockfile ([46f1379](https://github.com/dedalus-labs/dedalus-sdk-python/commit/46f13790651a2c284d50c414556fee925c7ff089)) + + +### Refactors + +* **api:** types for mcp server serialization ([2f614ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/2f614ca5493c9d57807aea9105e77a66c4703130)) +* **api:** update types ([584fe2a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/584fe2aa544ce99955d1d0a712cb51267e06357d)) + +## 0.2.0 (2025-12-06) + +Full Changelog: [v0.2.0...v0.2.0](https://github.com/dedalus-labs/dedalus-sdk-python/compare/v0.2.0...v0.2.0) + +### Features + +* add image edits/variation and vision format support ([f8a8c84](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f8a8c84f3379d92619de56929d6ad3048989b18c)) +* **api:** add endpoints ([c10d7b5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c10d7b55a8ec6bb82556b3efe4db20c91959131d)) +* **api:** add streaming ([745c331](https://github.com/dedalus-labs/dedalus-sdk-python/commit/745c33166a671b79a978961d576064618cc80bcb)) +* **api:** add streaming configuration ([0172ad5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0172ad5175dd15650252a084f213b16c56b8befc)) +* **api:** api update ([280a595](https://github.com/dedalus-labs/dedalus-sdk-python/commit/280a595b3d3900625cfdf26be12027a88eff9618)) +* **api:** auto exec tools ([780162b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/780162b01d27703bb873488702ebede232791ed2)) +* **api:** config update for dedalus-ai/dev ([34e7a71](https://github.com/dedalus-labs/dedalus-sdk-python/commit/34e7a7167f6db94bb7c8c10a7c11746656aec5a6)) +* **api:** config update for dedalus-ai/dev ([197d11b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/197d11bf57da500e05cde2861146da9e3ec278f3)) +* **api:** convenient bug reporting ux ([5aa032f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5aa032f24a9fe44d23cfbf83f12fc79104529c8d)) +* **api:** image support ([ca28133](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ca281334db05ac2e939436050d1cf70ca5359ab4)) +* **api:** improve types ([62cf7e1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/62cf7e1a643cda58ee5112a1a8fc35253fe50f07)) +* **api:** manual updates ([9bb6d0d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9bb6d0d0433111d177410f7ff21d3de254d899a0)) +* **api:** manual updates ([9b2851a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b2851a6bdbf861c0db0b01aa3e7a8f5a45bfa77)) +* **api:** mcp server params ([bf78aad](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bf78aad94d159622460b3a9ecddb9c5d1e1c82bb)) +* **api:** messages param nullable ([e905235](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e9052357b7efa9d49b4f8b8d4c7dfc026d69414b)) +* **api:** response format ([660ac29](https://github.com/dedalus-labs/dedalus-sdk-python/commit/660ac2954efc08eaed5212da934203b5b80f522e)) +* **api:** revert streaming for now ([56d57d5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/56d57d5a19034eec13d5a98a86d133d36ac2830a)) +* **api:** schema compiler landed ([9aeb7a7](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9aeb7a78bfaa81c07e920268afcacbba4a4ff9c9)) +* **api:** standardize name casing with stainless initialism ([ba1e188](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ba1e188beb62f6def79720d7d2ec8e22853fadaf)) +* **api:** stream helper for pydantic ([c4ab8b0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c4ab8b0d911b92afe76de99143567e6898e9e95c)) +* **api:** streaming support for structured output ([48ddd0a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/48ddd0a996e99b65fb4635f276a5562ef567ed26)) +* **api:** update via SDK Studio ([9407b44](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9407b44fa8dbd4df7c18c36eab95a5573399810a)) +* **client:** support file upload requests ([caadecd](https://github.com/dedalus-labs/dedalus-sdk-python/commit/caadecdf5c75297819cd41fe3adcc5f7af3de772)) +* encryption ([d72d130](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d72d130279b78fb4f946199a003032001b9c162e)) +* flexible input params for .parse() ([b208fbe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b208fbed8300526b323ac7c935d6d50bb652f0d3)) +* structured outputs for tools ([b0434ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b0434ca32e43dc5ef254e3fecb5493a2d3896384)) + + +### Bug Fixes + +* **api:** add shared DedalusModel type ([8855a07](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8855a07e4ea638102e71a049e182891e76e3d34d)) +* **api:** add thought signature ([d2203b1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d2203b129e6b64bbae0b1966654723c2f1ca1159)) +* **api:** hardened _compat types ([312b628](https://github.com/dedalus-labs/dedalus-sdk-python/commit/312b628b48d15aaae5b4d2765a75d2b6b830e318)) +* **client:** close streams without requiring full consumption ([24c4190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/24c4190ccb71d2c369b3d79f5764be31f2e8ead7)) +* compat with Python 3.14 ([aacb192](https://github.com/dedalus-labs/dedalus-sdk-python/commit/aacb192910f8bdd09625f098874cf54d3c0c0971)) +* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([bd1df12](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bd1df12d6c26ad35101bd7b181f33ee8f7fe75ce)) +* ensure streams are always closed ([e0e6406](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e0e6406dc5faeaae21286324d4d247e2706481e1)) +* runner tool calling mechanics ([a07f8eb](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a07f8ebd7d65d9a054bba7838443da90f396762d)) +* **runner:** import and dict issues ([fd500d6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd500d6c9ba7c503bfecbf7a3c53b8eaef149d59)) +* **runner:** narrow mcp types ([172b56b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/172b56b0ff518004f1bfb65fd0c28dca3215b026)) +* **runner:** use TypeAlias from typing_extensions for py3.9+ support ([0625b2c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0625b2c9cd7569140fb81848b9d77bbbfbe256b9)) +* **streaming:** correct stream type detection ([7b6576c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7b6576c23400bca2420e6782defa633b0f3dbff9)) +* **tests:** update bug reporting parameters/paths ([3838ebe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3838ebe4db440a852c233cd2a96c2b7a4f0c4082)) +* **types:** remove manual DedalusModel ([e1ce236](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e1ce236b931b0715b9fa280ef329bfa451eb05c1)) + + +### Chores + +* bump `httpx-aiohttp` version to 0.1.9 ([6b5f606](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6b5f60653d76bfe6b4a85d841f115d59c5eba976)) +* bump required `uv` version ([cb3f674](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb3f674dbc9f6392493de7984bcd5fee385adfa0)) +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([cb4d323](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb4d3232c845b60eeccd23efa06057c8408085f5)) +* **docs:** use environment variables for authentication in code snippets ([e4330c0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e4330c0db0f371c49f320ffb6f8238da0229f890)) +* **internal/tests:** avoid race condition with implicit client cleanup ([0854f1d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0854f1d8f52ad5d75c2da1781358f96543793c02)) +* **internal:** avoid using unstable Python versions in tests ([5905b55](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5905b553aaacf8ef4512d851d68b0e0eb7bc647c)) +* **internal:** codegen related update ([d288b0b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d288b0b4f8098c0aab0d79d45f27fd7b7cd8d127)) +* **internal:** detect missing future annotations with ruff ([6909c09](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6909c09996be7fe019ec6737a18b7e330b325c4a)) +* **internal:** grammar fix (it's -> its) ([f0c5880](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f0c58800e495c0cd5c1f13a9799e8e2025154a1c)) +* **package:** drop Python 3.8 support ([ef5e794](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ef5e794d49c800706eeb693170ea4a3ac0245290)) +* remove custom code ([81f922b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/81f922b8eabc571abf4cfd1b87e08517b4564128)) +* tidying ([354f95b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/354f95b1efde6b3df27275b3b8a36510f28d1858)) +* update lockfile ([46f1379](https://github.com/dedalus-labs/dedalus-sdk-python/commit/46f13790651a2c284d50c414556fee925c7ff089)) + + +### Refactors + +* **api:** types for mcp server serialization ([2f614ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/2f614ca5493c9d57807aea9105e77a66c4703130)) +* **api:** update types ([584fe2a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/584fe2aa544ce99955d1d0a712cb51267e06357d)) + +## 0.2.0 (2025-12-06) + +Full Changelog: [v0.3.0...v0.2.0](https://github.com/dedalus-labs/dedalus-sdk-python/compare/v0.3.0...v0.2.0) + +### Features + +* add image edits/variation and vision format support ([f8a8c84](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f8a8c84f3379d92619de56929d6ad3048989b18c)) +* **api:** add endpoints ([c10d7b5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c10d7b55a8ec6bb82556b3efe4db20c91959131d)) +* **api:** add streaming ([745c331](https://github.com/dedalus-labs/dedalus-sdk-python/commit/745c33166a671b79a978961d576064618cc80bcb)) +* **api:** add streaming configuration ([0172ad5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0172ad5175dd15650252a084f213b16c56b8befc)) +* **api:** api update ([280a595](https://github.com/dedalus-labs/dedalus-sdk-python/commit/280a595b3d3900625cfdf26be12027a88eff9618)) +* **api:** auto exec tools ([780162b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/780162b01d27703bb873488702ebede232791ed2)) +* **api:** config update for dedalus-ai/dev ([34e7a71](https://github.com/dedalus-labs/dedalus-sdk-python/commit/34e7a7167f6db94bb7c8c10a7c11746656aec5a6)) +* **api:** config update for dedalus-ai/dev ([197d11b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/197d11bf57da500e05cde2861146da9e3ec278f3)) +* **api:** convenient bug reporting ux ([5aa032f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5aa032f24a9fe44d23cfbf83f12fc79104529c8d)) +* **api:** image support ([ca28133](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ca281334db05ac2e939436050d1cf70ca5359ab4)) +* **api:** improve types ([62cf7e1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/62cf7e1a643cda58ee5112a1a8fc35253fe50f07)) +* **api:** manual updates ([9bb6d0d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9bb6d0d0433111d177410f7ff21d3de254d899a0)) +* **api:** manual updates ([9b2851a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b2851a6bdbf861c0db0b01aa3e7a8f5a45bfa77)) +* **api:** mcp server params ([bf78aad](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bf78aad94d159622460b3a9ecddb9c5d1e1c82bb)) +* **api:** messages param nullable ([e905235](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e9052357b7efa9d49b4f8b8d4c7dfc026d69414b)) +* **api:** response format ([660ac29](https://github.com/dedalus-labs/dedalus-sdk-python/commit/660ac2954efc08eaed5212da934203b5b80f522e)) +* **api:** revert streaming for now ([56d57d5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/56d57d5a19034eec13d5a98a86d133d36ac2830a)) +* **api:** schema compiler landed ([9aeb7a7](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9aeb7a78bfaa81c07e920268afcacbba4a4ff9c9)) +* **api:** standardize name casing with stainless initialism ([ba1e188](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ba1e188beb62f6def79720d7d2ec8e22853fadaf)) +* **api:** stream helper for pydantic ([c4ab8b0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c4ab8b0d911b92afe76de99143567e6898e9e95c)) +* **api:** streaming support for structured output ([48ddd0a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/48ddd0a996e99b65fb4635f276a5562ef567ed26)) +* **api:** update via SDK Studio ([9407b44](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9407b44fa8dbd4df7c18c36eab95a5573399810a)) +* **client:** support file upload requests ([caadecd](https://github.com/dedalus-labs/dedalus-sdk-python/commit/caadecdf5c75297819cd41fe3adcc5f7af3de772)) +* encryption ([d72d130](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d72d130279b78fb4f946199a003032001b9c162e)) +* flexible input params for .parse() ([b208fbe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b208fbed8300526b323ac7c935d6d50bb652f0d3)) +* structured outputs for tools ([b0434ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b0434ca32e43dc5ef254e3fecb5493a2d3896384)) + + +### Bug Fixes + +* **api:** add shared DedalusModel type ([8855a07](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8855a07e4ea638102e71a049e182891e76e3d34d)) +* **api:** add thought signature ([d2203b1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d2203b129e6b64bbae0b1966654723c2f1ca1159)) +* **api:** hardened _compat types ([312b628](https://github.com/dedalus-labs/dedalus-sdk-python/commit/312b628b48d15aaae5b4d2765a75d2b6b830e318)) +* **client:** close streams without requiring full consumption ([24c4190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/24c4190ccb71d2c369b3d79f5764be31f2e8ead7)) +* compat with Python 3.14 ([aacb192](https://github.com/dedalus-labs/dedalus-sdk-python/commit/aacb192910f8bdd09625f098874cf54d3c0c0971)) +* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([bd1df12](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bd1df12d6c26ad35101bd7b181f33ee8f7fe75ce)) +* ensure streams are always closed ([e0e6406](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e0e6406dc5faeaae21286324d4d247e2706481e1)) +* runner tool calling mechanics ([a07f8eb](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a07f8ebd7d65d9a054bba7838443da90f396762d)) +* **runner:** import and dict issues ([fd500d6](https://github.com/dedalus-labs/dedalus-sdk-python/commit/fd500d6c9ba7c503bfecbf7a3c53b8eaef149d59)) +* **runner:** use TypeAlias from typing_extensions for py3.9+ support ([0625b2c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0625b2c9cd7569140fb81848b9d77bbbfbe256b9)) +* **streaming:** correct stream type detection ([7b6576c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7b6576c23400bca2420e6782defa633b0f3dbff9)) +* **tests:** update bug reporting parameters/paths ([3838ebe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3838ebe4db440a852c233cd2a96c2b7a4f0c4082)) +* **types:** remove manual DedalusModel ([e1ce236](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e1ce236b931b0715b9fa280ef329bfa451eb05c1)) + + +### Chores + +* bump `httpx-aiohttp` version to 0.1.9 ([6b5f606](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6b5f60653d76bfe6b4a85d841f115d59c5eba976)) +* bump required `uv` version ([cb3f674](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb3f674dbc9f6392493de7984bcd5fee385adfa0)) +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([cb4d323](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb4d3232c845b60eeccd23efa06057c8408085f5)) +* **docs:** use environment variables for authentication in code snippets ([e4330c0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e4330c0db0f371c49f320ffb6f8238da0229f890)) +* **internal/tests:** avoid race condition with implicit client cleanup ([0854f1d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0854f1d8f52ad5d75c2da1781358f96543793c02)) +* **internal:** avoid using unstable Python versions in tests ([5905b55](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5905b553aaacf8ef4512d851d68b0e0eb7bc647c)) +* **internal:** codegen related update ([d288b0b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d288b0b4f8098c0aab0d79d45f27fd7b7cd8d127)) +* **internal:** detect missing future annotations with ruff ([6909c09](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6909c09996be7fe019ec6737a18b7e330b325c4a)) +* **internal:** grammar fix (it's -> its) ([f0c5880](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f0c58800e495c0cd5c1f13a9799e8e2025154a1c)) +* **package:** drop Python 3.8 support ([ef5e794](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ef5e794d49c800706eeb693170ea4a3ac0245290)) +* remove custom code ([81f922b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/81f922b8eabc571abf4cfd1b87e08517b4564128)) +* tidying ([354f95b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/354f95b1efde6b3df27275b3b8a36510f28d1858)) +* update lockfile ([46f1379](https://github.com/dedalus-labs/dedalus-sdk-python/commit/46f13790651a2c284d50c414556fee925c7ff089)) + + +### Refactors + +* **api:** types for mcp server serialization ([2f614ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/2f614ca5493c9d57807aea9105e77a66c4703130)) +* **api:** update types ([584fe2a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/584fe2aa544ce99955d1d0a712cb51267e06357d)) + +## 0.3.0 (2025-12-02) + +Full Changelog: [v0.2.0...v0.3.0](https://github.com/dedalus-labs/dedalus-sdk-python/compare/v0.2.0...v0.3.0) + +### Features + +* add image edits/variation and vision format support ([f8a8c84](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f8a8c84f3379d92619de56929d6ad3048989b18c)) +* **api:** add endpoints ([c10d7b5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c10d7b55a8ec6bb82556b3efe4db20c91959131d)) +* **api:** add streaming ([745c331](https://github.com/dedalus-labs/dedalus-sdk-python/commit/745c33166a671b79a978961d576064618cc80bcb)) +* **api:** add streaming configuration ([0172ad5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0172ad5175dd15650252a084f213b16c56b8befc)) +* **api:** api update ([280a595](https://github.com/dedalus-labs/dedalus-sdk-python/commit/280a595b3d3900625cfdf26be12027a88eff9618)) +* **api:** auto exec tools ([780162b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/780162b01d27703bb873488702ebede232791ed2)) +* **api:** config update for dedalus-ai/dev ([34e7a71](https://github.com/dedalus-labs/dedalus-sdk-python/commit/34e7a7167f6db94bb7c8c10a7c11746656aec5a6)) +* **api:** config update for dedalus-ai/dev ([197d11b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/197d11bf57da500e05cde2861146da9e3ec278f3)) +* **api:** convenient bug reporting ux ([5aa032f](https://github.com/dedalus-labs/dedalus-sdk-python/commit/5aa032f24a9fe44d23cfbf83f12fc79104529c8d)) +* **api:** image support ([ca28133](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ca281334db05ac2e939436050d1cf70ca5359ab4)) +* **api:** improve types ([62cf7e1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/62cf7e1a643cda58ee5112a1a8fc35253fe50f07)) +* **api:** manual updates ([9bb6d0d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9bb6d0d0433111d177410f7ff21d3de254d899a0)) +* **api:** manual updates ([9b2851a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9b2851a6bdbf861c0db0b01aa3e7a8f5a45bfa77)) +* **api:** mcp server params ([bf78aad](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bf78aad94d159622460b3a9ecddb9c5d1e1c82bb)) +* **api:** messages param nullable ([e905235](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e9052357b7efa9d49b4f8b8d4c7dfc026d69414b)) +* **api:** response format ([660ac29](https://github.com/dedalus-labs/dedalus-sdk-python/commit/660ac2954efc08eaed5212da934203b5b80f522e)) +* **api:** revert streaming for now ([56d57d5](https://github.com/dedalus-labs/dedalus-sdk-python/commit/56d57d5a19034eec13d5a98a86d133d36ac2830a)) +* **api:** schema compiler landed ([9aeb7a7](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9aeb7a78bfaa81c07e920268afcacbba4a4ff9c9)) +* **api:** standardize name casing with stainless initialism ([ba1e188](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ba1e188beb62f6def79720d7d2ec8e22853fadaf)) +* **api:** stream helper for pydantic ([c4ab8b0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/c4ab8b0d911b92afe76de99143567e6898e9e95c)) +* **api:** streaming support for structured output ([48ddd0a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/48ddd0a996e99b65fb4635f276a5562ef567ed26)) +* **api:** update via SDK Studio ([9407b44](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9407b44fa8dbd4df7c18c36eab95a5573399810a)) +* **client:** support file upload requests ([caadecd](https://github.com/dedalus-labs/dedalus-sdk-python/commit/caadecdf5c75297819cd41fe3adcc5f7af3de772)) +* flexible input params for .parse() ([b208fbe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b208fbed8300526b323ac7c935d6d50bb652f0d3)) +* structured outputs for tools ([b0434ca](https://github.com/dedalus-labs/dedalus-sdk-python/commit/b0434ca32e43dc5ef254e3fecb5493a2d3896384)) + + +### Bug Fixes + +* **api:** add shared DedalusModel type ([8855a07](https://github.com/dedalus-labs/dedalus-sdk-python/commit/8855a07e4ea638102e71a049e182891e76e3d34d)) +* **api:** add thought signature ([d2203b1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d2203b129e6b64bbae0b1966654723c2f1ca1159)) +* **api:** hardened _compat types ([312b628](https://github.com/dedalus-labs/dedalus-sdk-python/commit/312b628b48d15aaae5b4d2765a75d2b6b830e318)) +* **client:** close streams without requiring full consumption ([24c4190](https://github.com/dedalus-labs/dedalus-sdk-python/commit/24c4190ccb71d2c369b3d79f5764be31f2e8ead7)) +* compat with Python 3.14 ([aacb192](https://github.com/dedalus-labs/dedalus-sdk-python/commit/aacb192910f8bdd09625f098874cf54d3c0c0971)) +* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([bd1df12](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bd1df12d6c26ad35101bd7b181f33ee8f7fe75ce)) +* ensure streams are always closed ([e0e6406](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e0e6406dc5faeaae21286324d4d247e2706481e1)) +* runner tool calling mechanics ([a07f8eb](https://github.com/dedalus-labs/dedalus-sdk-python/commit/a07f8ebd7d65d9a054bba7838443da90f396762d)) +* **runner:** use TypeAlias from typing_extensions for py3.9+ support ([0625b2c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0625b2c9cd7569140fb81848b9d77bbbfbe256b9)) +* **streaming:** correct stream type detection ([7b6576c](https://github.com/dedalus-labs/dedalus-sdk-python/commit/7b6576c23400bca2420e6782defa633b0f3dbff9)) +* **tests:** update bug reporting parameters/paths ([3838ebe](https://github.com/dedalus-labs/dedalus-sdk-python/commit/3838ebe4db440a852c233cd2a96c2b7a4f0c4082)) +* **types:** remove manual DedalusModel ([e1ce236](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e1ce236b931b0715b9fa280ef329bfa451eb05c1)) + + +### Chores + +* bump `httpx-aiohttp` version to 0.1.9 ([6b5f606](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6b5f60653d76bfe6b4a85d841f115d59c5eba976)) +* bump required `uv` version ([cb3f674](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb3f674dbc9f6392493de7984bcd5fee385adfa0)) +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([cb4d323](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb4d3232c845b60eeccd23efa06057c8408085f5)) +* **docs:** use environment variables for authentication in code snippets ([e4330c0](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e4330c0db0f371c49f320ffb6f8238da0229f890)) +* **internal/tests:** avoid race condition with implicit client cleanup ([0854f1d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/0854f1d8f52ad5d75c2da1781358f96543793c02)) +* **internal:** codegen related update ([d288b0b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d288b0b4f8098c0aab0d79d45f27fd7b7cd8d127)) +* **internal:** detect missing future annotations with ruff ([6909c09](https://github.com/dedalus-labs/dedalus-sdk-python/commit/6909c09996be7fe019ec6737a18b7e330b325c4a)) +* **internal:** grammar fix (it's -> its) ([f0c5880](https://github.com/dedalus-labs/dedalus-sdk-python/commit/f0c58800e495c0cd5c1f13a9799e8e2025154a1c)) +* **package:** drop Python 3.8 support ([ef5e794](https://github.com/dedalus-labs/dedalus-sdk-python/commit/ef5e794d49c800706eeb693170ea4a3ac0245290)) +* remove custom code ([81f922b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/81f922b8eabc571abf4cfd1b87e08517b4564128)) +* tidying ([354f95b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/354f95b1efde6b3df27275b3b8a36510f28d1858)) +* update lockfile ([46f1379](https://github.com/dedalus-labs/dedalus-sdk-python/commit/46f13790651a2c284d50c414556fee925c7ff089)) + + +### Refactors + +* **api:** update types ([584fe2a](https://github.com/dedalus-labs/dedalus-sdk-python/commit/584fe2aa544ce99955d1d0a712cb51267e06357d)) + +## 0.2.0 (2025-12-01) + +Full Changelog: [v0.1.1...v0.2.0](https://github.com/dedalus-labs/dedalus-sdk-python/compare/v0.1.1...v0.2.0) + +### Features + +* **api:** config update for dedalus-ai/dev ([197d11b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/197d11bf57da500e05cde2861146da9e3ec278f3)) +* **api:** improve types ([62cf7e1](https://github.com/dedalus-labs/dedalus-sdk-python/commit/62cf7e1a643cda58ee5112a1a8fc35253fe50f07)) +* **api:** manual updates ([9bb6d0d](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9bb6d0d0433111d177410f7ff21d3de254d899a0)) +* **api:** mcp server params ([bf78aad](https://github.com/dedalus-labs/dedalus-sdk-python/commit/bf78aad94d159622460b3a9ecddb9c5d1e1c82bb)) +* **api:** schema compiler landed ([9aeb7a7](https://github.com/dedalus-labs/dedalus-sdk-python/commit/9aeb7a78bfaa81c07e920268afcacbba4a4ff9c9)) + + +### Bug Fixes + +* ensure streams are always closed ([e0e6406](https://github.com/dedalus-labs/dedalus-sdk-python/commit/e0e6406dc5faeaae21286324d4d247e2706481e1)) + + +### Chores + +* **deps:** mypy 1.18.1 has a regression, pin to 1.17 ([cb4d323](https://github.com/dedalus-labs/dedalus-sdk-python/commit/cb4d3232c845b60eeccd23efa06057c8408085f5)) +* **internal:** codegen related update ([d288b0b](https://github.com/dedalus-labs/dedalus-sdk-python/commit/d288b0b4f8098c0aab0d79d45f27fd7b7cd8d127)) + ## 0.1.1 (2025-11-12) Full Changelog: [v0.1.0...v0.1.1](https://github.com/dedalus-labs/dedalus-sdk-python/compare/v0.1.0...v0.1.1) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 286f9a3..f5c4f13 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,32 +1,32 @@ ## Setting up the environment -### With Rye +### With `uv` -We use [Rye](https://rye.astral.sh/) to manage dependencies because it will automatically provision a Python environment with the expected Python version. To set it up, run: +We use [uv](https://docs.astral.sh/uv/) to manage dependencies because it will automatically provision a Python environment with the expected Python version. To set it up, run: ```sh $ ./scripts/bootstrap ``` -Or [install Rye manually](https://rye.astral.sh/guide/installation/) and run: +Or [install uv manually](https://docs.astral.sh/uv/getting-started/installation/) and run: ```sh -$ rye sync --all-features +$ uv sync --all-extras ``` -You can then run scripts using `rye run python script.py` or by activating the virtual environment: +You can then run scripts using `uv run python script.py` or by manually activating the virtual environment: ```sh -# Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work +# manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work $ source .venv/bin/activate -# now you can omit the `rye run` prefix +# now you can omit the `uv run` prefix $ python script.py ``` -### Without Rye +### Without `uv` -Alternatively if you don't want to install `Rye`, you can stick with the standard `pip` setup by ensuring you have the Python version specified in `.python-version`, create a virtual environment however you desire and then install dependencies using this command: +Alternatively if you don't want to install `uv`, you can stick with the standard `pip` setup by ensuring you have the Python version specified in `.python-version`, create a virtual environment however you desire and then install dependencies using this command: ```sh $ pip install -r requirements-dev.lock @@ -45,7 +45,7 @@ All files in the `examples/` directory are not modified by the generator and can ```py # add an example to examples/.py -#!/usr/bin/env -S rye run python +#!/usr/bin/env -S uv run python … ``` @@ -72,7 +72,7 @@ Building this package will create two files in the `dist/` directory, a `.tar.gz To create a distributable version of the library, all you have to do is run this command: ```sh -$ rye build +$ uv build # or $ python -m build ``` @@ -85,17 +85,36 @@ $ pip install ./path-to-wheel-file.whl ## Running tests -Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. - ```sh -# you will need npm installed -$ npx prism mock path/to/your/openapi.yml +# Run all tests (uses respx mocking, no server needed) +$ uv run pytest + +# Run with Pydantic v1 and v2 across Python versions +$ ./scripts/test ``` +### Prism mock server (optional) + +Some API resource tests are marked as skipped by default. To run them, you need Prism: + ```sh -$ ./scripts/test +# Start mock server (uses spec URL from .stats.yml) +$ ./scripts/mock --daemon + +# Or manually with bun (recommended) or npm +$ bunx @stainless-api/prism-cli@5.15.0 prism mock \ + "$(grep openapi_spec_url .stats.yml | cut -d' ' -f2)" + +# Or with npx if you don't have bun +$ npx @stainless-api/prism-cli@5.15.0 prism mock \ + "$(grep openapi_spec_url .stats.yml | cut -d' ' -f2)" + +# Kill when done +$ kill $(lsof -t -i tcp:4010) ``` +Note: The Prism tests in `tests/api_resources/` are currently hardcoded with `@pytest.mark.skip`. These are Stainless-generated and validate wire format compliance. The core SDK logic is covered by the non-skipped tests. + ## Linting and formatting This repository uses [ruff](https://github.com/astral-sh/ruff) and diff --git a/LICENSE b/LICENSE index 9794a20..3aa6139 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2025 Dedalus +Copyright 2026 Dedalus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index eefed99..896e3c6 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,15 @@ and offers both synchronous and asynchronous clients powered by [httpx](https:// It is generated with [Stainless](https://www.stainless.com/). +## MCP Server + +Use the Dedalus MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. + +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=dedalus-labs-mcp&config=eyJuYW1lIjoiZGVkYWx1cy1sYWJzLW1jcCIsInRyYW5zcG9ydCI6InNzZSIsInVybCI6Imh0dHBzOi8vZGVkYWx1cy1zZGsuc3RsbWNwLmNvbS9zc2UifQ) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22dedalus-labs-mcp%22%2C%22type%22%3A%22sse%22%2C%22url%22%3A%22https%3A%2F%2Fdedalus-sdk.stlmcp.com%2Fsse%22%7D) + +> Note: You may need to set environment variables in your MCP client. + ## Documentation The REST API documentation can be found on [docs.dedaluslabs.ai](https://docs.dedaluslabs.ai). The full API of this library can be found in [api.md](api.md). @@ -34,16 +43,20 @@ client = Dedalus( environment="development", ) -completion = client.chat.completions.create( +chat_completion = client.chat.completions.create( model="openai/gpt-5-nano", messages=[ + { + "role": "system", + "content": "You are Stephen Dedalus. Respond in morose Joycean malaise.", + }, { "role": "user", "content": "Hello, how are you today?", - } + }, ], ) -print(completion.id) +print(chat_completion.id) ``` While you can provide an `api_key` keyword argument, @@ -68,16 +81,20 @@ client = AsyncDedalus( async def main() -> None: - completion = await client.chat.completions.create( + chat_completion = await client.chat.completions.create( model="openai/gpt-5-nano", messages=[ + { + "role": "system", + "content": "You are Stephen Dedalus. Respond in morose Joycean malaise.", + }, { "role": "user", "content": "Hello, how are you today?", - } + }, ], ) - print(completion.id) + print(chat_completion.id) asyncio.run(main()) @@ -99,6 +116,7 @@ pip install dedalus_labs[aiohttp] Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: ```python +import os import asyncio from dedalus_labs import DefaultAioHttpClient from dedalus_labs import AsyncDedalus @@ -106,19 +124,23 @@ from dedalus_labs import AsyncDedalus async def main() -> None: async with AsyncDedalus( - api_key="My API Key", + api_key=os.environ.get("DEDALUS_API_KEY"), # This is the default and can be omitted http_client=DefaultAioHttpClient(), ) as client: - completion = await client.chat.completions.create( + chat_completion = await client.chat.completions.create( model="openai/gpt-5-nano", messages=[ + { + "role": "system", + "content": "You are Stephen Dedalus. Respond in morose Joycean malaise.", + }, { "role": "user", "content": "Hello, how are you today?", - } + }, ], ) - print(completion.id) + print(chat_completion.id) asyncio.run(main()) @@ -147,8 +169,8 @@ stream = client.chat.completions.create( }, ], ) -for completion in stream: - print(completion.id) +for chat_completion in stream: + print(chat_completion.id) ``` The async client uses the exact same interface. @@ -172,8 +194,8 @@ stream = await client.chat.completions.create( }, ], ) -async for completion in stream: - print(completion.id) +async for chat_completion in stream: + print(chat_completion.id) ``` ## Using types @@ -185,6 +207,25 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. +## Nested params + +Nested parameters are dictionaries, typed using `TypedDict`, for example: + +```python +from dedalus_labs import Dedalus + +client = Dedalus() + +chat_completion = client.chat.completions.create( + model="openai/gpt-5", + audio={ + "format": "wav", + "voice": "string", + }, +) +print(chat_completion.audio) +``` + ## File uploads Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`. @@ -219,7 +260,19 @@ from dedalus_labs import Dedalus client = Dedalus() try: - client.health.check() + client.chat.completions.create( + model="openai/gpt-5-nano", + messages=[ + { + "role": "system", + "content": "You are Stephen Dedalus. Respond in morose Joycean malaise.", + }, + { + "role": "user", + "content": "Hello, how are you today?", + }, + ], + ) except dedalus_labs.APIConnectionError as e: print("The server could not be reached") print(e.__cause__) # an underlying Exception, likely raised within httpx. @@ -246,7 +299,7 @@ Error codes are as follows: ### Retries -Certain errors are automatically retried 2 times by default, with a short exponential backoff. +Certain errors are automatically retried 0 times by default, with a short exponential backoff. Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, 429 Rate Limit, and >=500 Internal errors are all retried by default. @@ -262,7 +315,19 @@ client = Dedalus( ) # Or, configure per-request: -client.with_options(max_retries=5).health.check() +client.with_options(max_retries=5).chat.completions.create( + model="openai/gpt-5-nano", + messages=[ + { + "role": "system", + "content": "You are Stephen Dedalus. Respond in morose Joycean malaise.", + }, + { + "role": "user", + "content": "Hello, how are you today?", + }, + ], +) ``` ### Timeouts @@ -285,7 +350,19 @@ client = Dedalus( ) # Override per-request: -client.with_options(timeout=5.0).health.check() +client.with_options(timeout=5.0).chat.completions.create( + model="openai/gpt-5-nano", + messages=[ + { + "role": "system", + "content": "You are Stephen Dedalus. Respond in morose Joycean malaise.", + }, + { + "role": "user", + "content": "Hello, how are you today?", + }, + ], +) ``` On timeout, an `APITimeoutError` is thrown. @@ -345,11 +422,20 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to from dedalus_labs import Dedalus client = Dedalus() -response = client.health.with_raw_response.check() +response = client.chat.completions.with_raw_response.create( + model="openai/gpt-5-nano", + messages=[{ + "role": "system", + "content": "You are Stephen Dedalus. Respond in morose Joycean malaise.", + }, { + "role": "user", + "content": "Hello, how are you today?", + }], +) print(response.headers.get('X-My-Header')) -health = response.parse() # get the object that `health.check()` would have returned -print(health.status) +completion = response.parse() # get the object that `chat.completions.create()` would have returned +print(completion.id) ``` These methods return an [`APIResponse`](https://github.com/dedalus-labs/dedalus-sdk-python/tree/main/src/dedalus_labs/_response.py) object. @@ -363,7 +449,19 @@ The above interface eagerly reads the full response body when you make the reque To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. ```python -with client.health.with_streaming_response.check() as response: +with client.chat.completions.with_streaming_response.create( + model="openai/gpt-5-nano", + messages=[ + { + "role": "system", + "content": "You are Stephen Dedalus. Respond in morose Joycean malaise.", + }, + { + "role": "user", + "content": "Hello, how are you today?", + }, + ], +) as response: print(response.headers.get("X-My-Header")) for line in response.iter_lines(): diff --git a/api.md b/api.md index 3fde9db..ffd2c65 100644 --- a/api.md +++ b/api.md @@ -2,38 +2,25 @@ ```python from dedalus_labs.types import ( + Credential, DedalusModel, DedalusModelChoice, + FunctionDefinition, + JSONObjectInput, + JSONValueInput, + MCPCredentials, + MCPServerSpec, + MCPServers, + MCPToolResult, + ModelSettings, + Reasoning, ResponseFormatJSONObject, ResponseFormatJSONSchema, ResponseFormatText, + ToolChoice, ) ``` -# Root - -Types: - -```python -from dedalus_labs.types import RootGetResponse -``` - -Methods: - -- client.root.get() -> RootGetResponse - -# Health - -Types: - -```python -from dedalus_labs.types import HealthCheckResponse -``` - -Methods: - -- client.health.check() -> HealthCheckResponse - # Models Types: @@ -113,16 +100,48 @@ Types: ```python from dedalus_labs.types.chat import ( + Audio, + ChatCompletion, + ChatCompletionAssistantMessageParam, + ChatCompletionAudioParam, + ChatCompletionChunk, + ChatCompletionContentPartFileParam, + ChatCompletionContentPartImageParam, + ChatCompletionContentPartInputAudioParam, + ChatCompletionContentPartRefusalParam, + ChatCompletionContentPartTextParam, + ChatCompletionCreateParams, + ChatCompletionDeveloperMessageParam, + ChatCompletionFunctionMessageParam, + ChatCompletionFunctions, + ChatCompletionMessage, + ChatCompletionMessageCustomToolCall, + ChatCompletionMessageToolCall, + ChatCompletionSystemMessageParam, ChatCompletionTokenLogprob, - Completion, - CompletionRequest, - ModelID, - Models, - StreamChunk, - TopLogprob, + ChatCompletionToolMessageParam, + ChatCompletionToolParam, + ChatCompletionUserMessageParam, + Choice, + ChoiceDelta, + ChoiceDeltaToolCall, + ChoiceLogprobs, + CompletionTokensDetails, + CompletionUsage, + InputTokenDetails, + PredictionContent, + PromptTokensDetails, + StreamChoice, + StreamChoiceLogprobs, + ThinkingConfigDisabled, + ThinkingConfigEnabled, + ToolChoiceAny, + ToolChoiceAuto, + ToolChoiceNone, + ToolChoiceTool, ) ``` Methods: -- client.chat.completions.create(\*\*params) -> Completion +- client.chat.completions.create(\*\*params) -> ChatCompletion diff --git a/bin/publish-pypi b/bin/publish-pypi index 826054e..e72ca2f 100644 --- a/bin/publish-pypi +++ b/bin/publish-pypi @@ -1,6 +1,7 @@ #!/usr/bin/env bash set -eux +rm -rf dist mkdir -p dist -rye build --clean -rye publish --yes --token=$PYPI_TOKEN +uv build +uv publish --token=$PYPI_TOKEN diff --git a/noxfile.py b/noxfile.py deleted file mode 100644 index 53bca7f..0000000 --- a/noxfile.py +++ /dev/null @@ -1,9 +0,0 @@ -import nox - - -@nox.session(reuse_venv=True, name="test-pydantic-v1") -def test_pydantic_v1(session: nox.Session) -> None: - session.install("-r", "requirements-dev.lock") - session.install("pydantic<2") - - session.run("pytest", "--showlocals", "--ignore=tests/functional", *session.posargs) diff --git a/pyproject.toml b/pyproject.toml index f523161..8c5724e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,21 +1,23 @@ [project] name = "dedalus_labs" -version = "0.1.1" +version = "0.2.0" description = "The official Python library for the Dedalus API" dynamic = ["readme"] license = "MIT" authors = [ -{ name = "Dedalus", email = "founders@dedaluslabs.ai" }, +{ name = "Dedalus Labs", email = "oss@dedaluslabs.ai" } ] + dependencies = [ - "httpx>=0.23.0, <1", - "pydantic>=1.9.0, <3", - "typing-extensions>=4.10, <5", - "anyio>=3.5.0, <5", - "distro>=1.7.0, <2", - "sniffio", - "jiter<0.10.0", + "httpx>=0.23.0, <1", + "pydantic>=1.9.0, <3", + "typing-extensions>=4.10, <5", + "anyio>=3.5.0, <5", + "distro>=1.7.0, <2", + "sniffio", + "jiter>=0.10.0, <1", ] + requires-python = ">= 3.9" classifiers = [ "Typing :: Typed", @@ -25,6 +27,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: MacOS", @@ -40,53 +43,40 @@ Repository = "https://github.com/dedalus-labs/dedalus-sdk-python" [project.optional-dependencies] aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"] +auth = [ + "pyjwt[crypto]>=2.10.1", +] -[tool.rye] +[tool.uv] managed = true -# version pins are in requirements-dev.lock -dev-dependencies = [ - "pyright==1.1.399", - "mypy", - "respx", - "pytest", - "pytest-asyncio", - "ruff", - "time-machine", - "nox", - "dirty-equals>=0.6.0", - "importlib-metadata>=6.7.0", - "rich>=13.7.1", - "pytest-xdist>=3.6.1", +required-version = ">=0.9" +conflicts = [ + [ + { group = "pydantic-v1" }, + { group = "pydantic-v2" }, + ], ] -[tool.rye.scripts] -format = { chain = [ - "format:ruff", - "format:docs", - "fix:ruff", - # run formatting again to fix any inconsistencies when imports are stripped - "format:ruff", -]} -"format:docs" = "python scripts/utils/ruffen-docs.py README.md api.md" -"format:ruff" = "ruff format" - -"lint" = { chain = [ - "check:ruff", - "typecheck", - "check:importable", -]} -"check:ruff" = "ruff check ." -"fix:ruff" = "ruff check --fix ." - -"check:importable" = "python -c 'import dedalus_labs'" - -typecheck = { chain = [ - "typecheck:pyright", - "typecheck:mypy" -]} -"typecheck:pyright" = "pyright" -"typecheck:verify-types" = "pyright --verifytypes dedalus_labs --ignoreexternal" -"typecheck:mypy" = "mypy ." +[dependency-groups] +# version pins are in uv.lock +dev = [ + "pyright==1.1.399", + "mypy==1.17", + "respx", + "pytest", + "pytest-asyncio", + "ruff", + "time-machine", + "dirty-equals>=0.6.0", + "importlib-metadata>=6.7.0", + "rich>=13.7.1", + "pytest-xdist>=3.6.1", +] +pydantic-v1 = ["pydantic>=1.9.0,<2"] +pydantic-v2 = [ + "pydantic~=2.0 ; python_full_version < '3.14'", + "pydantic~=2.12 ; python_full_version >= '3.14'", +] [build-system] requires = ["hatchling==1.26.3", "hatch-fancy-pypi-readme"] @@ -215,6 +205,7 @@ target-version = "py38" [tool.ruff.format] docstring-code-format = true +quote-style = "double" [tool.ruff.lint] select = [ diff --git a/requirements-dev.lock b/requirements-dev.lock index fef32d1..c459281 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -1,137 +1,110 @@ -# generated by rye -# use `rye lock` or `rye sync` to update this lockfile -# -# last locked with the following flags: -# pre: false -# features: [] -# all-features: true -# with-sources: false -# generate-hashes: false -# universal: false - --e file:. -aiohappyeyeballs==2.6.1 - # via aiohttp -aiohttp==3.12.8 - # via dedalus-labs - # via httpx-aiohttp -aiosignal==1.3.2 - # via aiohttp -annotated-types==0.6.0 +# This file was autogenerated by uv via the following command: +# uv export -o requirements-dev.lock --no-hashes +-e . +annotated-types==0.7.0 # via pydantic -anyio==4.4.0 - # via dedalus-labs - # via httpx -argcomplete==3.1.2 - # via nox -async-timeout==5.0.1 - # via aiohttp -attrs==25.3.0 - # via aiohttp -certifi==2023.7.22 - # via httpcore - # via httpx -colorlog==6.7.0 - # via nox -dirty-equals==0.6.0 -distlib==0.3.7 - # via virtualenv -distro==1.8.0 - # via dedalus-labs -exceptiongroup==1.2.2 - # via anyio +anyio==4.12.0 + # via + # dedalus-labs + # httpx +backports-asyncio-runner==1.2.0 ; python_full_version < '3.11' + # via pytest-asyncio +certifi==2025.11.12 + # via + # httpcore + # httpx +colorama==0.4.6 ; sys_platform == 'win32' # via pytest -execnet==2.1.1 +dirty-equals==0.11 +distro==1.9.0 + # via dedalus-labs +exceptiongroup==1.3.1 ; python_full_version < '3.11' + # via + # anyio + # pytest +execnet==2.1.2 # via pytest-xdist -filelock==3.12.4 - # via virtualenv -frozenlist==1.6.2 - # via aiohttp - # via aiosignal h11==0.16.0 # via httpcore httpcore==1.0.9 # via httpx httpx==0.28.1 - # via dedalus-labs - # via httpx-aiohttp - # via respx -httpx-aiohttp==0.1.9 - # via dedalus-labs -idna==3.4 - # via anyio - # via httpx - # via yarl -importlib-metadata==7.0.0 -iniconfig==2.0.0 + # via + # dedalus-labs + # respx +idna==3.11 + # via + # anyio + # httpx +importlib-metadata==8.7.0 +iniconfig==2.1.0 ; python_full_version < '3.10' + # via pytest +iniconfig==2.3.0 ; python_full_version >= '3.10' # via pytest -markdown-it-py==3.0.0 +markdown-it-py==3.0.0 ; python_full_version < '3.10' + # via rich +markdown-it-py==4.0.0 ; python_full_version >= '3.10' # via rich mdurl==0.1.2 # via markdown-it-py -multidict==6.4.4 - # via aiohttp - # via yarl -mypy==1.14.1 -mypy-extensions==1.0.0 +mypy==1.17.0 +mypy-extensions==1.1.0 # via mypy -nodeenv==1.8.0 +nodeenv==1.9.1 # via pyright -nox==2023.4.22 -packaging==23.2 - # via nox +packaging==25.0 # via pytest -platformdirs==3.11.0 - # via virtualenv -pluggy==1.5.0 +pathspec==0.12.1 + # via mypy +pluggy==1.6.0 # via pytest -propcache==0.3.1 - # via aiohttp - # via yarl -pydantic==2.11.9 +pydantic==2.12.5 # via dedalus-labs -pydantic-core==2.33.2 +pydantic-core==2.41.5 # via pydantic -pygments==2.18.0 - # via rich +pygments==2.19.2 + # via + # pytest + # rich pyright==1.1.399 -pytest==8.3.3 - # via pytest-asyncio - # via pytest-xdist -pytest-asyncio==0.24.0 -pytest-xdist==3.7.0 -python-dateutil==2.8.2 +pytest==8.4.2 ; python_full_version < '3.10' + # via + # pytest-asyncio + # pytest-xdist +pytest==9.0.1 ; python_full_version >= '3.10' + # via + # pytest-asyncio + # pytest-xdist +pytest-asyncio==1.2.0 ; python_full_version < '3.10' +pytest-asyncio==1.3.0 ; python_full_version >= '3.10' +pytest-xdist==3.8.0 +python-dateutil==2.9.0.post0 ; python_full_version < '3.10' # via time-machine -pytz==2023.3.post1 - # via dirty-equals respx==0.22.0 -rich==13.7.1 -ruff==0.9.4 -setuptools==68.2.2 - # via nodeenv -six==1.16.0 +rich==14.2.0 +ruff==0.14.7 +six==1.17.0 ; python_full_version < '3.10' # via python-dateutil -sniffio==1.3.0 - # via anyio +sniffio==1.3.1 # via dedalus-labs -time-machine==2.9.0 -tomli==2.0.2 - # via mypy - # via pytest -typing-extensions==4.12.2 - # via anyio - # via dedalus-labs - # via multidict - # via mypy - # via pydantic - # via pydantic-core - # via pyright - # via typing-inspection -typing-inspection==0.4.1 +time-machine==2.19.0 ; python_full_version < '3.10' +time-machine==3.1.0 ; python_full_version >= '3.10' +tomli==2.3.0 ; python_full_version < '3.11' + # via + # mypy + # pytest +typing-extensions==4.15.0 + # via + # anyio + # dedalus-labs + # exceptiongroup + # mypy + # pydantic + # pydantic-core + # pyright + # pytest-asyncio + # typing-inspection +typing-inspection==0.4.2 # via pydantic -virtualenv==20.24.5 - # via nox -yarl==1.20.0 - # via aiohttp -zipp==3.17.0 +zipp==3.23.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock deleted file mode 100644 index 161ce5d..0000000 --- a/requirements.lock +++ /dev/null @@ -1,75 +0,0 @@ -# generated by rye -# use `rye lock` or `rye sync` to update this lockfile -# -# last locked with the following flags: -# pre: false -# features: [] -# all-features: true -# with-sources: false -# generate-hashes: false -# universal: false - --e file:. -aiohappyeyeballs==2.6.1 - # via aiohttp -aiohttp==3.12.8 - # via dedalus-labs - # via httpx-aiohttp -aiosignal==1.3.2 - # via aiohttp -annotated-types==0.6.0 - # via pydantic -anyio==4.4.0 - # via dedalus-labs - # via httpx -async-timeout==5.0.1 - # via aiohttp -attrs==25.3.0 - # via aiohttp -certifi==2023.7.22 - # via httpcore - # via httpx -distro==1.8.0 - # via dedalus-labs -exceptiongroup==1.2.2 - # via anyio -frozenlist==1.6.2 - # via aiohttp - # via aiosignal -h11==0.16.0 - # via httpcore -httpcore==1.0.9 - # via httpx -httpx==0.28.1 - # via dedalus-labs - # via httpx-aiohttp -httpx-aiohttp==0.1.9 - # via dedalus-labs -idna==3.4 - # via anyio - # via httpx - # via yarl -multidict==6.4.4 - # via aiohttp - # via yarl -propcache==0.3.1 - # via aiohttp - # via yarl -pydantic==2.11.9 - # via dedalus-labs -pydantic-core==2.33.2 - # via pydantic -sniffio==1.3.0 - # via anyio - # via dedalus-labs -typing-extensions==4.12.2 - # via anyio - # via dedalus-labs - # via multidict - # via pydantic - # via pydantic-core - # via typing-inspection -typing-inspection==0.4.1 - # via pydantic -yarl==1.20.0 - # via aiohttp diff --git a/scripts/bootstrap b/scripts/bootstrap index b430fee..4638ec6 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -19,9 +19,12 @@ if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] } fi -echo "==> Installing Python dependencies…" +echo "==> Installing Python…" +uv python install -# experimental uv support makes installations significantly faster -rye config --set-bool behavior.use-uv=true +echo "==> Installing Python dependencies…" +uv sync --all-extras -rye sync --all-features +echo "==> Exporting Python dependencies…" +# note: `--no-hashes` is required because of https://github.com/pypa/pip/issues/4995 +uv export -o requirements-dev.lock --no-hashes diff --git a/scripts/format b/scripts/format index 667ec2d..1d2f9c6 100755 --- a/scripts/format +++ b/scripts/format @@ -4,5 +4,11 @@ set -e cd "$(dirname "$0")/.." -echo "==> Running formatters" -rye run format +echo "==> Running ruff" +uv run ruff format +uv run ruff check --fix . +# run formatting again to fix any inconsistencies when imports are stripped +uv run ruff format + +echo "==> Formatting docs" +uv run python scripts/utils/ruffen-docs.py README.md api.md diff --git a/scripts/lint b/scripts/lint index bf96ee8..bf60fc9 100755 --- a/scripts/lint +++ b/scripts/lint @@ -4,8 +4,19 @@ set -e cd "$(dirname "$0")/.." -echo "==> Running lints" -rye run lint +if [ "$1" = "--fix" ]; then + echo "==> Running ruff with --fix" + uv run ruff check . --fix +else + echo "==> Running ruff" + uv run ruff check . +fi + +echo "==> Running pyright" +uv run pyright + +echo "==> Running mypy" +uv run mypy . echo "==> Making sure it imports" -rye run python -c 'import dedalus_labs' +uv run python -c 'import dedalus_labs' diff --git a/scripts/test b/scripts/test index dbeda2d..b56970b 100755 --- a/scripts/test +++ b/scripts/test @@ -54,8 +54,31 @@ fi export DEFER_PYDANTIC_BUILD=false -echo "==> Running tests" -rye run pytest "$@" +# Note that we need to specify the patch version here so that uv +# won't use unstable (alpha, beta, rc) releases for the tests +PY_VERSION_MIN=">=3.9.0" +PY_VERSION_MAX=">=3.14.0" -echo "==> Running Pydantic v1 tests" -rye run nox -s test-pydantic-v1 -- "$@" +function run_tests() { + echo "==> Running tests with Pydantic v2" + uv run --isolated --all-extras pytest "$@" + + # Skip Pydantic v1 tests on latest Python (not supported) + if [[ "$UV_PYTHON" != "$PY_VERSION_MAX" ]]; then + echo "==> Running tests with Pydantic v1" + uv run --isolated --all-extras --group=pydantic-v1 pytest "$@" + fi +} + +# If UV_PYTHON is already set in the environment, just run the command once +if [[ -n "$UV_PYTHON" ]]; then + run_tests "$@" +else + # If UV_PYTHON is not set, run the command for min and max versions + + echo "==> Running tests for Python $PY_VERSION_MIN" + UV_PYTHON="$PY_VERSION_MIN" run_tests "$@" + + echo "==> Running tests for Python $PY_VERSION_MAX" + UV_PYTHON="$PY_VERSION_MAX" run_tests "$@" +fi diff --git a/src/dedalus_labs/_base_client.py b/src/dedalus_labs/_base_client.py index d94d0a0..c8d001a 100644 --- a/src/dedalus_labs/_base_client.py +++ b/src/dedalus_labs/_base_client.py @@ -1247,9 +1247,12 @@ def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + ) return self.request(cast_to, opts) def put( @@ -1767,9 +1770,12 @@ async def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options) + opts = FinalRequestOptions.construct( + method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options + ) return await self.request(cast_to, opts) async def put( diff --git a/src/dedalus_labs/_client.py b/src/dedalus_labs/_client.py index 8ea8e30..59086e8 100644 --- a/src/dedalus_labs/_client.py +++ b/src/dedalus_labs/_client.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from typing import Any, Dict, Mapping, cast +from typing import TYPE_CHECKING, Any, Dict, Mapping, cast from typing_extensions import Self, Literal, override import httpx @@ -21,8 +21,9 @@ not_given, ) from ._utils import is_given, get_async_library +from ._compat import cached_property +from ._models import FinalRequestOptions from ._version import __version__ -from .resources import root, health, images, models, embeddings from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError from ._base_client import ( @@ -30,8 +31,15 @@ SyncAPIClient, AsyncAPIClient, ) -from .resources.chat import chat -from .resources.audio import audio +from .lib.mcp import prepare_mcp_request, prepare_mcp_request_sync + +if TYPE_CHECKING: + from .resources import chat, audio, images, models, embeddings + from .resources.images import ImagesResource, AsyncImagesResource + from .resources.models import ModelsResource, AsyncModelsResource + from .resources.chat.chat import ChatResource, AsyncChatResource + from .resources.embeddings import EmbeddingsResource, AsyncEmbeddingsResource + from .resources.audio.audio import AudioResource, AsyncAudioResource __all__ = [ "ENVIRONMENTS", @@ -47,24 +55,19 @@ ENVIRONMENTS: Dict[str, str] = { "production": "https://api.dedaluslabs.ai", - "development": "http://localhost:8080", + "development": "http://localhost:4010", } class Dedalus(SyncAPIClient): - root: root.RootResource - health: health.HealthResource - models: models.ModelsResource - embeddings: embeddings.EmbeddingsResource - audio: audio.AudioResource - images: images.ImagesResource - chat: chat.ChatResource - with_raw_response: DedalusWithRawResponse - with_streaming_response: DedalusWithStreamedResponse - # client options api_key: str | None - organization: str | None + x_api_key: str | None + as_base_url: str | None + dedalus_org_id: str | None + provider: str | None + provider_key: str | None + provider_model: str | None _environment: Literal["production", "development"] | NotGiven @@ -72,7 +75,12 @@ def __init__( self, *, api_key: str | None = None, - organization: str | None = None, + x_api_key: str | None = None, + as_base_url: str | None = None, + dedalus_org_id: str | None = None, + provider: str | None = None, + provider_key: str | None = None, + provider_model: str | None = None, environment: Literal["production", "development"] | NotGiven = not_given, base_url: str | httpx.URL | None | NotGiven = not_given, timeout: float | Timeout | None | NotGiven = not_given, @@ -97,15 +105,40 @@ def __init__( This automatically infers the following arguments from their corresponding environment variables if they are not provided: - `api_key` from `DEDALUS_API_KEY` - - `organization` from `DEDALUS_ORG_ID` + - `x_api_key` from `DEDALUS_X_API_KEY` + - `as_base_url` from `DEDALUS_AS_URL` + - `dedalus_org_id` from `DEDALUS_ORG_ID` + - `provider` from `DEDALUS_PROVIDER` + - `provider_key` from `DEDALUS_PROVIDER_KEY` + - `provider_model` from `DEDALUS_PROVIDER_MODEL` """ if api_key is None: api_key = os.environ.get("DEDALUS_API_KEY") self.api_key = api_key - if organization is None: - organization = os.environ.get("DEDALUS_ORG_ID") - self.organization = organization + if x_api_key is None: + x_api_key = os.environ.get("DEDALUS_X_API_KEY") + self.x_api_key = x_api_key + + if as_base_url is None: + as_base_url = os.environ.get("DEDALUS_AS_URL") + self.as_base_url = as_base_url + + if dedalus_org_id is None: + dedalus_org_id = os.environ.get("DEDALUS_ORG_ID") + self.dedalus_org_id = dedalus_org_id + + if provider is None: + provider = os.environ.get("DEDALUS_PROVIDER") + self.provider = provider + + if provider_key is None: + provider_key = os.environ.get("DEDALUS_PROVIDER_KEY") + self.provider_key = provider_key + + if provider_model is None: + provider_model = os.environ.get("DEDALUS_PROVIDER_MODEL") + self.provider_model = provider_model self._environment = environment @@ -148,15 +181,49 @@ def __init__( self._default_stream_cls = Stream - self.root = root.RootResource(self) - self.health = health.HealthResource(self) - self.models = models.ModelsResource(self) - self.embeddings = embeddings.EmbeddingsResource(self) - self.audio = audio.AudioResource(self) - self.images = images.ImagesResource(self) - self.chat = chat.ChatResource(self) - self.with_raw_response = DedalusWithRawResponse(self) - self.with_streaming_response = DedalusWithStreamedResponse(self) + @override + def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions: + if options.json_data and isinstance(options.json_data, dict): + options.json_data = prepare_mcp_request_sync(options.json_data, self.as_base_url, self._client) + return super()._prepare_options(options) + + @cached_property + def models(self) -> ModelsResource: + from .resources.models import ModelsResource + + return ModelsResource(self) + + @cached_property + def embeddings(self) -> EmbeddingsResource: + from .resources.embeddings import EmbeddingsResource + + return EmbeddingsResource(self) + + @cached_property + def audio(self) -> AudioResource: + from .resources.audio import AudioResource + + return AudioResource(self) + + @cached_property + def images(self) -> ImagesResource: + from .resources.images import ImagesResource + + return ImagesResource(self) + + @cached_property + def chat(self) -> ChatResource: + from .resources.chat import ChatResource + + return ChatResource(self) + + @cached_property + def with_raw_response(self) -> DedalusWithRawResponse: + return DedalusWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> DedalusWithStreamedResponse: + return DedalusWithStreamedResponse(self) @property @override @@ -166,11 +233,22 @@ def qs(self) -> Querystring: @property @override def auth_headers(self) -> dict[str, str]: + return {**self._bearer, **self._api_key_auth} + + @property + def _bearer(self) -> dict[str, str]: api_key = self.api_key if api_key is None: return {} return {"Authorization": f"Bearer {api_key}"} + @property + def _api_key_auth(self) -> dict[str, str]: + x_api_key = self.x_api_key + if x_api_key is None: + return {} + return {"x-api-key": x_api_key} + @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -179,25 +257,33 @@ def default_headers(self) -> dict[str, str | Omit]: "X-Stainless-Async": "false", "User-Agent": "Dedalus-SDK", "X-SDK-Version": "1.0.0", + "X-Provider": self.provider if self.provider is not None else Omit(), + "X-Provider-Key": self.provider_key if self.provider_key is not None else Omit(), **self._custom_headers, } @override def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: - if self.api_key and headers.get("Authorization"): + if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit): return - if isinstance(custom_headers.get("Authorization"), Omit): + + if headers.get("x-api-key") or isinstance(custom_headers.get("x-api-key"), Omit): return raise TypeError( - '"Could not resolve authentication method. Expected the api_key to be set. Or for the `Authorization` headers to be explicitly omitted"' + "Could not resolve authentication method. Expected either api_key or x_api_key to be set. Or for one of the `Authorization` or `x-api-key` headers to be explicitly omitted" ) def copy( self, *, api_key: str | None = None, - organization: str | None = None, + x_api_key: str | None = None, + as_base_url: str | None = None, + dedalus_org_id: str | None = None, + provider: str | None = None, + provider_key: str | None = None, + provider_model: str | None = None, environment: Literal["production", "development"] | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, @@ -233,7 +319,12 @@ def copy( http_client = http_client or self._client return self.__class__( api_key=api_key or self.api_key, - organization=organization or self.organization, + x_api_key=x_api_key or self.x_api_key, + as_base_url=as_base_url or self.as_base_url, + dedalus_org_id=dedalus_org_id or self.dedalus_org_id, + provider=provider or self.provider, + provider_key=provider_key or self.provider_key, + provider_model=provider_model or self.provider_model, base_url=base_url or self.base_url, environment=environment or self._environment, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, @@ -283,19 +374,14 @@ def _make_status_error( class AsyncDedalus(AsyncAPIClient): - root: root.AsyncRootResource - health: health.AsyncHealthResource - models: models.AsyncModelsResource - embeddings: embeddings.AsyncEmbeddingsResource - audio: audio.AsyncAudioResource - images: images.AsyncImagesResource - chat: chat.AsyncChatResource - with_raw_response: AsyncDedalusWithRawResponse - with_streaming_response: AsyncDedalusWithStreamedResponse - # client options api_key: str | None - organization: str | None + x_api_key: str | None + as_base_url: str | None + dedalus_org_id: str | None + provider: str | None + provider_key: str | None + provider_model: str | None _environment: Literal["production", "development"] | NotGiven @@ -303,7 +389,12 @@ def __init__( self, *, api_key: str | None = None, - organization: str | None = None, + x_api_key: str | None = None, + as_base_url: str | None = None, + dedalus_org_id: str | None = None, + provider: str | None = None, + provider_key: str | None = None, + provider_model: str | None = None, environment: Literal["production", "development"] | NotGiven = not_given, base_url: str | httpx.URL | None | NotGiven = not_given, timeout: float | Timeout | None | NotGiven = not_given, @@ -328,15 +419,40 @@ def __init__( This automatically infers the following arguments from their corresponding environment variables if they are not provided: - `api_key` from `DEDALUS_API_KEY` - - `organization` from `DEDALUS_ORG_ID` + - `x_api_key` from `DEDALUS_X_API_KEY` + - `as_base_url` from `DEDALUS_AS_URL` + - `dedalus_org_id` from `DEDALUS_ORG_ID` + - `provider` from `DEDALUS_PROVIDER` + - `provider_key` from `DEDALUS_PROVIDER_KEY` + - `provider_model` from `DEDALUS_PROVIDER_MODEL` """ if api_key is None: api_key = os.environ.get("DEDALUS_API_KEY") self.api_key = api_key - if organization is None: - organization = os.environ.get("DEDALUS_ORG_ID") - self.organization = organization + if x_api_key is None: + x_api_key = os.environ.get("DEDALUS_X_API_KEY") + self.x_api_key = x_api_key + + if as_base_url is None: + as_base_url = os.environ.get("DEDALUS_AS_URL") + self.as_base_url = as_base_url + + if dedalus_org_id is None: + dedalus_org_id = os.environ.get("DEDALUS_ORG_ID") + self.dedalus_org_id = dedalus_org_id + + if provider is None: + provider = os.environ.get("DEDALUS_PROVIDER") + self.provider = provider + + if provider_key is None: + provider_key = os.environ.get("DEDALUS_PROVIDER_KEY") + self.provider_key = provider_key + + if provider_model is None: + provider_model = os.environ.get("DEDALUS_PROVIDER_MODEL") + self.provider_model = provider_model self._environment = environment @@ -379,15 +495,49 @@ def __init__( self._default_stream_cls = AsyncStream - self.root = root.AsyncRootResource(self) - self.health = health.AsyncHealthResource(self) - self.models = models.AsyncModelsResource(self) - self.embeddings = embeddings.AsyncEmbeddingsResource(self) - self.audio = audio.AsyncAudioResource(self) - self.images = images.AsyncImagesResource(self) - self.chat = chat.AsyncChatResource(self) - self.with_raw_response = AsyncDedalusWithRawResponse(self) - self.with_streaming_response = AsyncDedalusWithStreamedResponse(self) + @override + async def _prepare_options(self, options: FinalRequestOptions) -> FinalRequestOptions: + if options.json_data and isinstance(options.json_data, dict): + options.json_data = await prepare_mcp_request(options.json_data, self.as_base_url, self._client) + return await super()._prepare_options(options) + + @cached_property + def models(self) -> AsyncModelsResource: + from .resources.models import AsyncModelsResource + + return AsyncModelsResource(self) + + @cached_property + def embeddings(self) -> AsyncEmbeddingsResource: + from .resources.embeddings import AsyncEmbeddingsResource + + return AsyncEmbeddingsResource(self) + + @cached_property + def audio(self) -> AsyncAudioResource: + from .resources.audio import AsyncAudioResource + + return AsyncAudioResource(self) + + @cached_property + def images(self) -> AsyncImagesResource: + from .resources.images import AsyncImagesResource + + return AsyncImagesResource(self) + + @cached_property + def chat(self) -> AsyncChatResource: + from .resources.chat import AsyncChatResource + + return AsyncChatResource(self) + + @cached_property + def with_raw_response(self) -> AsyncDedalusWithRawResponse: + return AsyncDedalusWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncDedalusWithStreamedResponse: + return AsyncDedalusWithStreamedResponse(self) @property @override @@ -397,11 +547,22 @@ def qs(self) -> Querystring: @property @override def auth_headers(self) -> dict[str, str]: + return {**self._bearer, **self._api_key_auth} + + @property + def _bearer(self) -> dict[str, str]: api_key = self.api_key if api_key is None: return {} return {"Authorization": f"Bearer {api_key}"} + @property + def _api_key_auth(self) -> dict[str, str]: + x_api_key = self.x_api_key + if x_api_key is None: + return {} + return {"x-api-key": x_api_key} + @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -410,25 +571,33 @@ def default_headers(self) -> dict[str, str | Omit]: "X-Stainless-Async": f"async:{get_async_library()}", "User-Agent": "Dedalus-SDK", "X-SDK-Version": "1.0.0", + "X-Provider": self.provider if self.provider is not None else Omit(), + "X-Provider-Key": self.provider_key if self.provider_key is not None else Omit(), **self._custom_headers, } @override def _validate_headers(self, headers: Headers, custom_headers: Headers) -> None: - if self.api_key and headers.get("Authorization"): + if headers.get("Authorization") or isinstance(custom_headers.get("Authorization"), Omit): return - if isinstance(custom_headers.get("Authorization"), Omit): + + if headers.get("x-api-key") or isinstance(custom_headers.get("x-api-key"), Omit): return raise TypeError( - '"Could not resolve authentication method. Expected the api_key to be set. Or for the `Authorization` headers to be explicitly omitted"' + "Could not resolve authentication method. Expected either api_key or x_api_key to be set. Or for one of the `Authorization` or `x-api-key` headers to be explicitly omitted" ) def copy( self, *, api_key: str | None = None, - organization: str | None = None, + x_api_key: str | None = None, + as_base_url: str | None = None, + dedalus_org_id: str | None = None, + provider: str | None = None, + provider_key: str | None = None, + provider_model: str | None = None, environment: Literal["production", "development"] | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, @@ -464,7 +633,12 @@ def copy( http_client = http_client or self._client return self.__class__( api_key=api_key or self.api_key, - organization=organization or self.organization, + x_api_key=x_api_key or self.x_api_key, + as_base_url=as_base_url or self.as_base_url, + dedalus_org_id=dedalus_org_id or self.dedalus_org_id, + provider=provider or self.provider, + provider_key=provider_key or self.provider_key, + provider_model=provider_model or self.provider_model, base_url=base_url or self.base_url, environment=environment or self._environment, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, @@ -514,47 +688,151 @@ def _make_status_error( class DedalusWithRawResponse: + _client: Dedalus + def __init__(self, client: Dedalus) -> None: - self.root = root.RootResourceWithRawResponse(client.root) - self.health = health.HealthResourceWithRawResponse(client.health) - self.models = models.ModelsResourceWithRawResponse(client.models) - self.embeddings = embeddings.EmbeddingsResourceWithRawResponse(client.embeddings) - self.audio = audio.AudioResourceWithRawResponse(client.audio) - self.images = images.ImagesResourceWithRawResponse(client.images) - self.chat = chat.ChatResourceWithRawResponse(client.chat) + self._client = client + + @cached_property + def models(self) -> models.ModelsResourceWithRawResponse: + from .resources.models import ModelsResourceWithRawResponse + + return ModelsResourceWithRawResponse(self._client.models) + + @cached_property + def embeddings(self) -> embeddings.EmbeddingsResourceWithRawResponse: + from .resources.embeddings import EmbeddingsResourceWithRawResponse + + return EmbeddingsResourceWithRawResponse(self._client.embeddings) + + @cached_property + def audio(self) -> audio.AudioResourceWithRawResponse: + from .resources.audio import AudioResourceWithRawResponse + + return AudioResourceWithRawResponse(self._client.audio) + + @cached_property + def images(self) -> images.ImagesResourceWithRawResponse: + from .resources.images import ImagesResourceWithRawResponse + + return ImagesResourceWithRawResponse(self._client.images) + + @cached_property + def chat(self) -> chat.ChatResourceWithRawResponse: + from .resources.chat import ChatResourceWithRawResponse + + return ChatResourceWithRawResponse(self._client.chat) class AsyncDedalusWithRawResponse: + _client: AsyncDedalus + def __init__(self, client: AsyncDedalus) -> None: - self.root = root.AsyncRootResourceWithRawResponse(client.root) - self.health = health.AsyncHealthResourceWithRawResponse(client.health) - self.models = models.AsyncModelsResourceWithRawResponse(client.models) - self.embeddings = embeddings.AsyncEmbeddingsResourceWithRawResponse(client.embeddings) - self.audio = audio.AsyncAudioResourceWithRawResponse(client.audio) - self.images = images.AsyncImagesResourceWithRawResponse(client.images) - self.chat = chat.AsyncChatResourceWithRawResponse(client.chat) + self._client = client + + @cached_property + def models(self) -> models.AsyncModelsResourceWithRawResponse: + from .resources.models import AsyncModelsResourceWithRawResponse + + return AsyncModelsResourceWithRawResponse(self._client.models) + + @cached_property + def embeddings(self) -> embeddings.AsyncEmbeddingsResourceWithRawResponse: + from .resources.embeddings import AsyncEmbeddingsResourceWithRawResponse + + return AsyncEmbeddingsResourceWithRawResponse(self._client.embeddings) + + @cached_property + def audio(self) -> audio.AsyncAudioResourceWithRawResponse: + from .resources.audio import AsyncAudioResourceWithRawResponse + + return AsyncAudioResourceWithRawResponse(self._client.audio) + + @cached_property + def images(self) -> images.AsyncImagesResourceWithRawResponse: + from .resources.images import AsyncImagesResourceWithRawResponse + + return AsyncImagesResourceWithRawResponse(self._client.images) + + @cached_property + def chat(self) -> chat.AsyncChatResourceWithRawResponse: + from .resources.chat import AsyncChatResourceWithRawResponse + + return AsyncChatResourceWithRawResponse(self._client.chat) class DedalusWithStreamedResponse: + _client: Dedalus + def __init__(self, client: Dedalus) -> None: - self.root = root.RootResourceWithStreamingResponse(client.root) - self.health = health.HealthResourceWithStreamingResponse(client.health) - self.models = models.ModelsResourceWithStreamingResponse(client.models) - self.embeddings = embeddings.EmbeddingsResourceWithStreamingResponse(client.embeddings) - self.audio = audio.AudioResourceWithStreamingResponse(client.audio) - self.images = images.ImagesResourceWithStreamingResponse(client.images) - self.chat = chat.ChatResourceWithStreamingResponse(client.chat) + self._client = client + + @cached_property + def models(self) -> models.ModelsResourceWithStreamingResponse: + from .resources.models import ModelsResourceWithStreamingResponse + + return ModelsResourceWithStreamingResponse(self._client.models) + + @cached_property + def embeddings(self) -> embeddings.EmbeddingsResourceWithStreamingResponse: + from .resources.embeddings import EmbeddingsResourceWithStreamingResponse + + return EmbeddingsResourceWithStreamingResponse(self._client.embeddings) + + @cached_property + def audio(self) -> audio.AudioResourceWithStreamingResponse: + from .resources.audio import AudioResourceWithStreamingResponse + + return AudioResourceWithStreamingResponse(self._client.audio) + + @cached_property + def images(self) -> images.ImagesResourceWithStreamingResponse: + from .resources.images import ImagesResourceWithStreamingResponse + + return ImagesResourceWithStreamingResponse(self._client.images) + + @cached_property + def chat(self) -> chat.ChatResourceWithStreamingResponse: + from .resources.chat import ChatResourceWithStreamingResponse + + return ChatResourceWithStreamingResponse(self._client.chat) class AsyncDedalusWithStreamedResponse: + _client: AsyncDedalus + def __init__(self, client: AsyncDedalus) -> None: - self.root = root.AsyncRootResourceWithStreamingResponse(client.root) - self.health = health.AsyncHealthResourceWithStreamingResponse(client.health) - self.models = models.AsyncModelsResourceWithStreamingResponse(client.models) - self.embeddings = embeddings.AsyncEmbeddingsResourceWithStreamingResponse(client.embeddings) - self.audio = audio.AsyncAudioResourceWithStreamingResponse(client.audio) - self.images = images.AsyncImagesResourceWithStreamingResponse(client.images) - self.chat = chat.AsyncChatResourceWithStreamingResponse(client.chat) + self._client = client + + @cached_property + def models(self) -> models.AsyncModelsResourceWithStreamingResponse: + from .resources.models import AsyncModelsResourceWithStreamingResponse + + return AsyncModelsResourceWithStreamingResponse(self._client.models) + + @cached_property + def embeddings(self) -> embeddings.AsyncEmbeddingsResourceWithStreamingResponse: + from .resources.embeddings import AsyncEmbeddingsResourceWithStreamingResponse + + return AsyncEmbeddingsResourceWithStreamingResponse(self._client.embeddings) + + @cached_property + def audio(self) -> audio.AsyncAudioResourceWithStreamingResponse: + from .resources.audio import AsyncAudioResourceWithStreamingResponse + + return AsyncAudioResourceWithStreamingResponse(self._client.audio) + + @cached_property + def images(self) -> images.AsyncImagesResourceWithStreamingResponse: + from .resources.images import AsyncImagesResourceWithStreamingResponse + + return AsyncImagesResourceWithStreamingResponse(self._client.images) + + @cached_property + def chat(self) -> chat.AsyncChatResourceWithStreamingResponse: + from .resources.chat import AsyncChatResourceWithStreamingResponse + + return AsyncChatResourceWithStreamingResponse(self._client.chat) Client = Dedalus diff --git a/src/dedalus_labs/_constants.py b/src/dedalus_labs/_constants.py index 6ddf2c7..733a5e9 100644 --- a/src/dedalus_labs/_constants.py +++ b/src/dedalus_labs/_constants.py @@ -7,8 +7,8 @@ # default timeout is 1 minute DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0) -DEFAULT_MAX_RETRIES = 2 +DEFAULT_MAX_RETRIES = 0 DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20) -INITIAL_RETRY_DELAY = 0.5 -MAX_RETRY_DELAY = 8.0 +INITIAL_RETRY_DELAY = 0.1 +MAX_RETRY_DELAY = 3.0 diff --git a/src/dedalus_labs/_exceptions.py b/src/dedalus_labs/_exceptions.py index b1b6b06..cafbf3e 100644 --- a/src/dedalus_labs/_exceptions.py +++ b/src/dedalus_labs/_exceptions.py @@ -2,10 +2,17 @@ from __future__ import annotations +from typing import TYPE_CHECKING, Any, Optional, cast from typing_extensions import Literal import httpx +from ._utils import is_dict +from ._models import construct_type + +if TYPE_CHECKING: + from .types.chat import ChatCompletion + __all__ = [ "BadRequestError", "AuthenticationError", @@ -15,6 +22,8 @@ "UnprocessableEntityError", "RateLimitError", "InternalServerError", + "LengthFinishReasonError", + "ContentFilterFinishReasonError", ] @@ -37,12 +46,25 @@ class APIError(DedalusError): If there was no response associated with this error then it will be `None`. """ - def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None: # noqa: ARG002 + code: Optional[str] = None + param: Optional[str] = None + type: Optional[str] + + def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None: super().__init__(message) self.request = request self.message = message self.body = body + if is_dict(body): + self.code = cast(Any, construct_type(type_=Optional[str], value=body.get("code"))) + self.param = cast(Any, construct_type(type_=Optional[str], value=body.get("param"))) + self.type = cast(Any, construct_type(type_=str, value=body.get("type"))) + else: + self.code = None + self.param = None + self.type = None + class APIResponseValidationError(APIError): response: httpx.Response @@ -59,11 +81,13 @@ class APIStatusError(APIError): response: httpx.Response status_code: int + request_id: str | None def __init__(self, message: str, *, response: httpx.Response, body: object | None) -> None: super().__init__(message, response.request, body=body) self.response = response self.status_code = response.status_code + self.request_id = response.headers.get("x-request-id") class APIConnectionError(APIError): @@ -106,3 +130,27 @@ class RateLimitError(APIStatusError): class InternalServerError(APIStatusError): pass + + +class LengthFinishReasonError(DedalusError): + completion: ChatCompletion + """The completion that caused this error. + + Note: this will *not* be a complete `ChatCompletion` object when streaming as `usage` + will not be included. + """ + + def __init__(self, *, completion: ChatCompletion) -> None: + msg = "Could not parse response content as the length limit was reached" + if completion.usage: + msg += f" - {completion.usage}" + + super().__init__(msg) + self.completion = completion + + +class ContentFilterFinishReasonError(DedalusError): + def __init__(self) -> None: + super().__init__( + f"Could not parse response content as the request was rejected by the content filter", + ) diff --git a/src/dedalus_labs/_models.py b/src/dedalus_labs/_models.py index ca9500b..75cad7f 100644 --- a/src/dedalus_labs/_models.py +++ b/src/dedalus_labs/_models.py @@ -23,6 +23,7 @@ import pydantic from pydantic.fields import FieldInfo + from ._types import ( Body, IncEx, diff --git a/src/dedalus_labs/_streaming.py b/src/dedalus_labs/_streaming.py index ff041b1..9c7592f 100644 --- a/src/dedalus_labs/_streaming.py +++ b/src/dedalus_labs/_streaming.py @@ -54,30 +54,31 @@ def __stream__(self) -> Iterator[_T]: process_data = self._client._process_response_data iterator = self._iter_events() - for sse in iterator: - if sse.data.startswith("[DONE]"): - break - - if sse.event == "error": - body = sse.data - - try: - body = sse.json() - err_msg = f"{body}" - except Exception: - err_msg = sse.data or f"Error code: {response.status_code}" - - raise self._client._make_status_error( - err_msg, - body=body, - response=self.response, - ) - - if sse.event is None: - yield process_data(data=sse.json(), cast_to=cast_to, response=response) - - # As we might not fully consume the response stream, we need to close it explicitly - response.close() + try: + for sse in iterator: + if sse.data.startswith("[DONE]"): + break + + if sse.event == "error": + body = sse.data + + try: + body = sse.json() + err_msg = f"{body}" + except Exception: + err_msg = sse.data or f"Error code: {response.status_code}" + + raise self._client._make_status_error( + err_msg, + body=body, + response=self.response, + ) + + if sse.event is None: + yield process_data(data=sse.json(), cast_to=cast_to, response=response) + finally: + # Ensure the response is closed even if the consumer doesn't read all data + response.close() def __enter__(self) -> Self: return self @@ -136,30 +137,31 @@ async def __stream__(self) -> AsyncIterator[_T]: process_data = self._client._process_response_data iterator = self._iter_events() - async for sse in iterator: - if sse.data.startswith("[DONE]"): - break - - if sse.event == "error": - body = sse.data - - try: - body = sse.json() - err_msg = f"{body}" - except Exception: - err_msg = sse.data or f"Error code: {response.status_code}" - - raise self._client._make_status_error( - err_msg, - body=body, - response=self.response, - ) - - if sse.event is None: - yield process_data(data=sse.json(), cast_to=cast_to, response=response) - - # As we might not fully consume the response stream, we need to close it explicitly - await response.aclose() + try: + async for sse in iterator: + if sse.data.startswith("[DONE]"): + break + + if sse.event == "error": + body = sse.data + + try: + body = sse.json() + err_msg = f"{body}" + except Exception: + err_msg = sse.data or f"Error code: {response.status_code}" + + raise self._client._make_status_error( + err_msg, + body=body, + response=self.response, + ) + + if sse.event is None: + yield process_data(data=sse.json(), cast_to=cast_to, response=response) + finally: + # Ensure the response is closed even if the consumer doesn't read all data + await response.aclose() async def __aenter__(self) -> Self: return self diff --git a/src/dedalus_labs/_types.py b/src/dedalus_labs/_types.py index bc05f2f..c7221e6 100644 --- a/src/dedalus_labs/_types.py +++ b/src/dedalus_labs/_types.py @@ -243,6 +243,9 @@ class HttpxSendArgs(TypedDict, total=False): if TYPE_CHECKING: # This works because str.__contains__ does not accept object (either in typeshed or at runtime) # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285 + # + # Note: index() and count() methods are intentionally omitted to allow pyright to properly + # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr. class SequenceNotStr(Protocol[_T_co]): @overload def __getitem__(self, index: SupportsIndex, /) -> _T_co: ... @@ -251,8 +254,6 @@ def __getitem__(self, index: slice, /) -> Sequence[_T_co]: ... def __contains__(self, value: object, /) -> bool: ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[_T_co]: ... - def index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ... - def count(self, value: Any, /) -> int: ... def __reversed__(self) -> Iterator[_T_co]: ... else: # just point this to a normal `Sequence` at runtime to avoid having to special case diff --git a/src/dedalus_labs/_version.py b/src/dedalus_labs/_version.py index b944c75..6971f9b 100644 --- a/src/dedalus_labs/_version.py +++ b/src/dedalus_labs/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "dedalus_labs" -__version__ = "0.1.1" # x-release-please-version +__version__ = "0.2.0" # x-release-please-version diff --git a/src/dedalus_labs/lib/_parsing/_completions.py b/src/dedalus_labs/lib/_parsing/_completions.py index 4237034..b9e9a17 100644 --- a/src/dedalus_labs/lib/_parsing/_completions.py +++ b/src/dedalus_labs/lib/_parsing/_completions.py @@ -1,8 +1,9 @@ from __future__ import annotations import json +import logging from typing import TYPE_CHECKING, Any, Dict, Iterable, cast -from typing_extensions import TypeGuard, TypeVar +from typing_extensions import TypeGuard, TypeVar, assert_never import pydantic @@ -12,13 +13,8 @@ from ..._compat import PYDANTIC_V1, model_parse_json from ..._models import construct_type_unchecked from .._pydantic import is_basemodel_type, is_dataclass_like_type, to_strict_json_schema - -if TYPE_CHECKING: - # Import proper ResponseFormat type from server schemas - # At runtime this is just a dict, but for type checking we want the union - ResponseFormatParam = Dict[str, Any] -else: - ResponseFormatParam = Dict[str, Any] +from ..._exceptions import LengthFinishReasonError, ContentFilterFinishReasonError +from ...types.chat.completion_create_params import ResponseFormat as ResponseFormatParam if TYPE_CHECKING: from ...types.chat.completion import ( @@ -41,6 +37,8 @@ ) _default_response_format: None = None +log: logging.Logger = logging.getLogger("dedalus.lib.parsing") + def type_to_response_format_param( response_format: type | ResponseFormatParam | Omit, @@ -107,6 +105,12 @@ def parse_chat_completion( parsed_choices = [] for choice in chat_completion.choices: + if choice.finish_reason == "length": + raise LengthFinishReasonError(completion=chat_completion) + + if choice.finish_reason == "content_filter": + raise ContentFilterFinishReasonError() + message = choice.message tool_calls = [] @@ -129,6 +133,15 @@ def parse_chat_completion( type_=ParsedFunctionToolCall, ) ) + elif getattr(tool_call, "type", None) == "custom": + log.warning( + "Custom tool calls are not callable. Ignoring tool call: %s - %s", + tool_call.id, # type: ignore[attr-defined] + getattr(getattr(tool_call, "custom", None), "name", "unknown"), + stacklevel=2, + ) + elif TYPE_CHECKING: # type: ignore[unreachable] + assert_never(tool_call) else: tool_calls.append(tool_call) @@ -163,8 +176,10 @@ def maybe_parse_content( response_format: type[ResponseFormatT] | ResponseFormatParam | Omit, message: ChoiceMessage | ParsedChatCompletionMessage[object], ) -> ResponseFormatT | None: - if has_rich_response_format(response_format) and getattr(message, "content", None) and not getattr( - message, "refusal", None + if ( + has_rich_response_format(response_format) + and getattr(message, "content", None) + and not getattr(message, "refusal", None) ): return _parse_content(response_format, cast(str, message.content)) @@ -201,7 +216,11 @@ def get_input_tool_by_name( name: str, ) -> Dict[str, Any] | None: return next( - (tool for tool in input_tools if tool.get("type") == "function" and tool.get("function", {}).get("name") == name), + ( + tool + for tool in input_tools + if tool.get("type") == "function" and tool.get("function", {}).get("name") == name + ), None, ) diff --git a/src/dedalus_labs/lib/crypto/__init__.py b/src/dedalus_labs/lib/crypto/__init__.py new file mode 100644 index 0000000..64bbc26 --- /dev/null +++ b/src/dedalus_labs/lib/crypto/__init__.py @@ -0,0 +1,21 @@ +# ============================================================================== +# © 2025 Dedalus Labs, Inc. and affiliates +# Licensed under MIT +# github.com/dedalus-labs/dedalus-sdk-python/LICENSE +# ============================================================================== + +"""Cryptographic utilities for credential encryption.""" + +from .encryption import ( + encrypt_credentials, + fetch_encryption_key, + fetch_encryption_key_sync, + jwk_to_public_key, +) + +__all__ = [ + "encrypt_credentials", + "fetch_encryption_key", + "fetch_encryption_key_sync", + "jwk_to_public_key", +] diff --git a/src/dedalus_labs/lib/crypto/encryption.py b/src/dedalus_labs/lib/crypto/encryption.py new file mode 100644 index 0000000..2e7fbc3 --- /dev/null +++ b/src/dedalus_labs/lib/crypto/encryption.py @@ -0,0 +1,185 @@ +# ============================================================================== +# © 2025 Dedalus Labs, Inc. and affiliates +# Licensed under MIT +# github.com/dedalus-labs/dedalus-sdk-python/LICENSE +# ============================================================================== + +"""Client-side credential encryption using hybrid RSA-OAEP + AES-GCM. + +Credentials are encrypted locally before transmission. The envelope format: + [version:1][wrapped_key:256][nonce:12][ciphertext+tag:variable] + +Requires: uv pip install 'dedalus-labs[auth]' +""" + +from __future__ import annotations + +import base64 +import json +import os +from typing import Any + +try: + from cryptography.hazmat.primitives.asymmetric import padding + from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.ciphers.aead import AESGCM + from cryptography.hazmat.backends import default_backend + + _CRYPTO_AVAILABLE = True +except ImportError: + _CRYPTO_AVAILABLE = False + +# Envelope constants +_ENVELOPE_VERSION = 0x01 +_NONCE_LEN = 12 +_AES_KEY_LEN = 32 + + +def _require_crypto() -> None: + """Raise if cryptography is not installed.""" + if not _CRYPTO_AVAILABLE: + raise ImportError( + "cryptography is required for credential encryption. Install with: uv pip install 'dedalus-labs[auth]'" + ) + + +def _b64url_encode(data: bytes) -> str: + """Base64url encode without padding.""" + return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii") + + +def _b64url_decode(s: str) -> bytes: + """Base64url decode with padding restoration.""" + pad = 4 - len(s) % 4 + if pad != 4: + s += "=" * pad + return base64.urlsafe_b64decode(s) + + +def jwk_to_public_key(jwk: dict[str, Any], min_key_size: int = 2048) -> Any: + """Convert a JWK to an RSA public key. + + Args: + jwk: JWK dict with kty="RSA", n, and e fields. + min_key_size: Minimum key size in bits (default 2048). + + Returns: + RSA public key object. + + Raises: + ImportError: If cryptography is not installed. + ValueError: If JWK is invalid or key too small. + """ + _require_crypto() + + if jwk.get("kty") != "RSA": + raise ValueError(f"expected RSA key type, got: {jwk.get('kty')}") + + try: + n_bytes = _b64url_decode(jwk["n"]) + e_bytes = _b64url_decode(jwk["e"]) + except KeyError as e: + raise ValueError(f"missing required JWK field: {e}") from e + + n = int.from_bytes(n_bytes, "big") + e = int.from_bytes(e_bytes, "big") + + if n.bit_length() < min_key_size: + raise ValueError(f"key size {n.bit_length()} bits below minimum {min_key_size}") + + return RSAPublicNumbers(e, n).public_key(default_backend()) + + +def encrypt_credentials(public_key: Any, credentials: dict[str, Any]) -> str: + """Encrypt credentials using hybrid RSA-OAEP + AES-GCM. + + Args: + public_key: RSA public key from jwk_to_public_key(). + credentials: Credential values to encrypt. + + Returns: + Base64url-encoded encrypted envelope. + + Raises: + ImportError: If cryptography is not installed. + """ + _require_crypto() + + plaintext = json.dumps(credentials, separators=(",", ":")).encode("utf-8") + + # Generate ephemeral AES key and nonce + aes_key = os.urandom(_AES_KEY_LEN) + nonce = os.urandom(_NONCE_LEN) + + # Wrap AES key with RSA-OAEP + wrapped_key = public_key.encrypt( + aes_key, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None, + ), + ) + + # Encrypt with AES-GCM + ciphertext = AESGCM(aes_key).encrypt(nonce, plaintext, None) + + # Assemble envelope: version || wrapped_key || nonce || ciphertext+tag + envelope = bytes([_ENVELOPE_VERSION]) + wrapped_key + nonce + ciphertext + return _b64url_encode(envelope) + + +async def fetch_encryption_key(http_client: Any, as_url: str, key_id: str | None = None) -> Any: + """Fetch encryption public key from authorization server JWKS. + + Args: + http_client: httpx.AsyncClient instance. + as_url: Authorization server base URL. + key_id: Optional specific key ID. + + Returns: + RSA public key object. + + Raises: + ValueError: If no suitable encryption key found. + RuntimeError: On HTTP errors. + """ + url = f"{as_url.rstrip('/')}/.well-known/jwks.json" + response = await http_client.get(url) + if response.status_code != 200: + raise RuntimeError(f"failed to fetch JWKS from {url}: {response.status_code}") + + for key in response.json().get("keys", []): + if key.get("kty") != "RSA" or key.get("use") != "enc": + continue + if key_id and key.get("kid") != key_id: + continue + return jwk_to_public_key(key) + + raise ValueError(f"no RSA encryption key found at {url}") + + +def fetch_encryption_key_sync(http_client: Any, as_url: str, key_id: str | None = None) -> Any: + """Synchronous version of fetch_encryption_key.""" + url = f"{as_url.rstrip('/')}/.well-known/jwks.json" + response = http_client.get(url) + if response.status_code != 200: + raise RuntimeError(f"failed to fetch JWKS from {url}: {response.status_code}") + + for key in response.json().get("keys", []): + if key.get("kty") != "RSA" or key.get("use") != "enc": + continue + if key_id and key.get("kid") != key_id: + continue + return jwk_to_public_key(key) + + raise ValueError(f"no RSA encryption key found at {url}") + + +__all__ = [ + "jwk_to_public_key", + "encrypt_credentials", + "fetch_encryption_key", + "fetch_encryption_key_sync", +] diff --git a/src/dedalus_labs/lib/mcp/__init__.py b/src/dedalus_labs/lib/mcp/__init__.py new file mode 100644 index 0000000..6c4c3e2 --- /dev/null +++ b/src/dedalus_labs/lib/mcp/__init__.py @@ -0,0 +1,52 @@ +# ============================================================================== +# © 2025 Dedalus Labs, Inc. and affiliates +# Licensed under MIT +# github.com/dedalus-labs/dedalus-sdk-python/LICENSE +# ============================================================================== + +"""MCP server integration utilities.""" + +from .protocols import ( + CredentialProtocol, + MCPServerProtocol, + MCPServerRef, + MCPServerWithCredsProtocol, + MCPToolSpec, + is_mcp_server, + normalize_mcp_servers, +) +from .request import ( + EncryptedCredentials, + prepare_mcp_request, + prepare_mcp_request_sync, +) +from .wire import ( + MCPServerWireSpec, + collect_unique_connections, + match_credentials_to_connections, + serialize_connection, + serialize_mcp_servers, + validate_credentials_for_servers, +) + +__all__ = [ + # Protocols + "CredentialProtocol", + "MCPServerProtocol", + "MCPServerRef", + "MCPServerWithCredsProtocol", + "MCPToolSpec", + "is_mcp_server", + "normalize_mcp_servers", + # Wire format + "MCPServerWireSpec", + "collect_unique_connections", + "match_credentials_to_connections", + "serialize_connection", + "serialize_mcp_servers", + "validate_credentials_for_servers", + # Request preparation + "EncryptedCredentials", + "prepare_mcp_request", + "prepare_mcp_request_sync", +] diff --git a/src/dedalus_labs/lib/mcp/protocols.py b/src/dedalus_labs/lib/mcp/protocols.py new file mode 100644 index 0000000..ebec72e --- /dev/null +++ b/src/dedalus_labs/lib/mcp/protocols.py @@ -0,0 +1,135 @@ +# ============================================================================== +# © 2025 Dedalus Labs, Inc. and affiliates +# Licensed under MIT +# github.com/dedalus-labs/dedalus-sdk-python/LICENSE +# ============================================================================== + +"""Structural protocols for MCP server integration.""" + +from __future__ import annotations + +from typing import ( + Any, + Dict, + List, + Tuple, + Union, + Optional, + Protocol, + Sequence, + runtime_checkable, +) + +from typing_extensions import TypeGuard + + +# --- Type Aliases ------------------------------------------------------------ + +MCPServerRef = str # Slug ("org/server") or URL + +# --- Protocols --------------------------------------------------------------- + + +@runtime_checkable +class CredentialProtocol(Protocol): + """Protocol for a single credential binding (e.g., SecretValues).""" + + @property + def connection(self) -> Any: + """Connection this credential binds to (must have .name).""" + ... + + def values_for_encryption(self) -> Dict[str, Any]: + """Return CredentialEnvelope for client-side encryption.""" + ... + + +@runtime_checkable +class ToolsServiceProtocol(Protocol): + """Protocol for tools service from MCPServer.""" + + @property + def _tool_specs(self) -> Dict[str, Any]: ... + + +@runtime_checkable +class MCPServerProtocol(Protocol): + """Structural protocol for MCP servers.""" + + @property + def name(self) -> str: ... + + @property + def url(self) -> Optional[str]: ... + + def serve(self, *args: Any, **kwargs: Any) -> Any: ... + + +@runtime_checkable +class MCPServerWithCredsProtocol(Protocol): + """Extended protocol for MCPServer with credential bindings. + + Used for connection provisioning flow where servers need credentials. + """ + + @property + def name(self) -> str: ... + + @property + def credentials(self) -> Optional[CredentialsProtocol]: ... + + @property + def connection(self) -> Optional[str]: ... + + @property + def tools(self) -> ToolsServiceProtocol: ... + + +@runtime_checkable +class MCPToolSpec(Protocol): + """Duck-typed interface for tool specifications.""" + + @property + def name(self) -> str: ... + + @property + def description(self) -> Optional[str]: ... + + @property + def input_schema(self) -> Dict[str, Any]: ... + + +# --- Helpers ----------------------------------------------------------------- + + +def is_mcp_server(obj: Any) -> TypeGuard[MCPServerProtocol]: + """Check if obj satisfies MCPServerProtocol.""" + return isinstance(obj, MCPServerProtocol) + + +def normalize_mcp_servers( + servers: Union[ + MCPServerRef, + Sequence[Union[MCPServerRef, MCPServerProtocol]], + MCPServerProtocol, + None, + ], +) -> Tuple[List[MCPServerRef], List[MCPServerProtocol]]: + """Split into (string refs, server objects). Caller checks .url to know if serve() is needed.""" + if servers is None: + return [], [] + if isinstance(servers, str): + return [servers], [] + if is_mcp_server(servers): + return [], [servers] # type: ignore[list-item] + + refs: List[MCPServerRef] = [] + objects: List[MCPServerProtocol] = [] + for item in servers: + if isinstance(item, str): + refs.append(item) + elif is_mcp_server(item): + objects.append(item) + else: + refs.append(str(item)) + return refs, objects diff --git a/src/dedalus_labs/lib/mcp/request.py b/src/dedalus_labs/lib/mcp/request.py new file mode 100644 index 0000000..fa7943a --- /dev/null +++ b/src/dedalus_labs/lib/mcp/request.py @@ -0,0 +1,199 @@ +# ============================================================================== +# © 2025 Dedalus Labs, Inc. and affiliates +# Licensed under MIT +# github.com/dedalus-labs/dedalus-sdk-python/LICENSE +# ============================================================================== + +"""MCP request preparation. + +This module handles the client-side preparation of MCP requests: +1. Serializes MCPServer objects to wire format (dicts/strings) +2. Deep copies to protect retry logic from mutation side effects +3. Encrypts credentials client-side before transmission +""" + +from __future__ import annotations + +import copy +import logging +from dataclasses import dataclass +from typing import Any, Dict, List, Optional, Sequence + +from dedalus_labs.types.shared_params.mcp_server_spec import MCPServerSpec +from dedalus_labs.types.shared_params.mcp_servers import MCPServerItem + +from ..crypto import encrypt_credentials, fetch_encryption_key, fetch_encryption_key_sync +from .protocols import CredentialProtocol +from .wire import serialize_mcp_servers + +logger = logging.getLogger(__name__) + +__all__ = [ + "prepare_mcp_request", + "prepare_mcp_request_sync", + "EncryptedCredentials", +] + + +@dataclass(frozen=True) +class EncryptedCredentials: + """Map of connection names to encrypted envelopes (base64url). + + Instantiate with connection names as keyword arguments: + >>> EncryptedCredentials(foo="encrypted...", bar="encrypted...") + """ + + entries: Dict[str, str] + + def __init__(self, **kwargs: str) -> None: + object.__setattr__(self, "entries", dict(kwargs)) + + def to_dict(self) -> Dict[str, str]: + """Return the underlying dict for wire serialization.""" + return self.entries + + +# --------------------------------------------------------------------------- +# Request preparation +# --------------------------------------------------------------------------- + + +async def prepare_mcp_request( + data: Dict[str, Any], + as_url: Optional[str], + http_client: Any, +) -> Dict[str, Any]: + """Serialize mcp_servers, deepcopy, and encrypt credentials. + + Args: + data: Request body dict (modified in place before copy). + as_url: Authorization server URL for fetching encryption key. + http_client: httpx.AsyncClient for key fetch. + + Returns: + A new dict with serialized servers and encrypted credentials. + + """ + # Serialize MCP servers, if provided. + servers = data.get("mcp_servers") + if servers is not None: + data["mcp_servers"] = serialize_mcp_servers(servers) + + # Make a copy to avoid mutation side effects related to SDK retry logic. + data = copy.deepcopy(data) + credentials = data.get("credentials") + + # If credentials are provided, encrypt them on the client side + # and transport them along with the MCP servers. + if credentials and servers and as_url: + try: + public_key = await fetch_encryption_key(http_client, as_url) + encrypted = _encrypt_credentials(credentials, public_key) + if encrypted: + data["mcp_servers"] = _embed_credentials(data["mcp_servers"], encrypted) + data.pop("credentials", None) + except ImportError as err: + msg = "The `cryptography` package is required for authentication. Install: `uv pip install 'dedalus-labs[auth]'`" + raise ImportError(msg) from err + + return data + + +def prepare_mcp_request_sync( + data: Dict[str, Any], + as_url: Optional[str], + http_client: Any, +) -> Dict[str, Any]: + """Sync version of prepare_mcp_request. + + Args: + data: Request body dict (modified in place before copy). + as_url: Authorization server URL for fetching encryption key. + http_client: httpx.Client for key fetch. + + Returns: + A new dict with serialized servers and encrypted credentials. + + """ + # Serialize MCP servers, if provided. + servers = data.get("mcp_servers") + if servers is not None: + data["mcp_servers"] = serialize_mcp_servers(servers) + + # Make a copy to avoid mutation side effects related to SDK retry logic. + data = copy.deepcopy(data) + credentials = data.get("credentials") + + # If credentials are provided, encrypt them on the client side + # and transport them along with the MCP servers. + if credentials and servers and as_url: + try: + public_key = fetch_encryption_key_sync(http_client, as_url) + encrypted = _encrypt_credentials(credentials, public_key) + if encrypted: + data["mcp_servers"] = _embed_credentials(data["mcp_servers"], encrypted) + data.pop("credentials", None) + except ImportError as err: + msg = "cryptography required for credentials. Install: uv pip install 'dedalus-labs[auth]'" + raise ImportError(msg) from err + + return data + + +# --------------------------------------------------------------------------- +# Internal helpers +# --------------------------------------------------------------------------- + + +def _encrypt_credentials( + credentials: Sequence[CredentialProtocol], + public_key: Any, +) -> EncryptedCredentials: + """Encrypt each credential. + + Args: + credentials: Credential objects implementing CredentialProtocol. + public_key: RSA public key from fetch_encryption_key. + + Returns: + EncryptedCredentials with connection names mapped to base64url envelopes. + + """ + encrypted = { + cred.connection.name: encrypt_credentials(public_key, cred.values_for_encryption()) for cred in credentials + } + return EncryptedCredentials(**encrypted) + + +def _embed_credentials( + servers: List[MCPServerItem], + encrypted: EncryptedCredentials, +) -> List[MCPServerSpec]: + """Embed encrypted credentials into each server spec. + + Converts slug strings to full specs and adds credentials to all servers. + + Args: + servers: Serialized MCP servers (slug strings or spec dicts). + encrypted: EncryptedCredentials instance. + + Returns: + List of MCPServerSpec dicts with credentials embedded. + + """ + creds_dict = encrypted.to_dict() + result: List[MCPServerSpec] = [] + + for server in servers: + if isinstance(server, str): + if server.startswith(("http://", "https://")): + result.append({"url": server, "name": server, "credentials": creds_dict}) + else: + result.append({"slug": server, "name": server, "credentials": creds_dict}) + elif isinstance(server, dict): + # Existing spec -> add name (if missing) and credentials + name = server.get("name") or server.get("slug") or server.get("url") or "" + spec: MCPServerSpec = {**server, "name": name, "credentials": creds_dict} + result.append(spec) + + return result diff --git a/src/dedalus_labs/lib/mcp/wire.py b/src/dedalus_labs/lib/mcp/wire.py new file mode 100644 index 0000000..ec67840 --- /dev/null +++ b/src/dedalus_labs/lib/mcp/wire.py @@ -0,0 +1,459 @@ +# ============================================================================== +# © 2025 Dedalus Labs, Inc. and affiliates +# Licensed under MIT +# github.com/dedalus-labs/dedalus-sdk-python/LICENSE +# ============================================================================== + +"""MCP server wire format serialization. + +Converts MCPServer objects and various input formats to the API wire format. +""" + +from __future__ import annotations + +from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, cast + +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator +from typing_extensions import TypeAlias + +from .protocols import MCPServerProtocol, CredentialProtocol, is_mcp_server + +__all__ = [ + # Core types + "MCPServerWireSpec", + # Serialization + "serialize_mcp_servers", + "serialize_credentials", + "serialize_connection", + "serialize_mcp_server_with_creds", + "serialize_tool_specs", + # Credential matching + "match_credentials_to_server", + "match_credentials_to_connections", + "validate_credentials_for_servers", + # Helpers + "build_connection_record", + "collect_unique_connections", +] + + +# --------------------------------------------------------------------------- +# Type Aliases +# --------------------------------------------------------------------------- + +# Serialized wire output: slug string or spec dict +MCPServerWireOutput: TypeAlias = Union[str, Dict[str, Any]] + +# Input types that serialize_mcp_servers accepts +MCPServerInput: TypeAlias = Union[str, Dict[str, Any], MCPServerProtocol] + +# Connection/credential pair for provisioning +ConnectionCredentialPair: TypeAlias = Tuple[Any, CredentialProtocol] + + +# --------------------------------------------------------------------------- +# Wire Format Model (for validation during serialization) +# --------------------------------------------------------------------------- + + +class MCPServerWireSpec(BaseModel): + """MCP server spec for API transmission. + + Wire format: either slug or url (not both). + """ + + model_config = ConfigDict(extra="forbid") + + slug: Optional[str] = Field( + default=None, + pattern=r"^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$", + description="Marketplace identifier (org/name format)", + ) + url: Optional[str] = Field( + default=None, + description="MCP server URL endpoint", + ) + version: Optional[str] = Field( + default=None, + description="Version for MCP servers", + ) + + @model_validator(mode="after") + def validate_slug_or_url(self) -> MCPServerWireSpec: + """Require exactly one of slug or url.""" + has_slug = self.slug is not None + has_url = self.url is not None + + if not has_slug and not has_url: + raise ValueError("requires either 'slug' or 'url'") + if has_slug and has_url: + raise ValueError("cannot have both 'slug' and 'url'") + if has_slug and self.version and self.slug and "@" in self.slug: + raise ValueError("cannot specify both 'version' field and version in slug") + + return self + + @field_validator("url") + @classmethod + def validate_url_format(cls, v: Optional[str]) -> Optional[str]: + """Validate URL scheme.""" + if v is None: + return None + if not v.startswith(("http://", "https://")): + raise ValueError(f"URL must start with http:// or https://, got: {v}") + return v + + def to_wire(self) -> MCPServerWireOutput: + """Convert to wire format. Simple slugs become strings.""" + if self.slug and not self.version: + return self.slug + return self.model_dump(exclude_none=True) + + @classmethod + def from_slug(cls, slug: str, version: Optional[str] = None) -> MCPServerWireSpec: + """Create from slug, extracting version if embedded.""" + if "@" in slug and version is None: + slug, version = slug.rsplit("@", 1) + return cls(slug=slug, version=version) + + @classmethod + def from_url(cls, url: str) -> MCPServerWireSpec: + """Create from direct URL.""" + return cls(url=url) + + +# --------------------------------------------------------------------------- +# MCP Server Serialization +# --------------------------------------------------------------------------- + + +def serialize_mcp_servers( + servers: Union[MCPServerInput, Sequence[MCPServerInput], None], +) -> List[MCPServerWireOutput]: + """Convert mcp_servers input to API wire format. + + Accepts: + - Single slug string ("org/server" or "org/server@v1") + - Single URL string ("https://...") + - Single MCPServer object (from dedalus_mcp) + - Single dict matching MCPServerSpec + - Sequence of any of the above + - None (returns empty list) + + Returns: + List of wire format items (strings or dicts). + + """ + if servers is None: + return [] + if isinstance(servers, str): + return [_serialize_single(servers)] + if isinstance(servers, dict): + return [_serialize_single(cast(Dict[str, Any], servers))] + if is_mcp_server(servers): + return [_serialize_single(cast(MCPServerProtocol, servers))] + + # Sequence of inputs + return [_serialize_single(item) for item in servers] + + +def _serialize_single(item: MCPServerInput) -> MCPServerWireOutput: + """Serialize a single MCP server input to wire format.""" + if isinstance(item, str): + # URL passthrough + if item.startswith(("http://", "https://")): + return item + # Slug with embedded version + if "@" in item: + slug, version = item.rsplit("@", 1) + return MCPServerWireSpec.from_slug(slug, version).to_wire() + # Simple slug + return item + + if is_mcp_server(item): + # MCPServer object - check for URL first + url = getattr(item, "url", None) + if url is not None: + return MCPServerWireSpec.from_url(url).to_wire() + + # Fall back to name (slug) + name = getattr(item, "name", None) + if name is not None: + return name + + raise ValueError("MCP server must have either 'url' or 'name' attribute") + + if isinstance(item, dict): + # Validate and convert dict + return MCPServerWireSpec.model_validate(item).to_wire() + + # Fallback for unknown types + return str(item) + + +# --------------------------------------------------------------------------- +# Credential Serialization +# --------------------------------------------------------------------------- + + +def serialize_credentials(creds: Optional[CredentialProtocol]) -> Optional[Dict[str, Any]]: + """Serialize Credentials schema to wire format. + + Args: + creds: Credentials object with to_dict() method, or None. + + Returns: + Dict mapping field names to binding specs, or None. + """ + if creds is None: + return None + if hasattr(creds, "to_dict"): + return creds.to_dict() + return None + + +def serialize_tool_specs(tools_service: Any) -> Dict[str, Dict[str, Any]]: + """Serialize tool specs to intents manifest format. + + Args: + tools_service: Tools service from MCPServer with _tool_specs attribute. + + Returns: + Dict mapping tool names to {description, schema} dicts. + """ + specs = getattr(tools_service, "_tool_specs", {}) + if not specs: + return {} + + manifest: Dict[str, Dict[str, Any]] = {} + for name, spec in specs.items(): + if hasattr(spec, "description"): + manifest[name] = { + "description": spec.description, + "schema": getattr(spec, "input_schema", {}), + } + elif isinstance(spec, dict): + manifest[name] = { + "description": spec.get("description", ""), + "schema": spec.get("input_schema", {}), + } + return manifest + + +def serialize_mcp_server_with_creds(server: MCPServerProtocol) -> Dict[str, Any]: + """Serialize MCPServer with credentials for connection provisioning. + + Args: + server: MCPServer object with credentials/connection attributes. + + Returns: + Dict with server config (credential values added separately). + """ + result: Dict[str, Any] = {"name": getattr(server, "name", "unknown")} + + creds = getattr(server, "credentials", None) + if creds is not None: + creds_dict = serialize_credentials(creds) + if creds_dict: + result["credentials"] = creds_dict + + connection = getattr(server, "connection", None) + if connection: + result["connection"] = connection + + return result + + +# --------------------------------------------------------------------------- +# Connection Serialization +# --------------------------------------------------------------------------- + + +def serialize_connection(connection: Any) -> Dict[str, Any]: + """Serialize a Connection object to wire format. + + Works with any Connection-like object that provides: + - to_dict() method, OR + - name, base_url, timeout_ms attributes + + Args: + connection: Connection object or dict. + + Returns: + Dict with name, base_url, timeout_ms fields. + """ + if hasattr(connection, "to_dict"): + return connection.to_dict() + if isinstance(connection, dict): + return connection + # Duck-type extraction + return { + "name": getattr(connection, "name", "unknown"), + "base_url": getattr(connection, "base_url", None), + "timeout_ms": getattr(connection, "timeout_ms", 30000), + } + + +def collect_unique_connections(servers: Sequence[MCPServerProtocol]) -> List[Any]: + """Collect unique connections from multiple MCPServer instances. + + Args: + servers: List of MCPServer objects. + + Returns: + List of unique Connection objects. + """ + seen_names: set[str] = set() + unique: List[Any] = [] + + for server in servers: + connections = getattr(server, "connections", {}) + # Handle both dict and list formats + conn_list = list(connections.values()) if isinstance(connections, dict) else list(connections or []) + + for conn in conn_list: + name = ( + getattr(conn, "name", None) + if hasattr(conn, "name") + else conn.get("name") + if isinstance(conn, dict) + else None + ) + if name and name not in seen_names: + seen_names.add(name) + unique.append(conn) + + return unique + + +# --------------------------------------------------------------------------- +# Credential Matching +# --------------------------------------------------------------------------- + + +def match_credentials_to_server( + server: MCPServerProtocol, + credentials: Dict[str, Dict[str, Any]], +) -> Optional[Dict[str, Any]]: + """Match credentials dict to server.connection. + + Args: + server: MCPServer with optional connection field. + credentials: Dict mapping connection names to credential dicts. + + Returns: + Matched credential values, or None if no connection. + + Raises: + KeyError: If server.connection not found in credentials. + + """ + connection = getattr(server, "connection", None) + if not connection: + return None + if connection not in credentials: + raise KeyError(f"No credentials found for connection '{connection}'") + return credentials[connection] + + +def match_credentials_to_connections( + connections: Sequence[Any], + credentials: Sequence[CredentialProtocol], +) -> List[ConnectionCredentialPair]: + """Match Credential objects to their Connection definitions. + + Args: + connections: List of Connection objects. + credentials: List of Credential objects. + + Returns: + List of (connection, credential) pairs. + + Raises: + ValueError: If any connection lacks a matching credential. + """ + # Build lookup by connection name + creds_by_name: Dict[str, CredentialProtocol] = {} + for cred in credentials: + if hasattr(cred, "connection"): + name = getattr(cred.connection, "name", None) + elif isinstance(cred, dict): + name = cred.get("connection_name") + else: + continue + if name: + creds_by_name[name] = cred + + # Match connections to credentials + pairs: List[ConnectionCredentialPair] = [] + missing: List[str] = [] + + for conn in connections: + name = ( + getattr(conn, "name", None) + if hasattr(conn, "name") + else conn.get("name") + if isinstance(conn, dict) + else None + ) + if name and name in creds_by_name: + pairs.append((conn, creds_by_name[name])) + elif name: + missing.append(name) + + if missing: + raise ValueError( + f"Missing credentials for connections: {sorted(missing)}. " + f"Each Connection declared in mcp_servers must have a corresponding Credential." + ) + + return pairs + + +def validate_credentials_for_servers( + servers: Sequence[MCPServerProtocol], + credentials: Sequence[CredentialProtocol], +) -> List[ConnectionCredentialPair]: + """Validate that all connections across servers have credentials. + + Main entry point for SDK initialization validation. Collects unique + connections from all servers and ensures each has a matching Credential. + + Args: + servers: List of MCPServer objects. + credentials: List of Credential objects. + + Returns: + List of (connection, credential) pairs ready for provisioning. + + Raises: + ValueError: If any connection lacks a credential (fail-fast at init). + """ + connections = collect_unique_connections(servers) + return match_credentials_to_connections(connections, credentials) + + +def build_connection_record( + server: MCPServerProtocol, + credentials: Dict[str, Dict[str, Any]], + org_id: str, +) -> Dict[str, Any]: + """Build a connection record. + + Args: + server: MCPServer object. + credentials: Dict mapping connection names to credential values. + org_id: Organization ID for the connection. + + Returns: + Connection record payload. + + """ + matched_creds = match_credentials_to_server(server, credentials) + + return { + "org_id": org_id, + "connection": getattr(server, "connection", None), + "credentials": serialize_credentials(getattr(server, "credentials", None)), + "credential_values": matched_creds, + } diff --git a/src/dedalus_labs/lib/runner/__init__.py b/src/dedalus_labs/lib/runner/__init__.py index 8c3d478..3edd196 100644 --- a/src/dedalus_labs/lib/runner/__init__.py +++ b/src/dedalus_labs/lib/runner/__init__.py @@ -4,25 +4,27 @@ # github.com/dedalus-labs/dedalus-sdk-python/LICENSE # ============================================================================== +"""Dedalus runner module.""" + from __future__ import annotations -from .core import DedalusRunner +from ..utils._schemas import to_schema +from .core import DedalusRunner, MCPServersInput from .types import ( - Tool, - Message, - ToolCall, JsonValue, - ToolResult, - PolicyInput, - ToolHandler, + Message, PolicyContext, PolicyFunction, + PolicyInput, + Tool, + ToolCall, + ToolHandler, + ToolResult, ) -from ..utils import to_schema __all__ = [ "DedalusRunner", - # Types + "MCPServersInput", "JsonValue", "Message", "PolicyContext", @@ -33,4 +35,4 @@ "ToolHandler", "ToolResult", "to_schema", -] \ No newline at end of file +] diff --git a/src/dedalus_labs/lib/runner/core.py b/src/dedalus_labs/lib/runner/core.py index 4a3ece7..8ed01a4 100644 --- a/src/dedalus_labs/lib/runner/core.py +++ b/src/dedalus_labs/lib/runner/core.py @@ -9,17 +9,37 @@ import json import asyncio import inspect -from typing import TYPE_CHECKING, Any, Dict, Literal, Callable, Iterator, Protocol, AsyncIterator +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Literal, + Callable, + Iterator, + Protocol, + AsyncIterator, + Sequence, + Union, +) from dataclasses import field, asdict, dataclass -from dedalus_labs import Dedalus, AsyncDedalus - if TYPE_CHECKING: - from ...types.dedalus_model import DedalusModel + from ...types.shared.dedalus_model import DedalusModel +from ..._client import Dedalus, AsyncDedalus from .types import Message, ToolCall, JsonValue, ToolResult, PolicyInput, PolicyContext -from ..utils import to_schema +from ...types.shared import MCPToolResult +from ..mcp import serialize_mcp_servers, MCPServerProtocol + +# Type alias for mcp_servers parameter - accepts strings, server objects, or mixed lists +MCPServersInput = Union[ + str, # Single slug or URL + MCPServerProtocol, # MCP server object + Sequence[Union[str, MCPServerProtocol, Dict[str, Any]]], # Mixed list + None, +] +from ..utils._schemas import to_schema def _process_policy(policy: PolicyInput, context: PolicyContext) -> Dict[str, JsonValue]: @@ -43,6 +63,14 @@ def _process_policy(policy: PolicyInput, context: PolicyContext) -> Dict[str, Js return {} +def _extract_mcp_results(response: Any) -> list[MCPToolResult]: + """Extract MCP tool results from API response.""" + mcp_results = getattr(response, "mcp_tool_results", None) + if not mcp_results: + return [] + return [item if isinstance(item, MCPToolResult) else MCPToolResult.model_validate(item) for item in mcp_results] + + class _ToolHandler(Protocol): def schemas(self) -> list[Dict]: ... async def exec(self, name: str, args: Dict[str, JsonValue]) -> JsonValue: ... @@ -114,7 +142,8 @@ class _ModelConfig: class _ExecutionConfig: """Configuration for tool execution behavior and policies.""" - mcp_servers: list[str] = field(default_factory=list) + mcp_servers: list[str | Dict[str, Any]] = field(default_factory=list) # Wire format + credentials: list[Any] | None = None # CredentialProtocol objects (not serialized) max_steps: int = 10 stream: bool = False transport: Literal["http", "realtime"] = "http" @@ -137,6 +166,8 @@ class _RunResult: messages: list[Message] = field(default_factory=list) # Full conversation history intents: list[Dict[str, JsonValue]] | None = None tools_called: list[str] = field(default_factory=list) + mcp_results: list[MCPToolResult] = field(default_factory=list) + """MCP tool results from server-side tool calls.""" @property def output(self) -> str: @@ -168,7 +199,8 @@ def run( instructions: str | None = None, model: str | list[str] | DedalusModel | list[DedalusModel] | None = None, max_steps: int = 10, - mcp_servers: str | list[str] | None = None, + mcp_servers: MCPServersInput = None, + credentials: Sequence[Any] | None = None, # TODO: Loosely typed as `Any` for now temperature: float | None = None, max_tokens: int | None = None, top_p: float | None = None, @@ -207,7 +239,9 @@ def run( if isinstance(tool, list): msg = f"tools[{i}] is a list, not a callable function. Did you mean to pass tools={tool} instead of tools=[{tool}]?" raise TypeError(msg) - msg = f"tools[{i}] is not callable (got {type(tool).__name__}). All tools must be callable functions." + msg = ( + f"tools[{i}] is not callable (got {type(tool).__name__}). All tools must be callable functions." + ) raise TypeError(msg) # Parse model to extract name and config @@ -366,11 +400,12 @@ def run( handoff_config=handoff_config, ) - # Normalize mcp_servers to list - normalized_mcp_servers = [mcp_servers] if isinstance(mcp_servers, str) else (mcp_servers or []) + # Serialize mcp_servers to wire format + serialized_mcp_servers = serialize_mcp_servers(mcp_servers) exec_config = _ExecutionConfig( - mcp_servers=normalized_mcp_servers, + mcp_servers=serialized_mcp_servers, + credentials=list(credentials) if credentials else None, max_steps=max_steps, stream=stream, transport=transport, @@ -395,7 +430,10 @@ def run( # Convert instructions to system message, optionally with user input if input is not None: if isinstance(input, str): - conversation = [{"role": "system", "content": instructions}, {"role": "user", "content": input}] + conversation = [ + {"role": "system", "content": instructions}, + {"role": "user", "content": input}, + ] else: conversation = [{"role": "system", "content": instructions}] + list(input) else: @@ -478,6 +516,7 @@ async def _execute_turns_async( messages=current_messages, tools=tool_handler.schemas() or None, mcp_servers=policy_result["mcp_servers"], + credentials=exec_config.credentials, **{**self._mk_kwargs(model_config), **policy_result["model_kwargs"]}, ) @@ -536,11 +575,25 @@ async def _execute_turns_async( for tc in tool_calls: print(f" - {tc.get('function', {}).get('name', '?')} (id: {tc.get('id', '?')})") await self._execute_tool_calls( - tool_calls, tool_handler, messages, tool_results, tools_called, steps, verbose=exec_config.verbose + tool_calls, + tool_handler, + messages, + tool_results, + tools_called, + steps, + verbose=exec_config.verbose, ) + # Extract MCP tool executions from the last response + mcp_results = _extract_mcp_results(response) + return _RunResult( - final_output=final_text, tool_results=tool_results, steps_used=steps, tools_called=tools_called, messages=messages + final_output=final_text, + tool_results=tool_results, + steps_used=steps, + tools_called=tools_called, + messages=messages, + mcp_results=mcp_results, ) async def _execute_streaming_async( @@ -596,6 +649,7 @@ async def _execute_streaming_async( messages=current_messages, tools=tool_handler.schemas() or None, mcp_servers=policy_result["mcp_servers"], + credentials=exec_config.credentials, stream=True, **{**self._mk_kwargs(model_config), **policy_result["model_kwargs"]}, ) @@ -711,12 +765,22 @@ async def _execute_streaming_async( try: result = await tool_handler.exec(fn_name, fn_args) - messages.append({"role": "tool", "tool_call_id": tc["id"], "content": str(result)}) + messages.append( + { + "role": "tool", + "tool_call_id": tc["id"], + "content": str(result), + } + ) if exec_config.verbose: print(f" Executed local tool {fn_name}: {str(result)[:50]}...") except Exception as e: messages.append( - {"role": "tool", "tool_call_id": tc["id"], "content": f"Error: {str(e)}"} + { + "role": "tool", + "tool_call_id": tc["id"], + "content": f"Error: {str(e)}", + } ) if exec_config.verbose: print(f" Error executing local tool {fn_name}: {e}") @@ -801,6 +865,7 @@ def _execute_turns_sync( messages=current_messages, tools=tool_handler.schemas() or None, mcp_servers=policy_result["mcp_servers"], + credentials=exec_config.credentials, **{**self._mk_kwargs(model_config), **policy_result["model_kwargs"]}, ) @@ -835,8 +900,16 @@ def _execute_turns_sync( tool_calls = self._extract_tool_calls(response.choices[0]) self._execute_tool_calls_sync(tool_calls, tool_handler, messages, tool_results, tools_called, steps) + # Extract MCP tool executions from the last response + mcp_results = _extract_mcp_results(response) + return _RunResult( - final_output=final_text, tool_results=tool_results, steps_used=steps, tools_called=tools_called, messages=messages + final_output=final_text, + tool_results=tool_results, + steps_used=steps, + tools_called=tools_called, + messages=messages, + mcp_results=mcp_results, ) def _execute_streaming_sync( @@ -902,6 +975,7 @@ def _execute_streaming_sync( messages=current_messages, tools=tool_handler.schemas() or None, mcp_servers=policy_result["mcp_servers"], + credentials=exec_config.credentials, stream=True, **{**self._mk_kwargs(model_config), **policy_result["model_kwargs"]}, ) @@ -930,7 +1004,11 @@ def _execute_streaming_sync( if exec_config.verbose: # Show tool calls in a more readable format for tc_delta in delta.tool_calls: - if hasattr(tc_delta, 'function') and hasattr(tc_delta.function, 'name') and tc_delta.function.name: + if ( + hasattr(tc_delta, "function") + and hasattr(tc_delta.function, "name") + and tc_delta.function.name + ): print(f"-> Calling {tc_delta.function.name}") # Check for content @@ -1017,12 +1095,22 @@ def _execute_streaming_sync( try: result = tool_handler.exec_sync(fn_name, fn_args) - messages.append({"role": "tool", "tool_call_id": tc["id"], "content": str(result)}) + messages.append( + { + "role": "tool", + "tool_call_id": tc["id"], + "content": str(result), + } + ) if exec_config.verbose: print(f" Executed local tool {fn_name}: {str(result)[:50]}...") except Exception as e: messages.append( - {"role": "tool", "tool_call_id": tc["id"], "content": f"Error: {str(e)}"} + { + "role": "tool", + "tool_call_id": tc["id"], + "content": f"Error: {str(e)}", + } ) if exec_config.verbose: print(f" Error executing local tool {fn_name}: {e}") @@ -1050,7 +1138,11 @@ def _execute_streaming_sync( break def _apply_policy( - self, policy: PolicyInput, context: PolicyContext, model_config: _ModelConfig, exec_config: _ExecutionConfig + self, + policy: PolicyInput, + context: PolicyContext, + model_config: _ModelConfig, + exec_config: _ExecutionConfig, ) -> Dict[str, Any]: """Apply policy and return unified configuration.""" pol = _process_policy(policy, context) @@ -1134,7 +1226,10 @@ def _extract_tool_calls(self, choice) -> list[ToolCall]: { "id": tc_dict.get("id", ""), "type": tc_dict.get("type", "function"), - "function": {"name": fn_dict.get("name", ""), "arguments": fn_dict.get("arguments", "{}")}, + "function": { + "name": fn_dict.get("name", ""), + "arguments": fn_dict.get("arguments", "{}"), + }, } ) return calls @@ -1153,6 +1248,9 @@ async def _execute_tool_calls( if verbose: print(f" _execute_tool_calls: Processing {len(tool_calls)} tool calls") + # Record single assistant message with ALL tool calls (OpenAI format) + messages.append({"role": "assistant", "tool_calls": list(tool_calls)}) + for i, tc in enumerate(tool_calls): fn_name = tc["function"]["name"] fn_args_str = tc["function"]["arguments"] @@ -1160,9 +1258,6 @@ async def _execute_tool_calls( if verbose: print(f" Tool {i + 1}/{len(tool_calls)}: {fn_name}") - # Always record the assistant tool_call message so subsequent tool responses are valid. - messages.append({"role": "assistant", "tool_calls": [tc]}) - try: fn_args = json.loads(fn_args_str) except json.JSONDecodeError: @@ -1179,7 +1274,13 @@ async def _execute_tool_calls( except Exception as e: error_result = {"error": str(e), "name": fn_name, "step": step} tool_results.append(error_result) - messages.append({"role": "tool", "tool_call_id": tc["id"], "content": f"Error: {str(e)}"}) + messages.append( + { + "role": "tool", + "tool_call_id": tc["id"], + "content": f"Error: {str(e)}", + } + ) if verbose: print(f" Tool {fn_name} failed with error: {e}") @@ -1195,12 +1296,13 @@ def _execute_tool_calls_sync( step: int, ): """Execute tool calls synchronously.""" + # Record single assistant message with ALL tool calls (OpenAI format) + messages.append({"role": "assistant", "tool_calls": list(tool_calls)}) + for tc in tool_calls: fn_name = tc["function"]["name"] fn_args_str = tc["function"]["arguments"] - messages.append({"role": "assistant", "tool_calls": [tc]}) - try: fn_args = json.loads(fn_args_str) except json.JSONDecodeError: @@ -1214,7 +1316,13 @@ def _execute_tool_calls_sync( except Exception as e: error_result = {"error": str(e), "name": fn_name, "step": step} tool_results.append(error_result) - messages.append({"role": "tool", "tool_call_id": tc["id"], "content": f"Error: {str(e)}"}) + messages.append( + { + "role": "tool", + "tool_call_id": tc["id"], + "content": f"Error: {str(e)}", + } + ) def _accumulate_tool_calls(self, deltas, acc: list[ToolCall]) -> None: """Accumulate streaming tool call deltas.""" @@ -1223,7 +1331,13 @@ def _accumulate_tool_calls(self, deltas, acc: list[ToolCall]) -> None: # Ensure we have enough entries in acc while len(acc) <= index: - acc.append({"id": "", "type": "function", "function": {"name": "", "arguments": ""}}) + acc.append( + { + "id": "", + "type": "function", + "function": {"name": "", "arguments": ""}, + } + ) if hasattr(delta, "id") and delta.id: acc[index]["id"] = delta.id @@ -1237,8 +1351,8 @@ def _accumulate_tool_calls(self, deltas, acc: list[ToolCall]) -> None: @staticmethod def _mk_kwargs(mc: _ModelConfig) -> Dict[str, Any]: """Convert model config to kwargs for client call.""" - from ...lib._parsing import type_to_response_format_param from ..._utils import is_given + from ...lib._parsing import type_to_response_format_param d = asdict(mc) d.pop("id", None) # Remove id since it's passed separately diff --git a/src/dedalus_labs/lib/runner/protocols.py b/src/dedalus_labs/lib/runner/protocols.py new file mode 100644 index 0000000..af1e1ea --- /dev/null +++ b/src/dedalus_labs/lib/runner/protocols.py @@ -0,0 +1,128 @@ +# ============================================================================== +# © 2025 Dedalus Labs, Inc. and affiliates +# Licensed under MIT +# github.com/dedalus-labs/dedalus-sdk-python/LICENSE +# ============================================================================== + +"""Structural protocols for MCP server integration.""" + +from __future__ import annotations + +from typing import ( + Any, + Dict, + List, + Tuple, + Union, + Optional, + Protocol, + Sequence, + runtime_checkable, +) + +from typing_extensions import TypeGuard + + +# --- Type Aliases ------------------------------------------------------------ + +MCPServerRef = str # Slug ("org/server") or URL + +# --- Protocols --------------------------------------------------------------- + + +@runtime_checkable +class CredentialsProtocol(Protocol): + """Protocol for Credentials.""" + + def to_dict(self) -> Dict[str, Any]: ... + + +@runtime_checkable +class ToolsServiceProtocol(Protocol): + """Protocol for tools service from MCPServer.""" + + @property + def _tool_specs(self) -> Dict[str, Any]: ... + + +@runtime_checkable +class MCPServerProtocol(Protocol): + """Structural protocol for MCP servers.""" + + @property + def name(self) -> str: ... + + @property + def url(self) -> Optional[str]: ... + + def serve(self, *args: Any, **kwargs: Any) -> Any: ... + + +@runtime_checkable +class MCPServerWithCredsProtocol(Protocol): + """Extended protocol for MCPServer with credential bindings. + + Used for connection provisioning flow where servers need credentials. + """ + + @property + def name(self) -> str: ... + + @property + def credentials(self) -> Optional[CredentialsProtocol]: ... + + @property + def connection(self) -> Optional[str]: ... + + @property + def tools(self) -> ToolsServiceProtocol: ... + + +@runtime_checkable +class MCPToolSpec(Protocol): + """Duck-typed interface for tool specifications.""" + + @property + def name(self) -> str: ... + + @property + def description(self) -> Optional[str]: ... + + @property + def input_schema(self) -> Dict[str, Any]: ... + + +# --- Helpers ----------------------------------------------------------------- + + +def is_mcp_server(obj: Any) -> TypeGuard[MCPServerProtocol]: + """Check if obj satisfies MCPServerProtocol.""" + return isinstance(obj, MCPServerProtocol) + + +def normalize_mcp_servers( + servers: Union[ + MCPServerRef, + Sequence[Union[MCPServerRef, MCPServerProtocol]], + MCPServerProtocol, + None, + ], +) -> Tuple[List[MCPServerRef], List[MCPServerProtocol]]: + """Split into (string refs, server objects). Caller checks .url to know if serve() is needed.""" + if servers is None: + return [], [] + if isinstance(servers, str): + return [servers], [] + if is_mcp_server(servers): + return [], [servers] # type: ignore[list-item] + + refs: List[MCPServerRef] = [] + objects: List[MCPServerProtocol] = [] + for item in servers: + if isinstance(item, str): + refs.append(item) + elif is_mcp_server(item): + objects.append(item) + else: + refs.append(str(item)) + return refs, objects diff --git a/src/dedalus_labs/lib/runner/types/__init__.py b/src/dedalus_labs/lib/runner/types/__init__.py index 1060a29..7b18fbe 100644 --- a/src/dedalus_labs/lib/runner/types/__init__.py +++ b/src/dedalus_labs/lib/runner/types/__init__.py @@ -23,4 +23,4 @@ "ToolCall", "ToolHandler", "ToolResult", -] \ No newline at end of file +] diff --git a/src/dedalus_labs/lib/runner/types/tools.py b/src/dedalus_labs/lib/runner/types/tools.py index 67041f6..6bf247d 100644 --- a/src/dedalus_labs/lib/runner/types/tools.py +++ b/src/dedalus_labs/lib/runner/types/tools.py @@ -26,5 +26,6 @@ class ToolHandler(Protocol): """Protocol for tool handlers.""" + def schemas(self) -> List[Dict[str, Any]]: ... async def exec(self, name: str, args: Dict[str, JsonValue]) -> JsonValue: ... diff --git a/src/dedalus_labs/lib/streaming/chat/_completions.py b/src/dedalus_labs/lib/streaming/chat/_completions.py index 4a7fc3e..8ddc0f5 100644 --- a/src/dedalus_labs/lib/streaming/chat/_completions.py +++ b/src/dedalus_labs/lib/streaming/chat/_completions.py @@ -37,24 +37,17 @@ parse_function_tool_arguments, ) from ...._streaming import Stream, AsyncStream -from ....types.chat.stream_chunk import StreamChunk +from ....types.chat.chat_completion_chunk import ChatCompletionChunk +from ....types.chat.stream_choice import StreamChoice as ChoiceChunk +from ....types.chat.stream_choice_logprobs import StreamChoiceLogprobs as ChoiceLogprobs from ....types.chat.parsed_chat_completion import ParsedChatCompletion -from ....types.chat.completion import ChoiceLogprobs -from ....types.chat.stream_chunk import Choice as ChoiceChunk +from ...._exceptions import LengthFinishReasonError, ContentFilterFinishReasonError InputTool = Dict[str, Any] ResponseFormatParam = Dict[str, Any] -class LengthFinishReasonError(RuntimeError): - """Raised when streaming stops due to max tokens before parsing completes.""" - - -class ContentFilterFinishReasonError(RuntimeError): - """Raised when streaming output is blocked by a content filter.""" - - class ChatCompletionStream(Generic[ResponseFormatT]): """Wrapper over the Chat Completions streaming API that adds helpful events such as `content.done`, supports automatically parsing @@ -67,7 +60,7 @@ class ChatCompletionStream(Generic[ResponseFormatT]): def __init__( self, *, - raw_stream: Stream[StreamChunk], + raw_stream: Stream[ChatCompletionChunk], response_format: type[ResponseFormatT] | ResponseFormatParam | Omit, input_tools: Iterable[InputTool] | Omit, ) -> None: @@ -147,7 +140,7 @@ class ChatCompletionStreamManager(Generic[ResponseFormatT]): def __init__( self, - api_request: Callable[[], Stream[StreamChunk]], + api_request: Callable[[], Stream[ChatCompletionChunk]], *, response_format: type[ResponseFormatT] | ResponseFormatParam | Omit, input_tools: Iterable[InputTool] | Omit, @@ -190,7 +183,7 @@ class AsyncChatCompletionStream(Generic[ResponseFormatT]): def __init__( self, *, - raw_stream: AsyncStream[StreamChunk], + raw_stream: AsyncStream[ChatCompletionChunk], response_format: type[ResponseFormatT] | ResponseFormatParam | Omit, input_tools: Iterable[InputTool] | Omit, ) -> None: @@ -270,7 +263,7 @@ class AsyncChatCompletionStreamManager(Generic[ResponseFormatT]): def __init__( self, - api_request: Awaitable[AsyncStream[StreamChunk]], + api_request: Awaitable[AsyncStream[ChatCompletionChunk]], *, response_format: type[ResponseFormatT] | ResponseFormatParam | Omit, input_tools: Iterable[InputTool] | Omit, @@ -350,7 +343,7 @@ def current_completion_snapshot(self) -> ParsedChatCompletionSnapshot: assert self.__current_completion_snapshot is not None return self.__current_completion_snapshot - def handle_chunk(self, chunk: StreamChunk) -> Iterable[ChatCompletionStreamEvent[ResponseFormatT]]: + def handle_chunk(self, chunk: ChatCompletionChunk) -> Iterable[ChatCompletionStreamEvent[ResponseFormatT]]: """Accumulate a new chunk into the snapshot and returns an iterable of events to yield.""" self.__current_completion_snapshot = self._accumulate_chunk(chunk) @@ -367,7 +360,7 @@ def _get_choice_state(self, choice: ChoiceChunk) -> ChoiceEventState: self.__choice_event_states.append(choice_state) return choice_state - def _accumulate_chunk(self, chunk: StreamChunk) -> ParsedChatCompletionSnapshot: + def _accumulate_chunk(self, chunk: ChatCompletionChunk) -> ParsedChatCompletionSnapshot: completion_snapshot = self.__current_completion_snapshot if completion_snapshot is None: @@ -503,7 +496,7 @@ def _accumulate_chunk(self, chunk: StreamChunk) -> ParsedChatCompletionSnapshot: def _build_events( self, *, - chunk: StreamChunk, + chunk: ChatCompletionChunk, completion_snapshot: ParsedChatCompletionSnapshot, ) -> list[ChatCompletionStreamEvent[ResponseFormatT]]: events_to_fire: list[ChatCompletionStreamEvent[ResponseFormatT]] = [] @@ -747,7 +740,7 @@ def _add_tool_done_event( assert_never(tool_call_snapshot) -def _convert_initial_chunk_into_snapshot(chunk: StreamChunk) -> ParsedChatCompletionSnapshot: +def _convert_initial_chunk_into_snapshot(chunk: ChatCompletionChunk) -> ParsedChatCompletionSnapshot: data = chunk.to_dict() choices = cast("list[object]", data["choices"]) @@ -770,7 +763,7 @@ def _convert_initial_chunk_into_snapshot(chunk: StreamChunk) -> ParsedChatComple ) -def _is_valid_stream_chunk(sse_event: StreamChunk) -> bool: +def _is_valid_stream_chunk(sse_event: ChatCompletionChunk) -> bool: # Some providers occasionally send control messages that do not conform to the # standard chunk schema. Filtering on the object type shields downstream logic. return sse_event.object == "chat.completion.chunk" # type: ignore[attr-defined] diff --git a/src/dedalus_labs/lib/streaming/chat/_events.py b/src/dedalus_labs/lib/streaming/chat/_events.py index d36376d..6e5e48b 100644 --- a/src/dedalus_labs/lib/streaming/chat/_events.py +++ b/src/dedalus_labs/lib/streaming/chat/_events.py @@ -6,13 +6,13 @@ from ._types import ParsedChatCompletionSnapshot from ...._models import BaseModel, GenericModel from ..._parsing import ResponseFormatT -from ....types.chat.stream_chunk import StreamChunk +from ....types.chat.chat_completion_chunk import ChatCompletionChunk from ....types.chat.chat_completion_token_logprob import ChatCompletionTokenLogprob class ChunkEvent(BaseModel): type: Literal["chunk"] - chunk: StreamChunk + chunk: ChatCompletionChunk snapshot: ParsedChatCompletionSnapshot diff --git a/src/dedalus_labs/resources/__init__.py b/src/dedalus_labs/resources/__init__.py index e913dfe..a63a918 100644 --- a/src/dedalus_labs/resources/__init__.py +++ b/src/dedalus_labs/resources/__init__.py @@ -8,14 +8,6 @@ ChatResourceWithStreamingResponse, AsyncChatResourceWithStreamingResponse, ) -from .root import ( - RootResource, - AsyncRootResource, - RootResourceWithRawResponse, - AsyncRootResourceWithRawResponse, - RootResourceWithStreamingResponse, - AsyncRootResourceWithStreamingResponse, -) from .audio import ( AudioResource, AsyncAudioResource, @@ -24,14 +16,6 @@ AudioResourceWithStreamingResponse, AsyncAudioResourceWithStreamingResponse, ) -from .health import ( - HealthResource, - AsyncHealthResource, - HealthResourceWithRawResponse, - AsyncHealthResourceWithRawResponse, - HealthResourceWithStreamingResponse, - AsyncHealthResourceWithStreamingResponse, -) from .images import ( ImagesResource, AsyncImagesResource, @@ -58,18 +42,6 @@ ) __all__ = [ - "RootResource", - "AsyncRootResource", - "RootResourceWithRawResponse", - "AsyncRootResourceWithRawResponse", - "RootResourceWithStreamingResponse", - "AsyncRootResourceWithStreamingResponse", - "HealthResource", - "AsyncHealthResource", - "HealthResourceWithRawResponse", - "AsyncHealthResourceWithRawResponse", - "HealthResourceWithStreamingResponse", - "AsyncHealthResourceWithStreamingResponse", "ModelsResource", "AsyncModelsResource", "ModelsResourceWithRawResponse", diff --git a/src/dedalus_labs/resources/audio/speech.py b/src/dedalus_labs/resources/audio/speech.py index bbeba33..67b033c 100644 --- a/src/dedalus_labs/resources/audio/speech.py +++ b/src/dedalus_labs/resources/audio/speech.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional +from typing import Union from typing_extensions import Literal import httpx @@ -51,12 +51,14 @@ def create( self, *, input: str, - model: str, - voice: Literal["alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"], - instructions: Optional[str] | Omit = omit, - response_format: Optional[Literal["mp3", "opus", "aac", "flac", "wav", "pcm"]] | Omit = omit, - speed: Optional[float] | Omit = omit, - stream_format: Optional[Literal["sse", "audio"]] | Omit = omit, + model: Union[str, Literal["tts-1", "tts-1-hd", "gpt-4o-mini-tts"]], + voice: Union[ + str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"] + ], + instructions: str | Omit = omit, + response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] | Omit = omit, + speed: float | Omit = omit, + stream_format: Literal["sse", "audio"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -79,7 +81,7 @@ def create( model: One of the available [TTS models](https://platform.openai.com/docs/models#tts): - `openai/tts-1`, `openai/tts-1-hd` or `openai/gpt-4o-mini-tts`. + `tts-1`, `tts-1-hd` or `gpt-4o-mini-tts`. voice: The voice to use when generating the audio. Supported voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `onyx`, `nova`, `sage`, `shimmer`, and @@ -158,12 +160,14 @@ async def create( self, *, input: str, - model: str, - voice: Literal["alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"], - instructions: Optional[str] | Omit = omit, - response_format: Optional[Literal["mp3", "opus", "aac", "flac", "wav", "pcm"]] | Omit = omit, - speed: Optional[float] | Omit = omit, - stream_format: Optional[Literal["sse", "audio"]] | Omit = omit, + model: Union[str, Literal["tts-1", "tts-1-hd", "gpt-4o-mini-tts"]], + voice: Union[ + str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"] + ], + instructions: str | Omit = omit, + response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] | Omit = omit, + speed: float | Omit = omit, + stream_format: Literal["sse", "audio"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -186,7 +190,7 @@ async def create( model: One of the available [TTS models](https://platform.openai.com/docs/models#tts): - `openai/tts-1`, `openai/tts-1-hd` or `openai/gpt-4o-mini-tts`. + `tts-1`, `tts-1-hd` or `gpt-4o-mini-tts`. voice: The voice to use when generating the audio. Supported voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `onyx`, `nova`, `sage`, `shimmer`, and diff --git a/src/dedalus_labs/resources/chat/completions.py b/src/dedalus_labs/resources/chat/completions.py index 0cfb5a1..aeca9c7 100644 --- a/src/dedalus_labs/resources/chat/completions.py +++ b/src/dedalus_labs/resources/chat/completions.py @@ -19,10 +19,10 @@ async_to_streamed_response_wrapper, ) from ..._streaming import Stream, AsyncStream -from ...types.chat import completion_create_params +from ...types.chat import ChatCompletionAudioParam, completion_create_params from ..._base_client import make_request_options -from ...types.chat.completion import Completion -from ...types.chat.stream_chunk import StreamChunk +from ...types.chat.chat_completion import ChatCompletion +from ...types.chat.chat_completion_chunk import ChatCompletionChunk from ...lib._parsing import ( ResponseFormatT, parse_chat_completion as _parse_chat_completion, @@ -33,6 +33,10 @@ ChatCompletionStreamManager, AsyncChatCompletionStreamManager, ) +from ...types.chat.prediction_content_param import PredictionContentParam +from ...types.shared_params.json_object_input import JSONObjectInput +from ...types.chat.chat_completion_audio_param import ChatCompletionAudioParam +from ...types.chat.chat_completion_functions_param import ChatCompletionFunctionsParam __all__ = ["CompletionsResource", "AsyncCompletionsResource"] @@ -63,56 +67,58 @@ def create( *, model: completion_create_params.Model, agent_attributes: Optional[Dict[str, float]] | Omit = omit, - audio: Optional[Dict[str, object]] | Omit = omit, - auto_execute_tools: bool | Omit = omit, + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + automatic_tool_execution: bool | Omit = omit, + cached_content: Optional[str] | Omit = omit, + credentials: Optional[completion_create_params.Credentials] | Omit = omit, deferred: Optional[bool] | Omit = omit, - disable_automatic_function_calling: Optional[bool] | Omit = omit, frequency_penalty: Optional[float] | Omit = omit, - function_call: Union[str, Dict[str, object], None] | Omit = omit, - functions: Optional[Iterable[Dict[str, object]]] | Omit = omit, - generation_config: Optional[Dict[str, object]] | Omit = omit, + function_call: Optional[str] | Omit = omit, + functions: Optional[Iterable[ChatCompletionFunctionsParam]] | Omit = omit, + generation_config: Optional[JSONObjectInput] | Omit = omit, guardrails: Optional[Iterable[Dict[str, object]]] | Omit = omit, handoff_config: Optional[Dict[str, object]] | Omit = omit, - input: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - instructions: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, logit_bias: Optional[Dict[str, int]] | Omit = omit, logprobs: Optional[bool] | Omit = omit, max_completion_tokens: Optional[int] | Omit = omit, max_tokens: Optional[int] | Omit = omit, max_turns: Optional[int] | Omit = omit, - mcp_servers: Union[str, SequenceNotStr[str], None] | Omit = omit, - messages: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - metadata: Optional[Dict[str, str]] | Omit = omit, + mcp_servers: Optional[completion_create_params.MCPServers] | Omit = omit, + messages: Optional[Iterable[completion_create_params.Message]] | Omit = omit, + metadata: Optional[JSONObjectInput] | Omit = omit, modalities: Optional[SequenceNotStr[str]] | Omit = omit, model_attributes: Optional[Dict[str, Dict[str, float]]] | Omit = omit, n: Optional[int] | Omit = omit, parallel_tool_calls: Optional[bool] | Omit = omit, - prediction: Optional[Dict[str, object]] | Omit = omit, + prediction: Optional[PredictionContentParam] | Omit = omit, presence_penalty: Optional[float] | Omit = omit, prompt_cache_key: Optional[str] | Omit = omit, - reasoning_effort: Optional[Literal["low", "medium", "high"]] | Omit = omit, + prompt_cache_retention: Optional[str] | Omit = omit, + prompt_mode: Optional[Literal["reasoning"]] | Omit = omit, + reasoning_effort: Optional[str] | Omit = omit, response_format: Optional[completion_create_params.ResponseFormat] | Omit = omit, + safe_prompt: Optional[bool] | Omit = omit, safety_identifier: Optional[str] | Omit = omit, - safety_settings: Optional[Iterable[Dict[str, object]]] | Omit = omit, - search_parameters: Optional[Dict[str, object]] | Omit = omit, + safety_settings: Optional[Iterable[completion_create_params.SafetySetting]] | Omit = omit, + search_parameters: Optional[JSONObjectInput] | Omit = omit, seed: Optional[int] | Omit = omit, - service_tier: Optional[Literal["auto", "default"]] | Omit = omit, - stop: Optional[SequenceNotStr[str]] | Omit = omit, + service_tier: Optional[str] | Omit = omit, + stop: Union[SequenceNotStr[str], str, None] | Omit = omit, store: Optional[bool] | Omit = omit, - stream: Literal[False] | Omit = omit, - stream_options: Optional[Dict[str, object]] | Omit = omit, - system: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + stream_options: Optional[JSONObjectInput] | Omit = omit, + system_instruction: Union[JSONObjectInput, str, None] | Omit = omit, temperature: Optional[float] | Omit = omit, thinking: Optional[completion_create_params.Thinking] | Omit = omit, - tool_choice: Union[str, Dict[str, object], None] | Omit = omit, - tool_config: Optional[Dict[str, object]] | Omit = omit, - tools: Optional[Iterable[Dict[str, object]]] | Omit = omit, + tool_choice: Optional[completion_create_params.ToolChoice] | Omit = omit, + tool_config: Optional[JSONObjectInput] | Omit = omit, + tools: Optional[Iterable[completion_create_params.Tool]] | Omit = omit, top_k: Optional[int] | Omit = omit, top_logprobs: Optional[int] | Omit = omit, top_p: Optional[float] | Omit = omit, user: Optional[str] | Omit = omit, - verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, - web_search_options: Optional[Dict[str, object]] | Omit = omit, + verbosity: Optional[str] | Omit = omit, + web_search_options: Optional[JSONObjectInput] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -120,23 +126,12 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, idempotency_key: str | None = None, - ) -> Completion: + ) -> ChatCompletion: """ Create a chat completion. - Unified chat-completions endpoint that works across many model providers. - Supports optional MCP integration, multi-model routing with agentic handoffs, - server- or client-executed tools, and both streaming and non-streaming delivery. - - Request body: - - - messages: ordered list of chat turns. - - model: identifier or a list of identifiers for routing. - - tools: optional tool declarations available to the model. - - mcp_servers: optional list of MCP server slugs to enable during the run. - - stream: boolean to request incremental output. - - config: optional generation parameters (e.g., temperature, max_tokens, - metadata). + Generates a model response for the given conversation and configuration. + Supports OpenAI-compatible parameters and provider-specific extensions. Headers: @@ -145,8 +140,8 @@ def create( Behavior: - - If multiple models are supplied, the router may select or hand off across - them. + - If multiple models are supplied, the first one is used, and the agent may hand + off to another model. - Tools may be invoked on the server or signaled for the client to run. - Streaming responses emit incremental deltas; non-streaming returns a single object. @@ -186,190 +181,204 @@ def create( [DONE] Args: - model: Model(s) to use for completion. Can be a single model ID, a DedalusModel object, - or a list for multi-model routing. Single model: 'openai/gpt-4', - 'anthropic/claude-3-5-sonnet-20241022', 'openai/gpt-4o-mini', or a DedalusModel - instance. Multi-model routing: ['openai/gpt-4o-mini', 'openai/gpt-4', - 'anthropic/claude-3-5-sonnet'] or list of DedalusModel objects - agent will - choose optimal model based on task complexity. + model: Model identifier. Accepts model ID strings, lists for routing, or DedalusModel + objects with per-model settings. - agent_attributes: Attributes for the agent itself, influencing behavior and model selection. - Format: {'attribute': value}, where values are 0.0-1.0. Common attributes: - 'complexity', 'accuracy', 'efficiency', 'creativity', 'friendliness'. Higher - values indicate stronger preference for that characteristic. + agent_attributes: Agent attributes. Values in [0.0, 1.0]. - audio: Parameters for audio output. Required when requesting audio responses (for - example, modalities including 'audio'). + audio: Parameters for audio output. Required when audio output is requested with + `modalities: ["audio"]`. + [Learn more](https://platform.openai.com/docs/guides/audio). - auto_execute_tools: When False, skip server-side tool execution and return raw OpenAI-style - tool_calls in the response. + Fields: - deferred: xAI-specific parameter. If set to true, the request returns a request_id for - async completion retrieval via GET /v1/chat/deferred-completion/{request_id}. + - voice (required): VoiceIdsShared + - format (required): Literal["wav", "aac", "mp3", "flac", "opus", "pcm16"] - disable_automatic_function_calling: Google-only flag to disable the SDK's automatic function execution. When true, - the model returns function calls for the client to execute manually. + automatic_tool_execution: Execute tools server-side. If false, returns raw tool calls for manual handling. + + cached_content: Optional. The name of the content + [cached](https://ai.google.dev/gemini-api/docs/caching) to use as context to + serve the prediction. Format: `cachedContents/{cachedContent}` + + credentials: Credentials for MCP server authentication. Each credential is matched to servers + by connection name. + + deferred: If set to `true`, the request returns a `request_id`. You can then get the + deferred response by GET `/v1/chat/deferred-completion/{request_id}`. frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - function_call: Deprecated in favor of 'tool_choice'. Controls which function is called by the - model (none, auto, or specific name). + function_call: Wrapper for union variant: function call mode. - functions: Deprecated in favor of 'tools'. Legacy list of function definitions the model - may generate JSON inputs for. + functions: Deprecated in favor of `tools`. A list of functions the model may generate JSON + inputs for. - generation_config: Google generationConfig object. Merged with auto-generated config. Use for - Google-specific params (candidateCount, responseMimeType, etc.). + generation_config: Generation parameters wrapper (Google-specific) - guardrails: Guardrails to apply to the agent for input/output validation and safety checks. - Reserved for future use - guardrails configuration format not yet finalized. + guardrails: Content filtering and safety policy configuration. - handoff_config: Configuration for multi-model handoffs and agent orchestration. Reserved for - future use - handoff configuration format not yet finalized. + handoff_config: Configuration for multi-model handoffs. - input: Convenience alias for Responses-style `input`. Used when `messages` is omitted - to provide the user prompt directly. + logit_bias: Modify the likelihood of specified tokens appearing in the completion. Accepts a + JSON object that maps tokens (specified by their token ID in the tokenizer) to + an associated bias value from -100 to 100. Mathematically, the bias is added to + the logits generated by the model prior to sampling. The exact effect will vary + per model, but values between -1 and 1 should decrease or increase likelihood of + selection; values like -100 or 100 should result in a ban or exclusive selection + of the relevant token. - instructions: Convenience alias for Responses-style `instructions`. Takes precedence over - `system` and over system-role messages when provided. + logprobs: Whether to return log probabilities of the output tokens or not. If true, + returns the log probabilities of each output token returned in the `content` of + `message`. - logit_bias: Modify the likelihood of specified tokens appearing in the completion. Accepts a - JSON object mapping token IDs (as strings) to bias values from -100 to 100. The - bias is added to the logits before sampling; values between -1 and 1 nudge - selection probability, while values like -100 or 100 effectively ban or require - a token. + max_completion_tokens: Maximum tokens in completion (newer parameter name) - logprobs: Whether to return log probabilities of the output tokens. If true, returns the - log probabilities for each token in the response content. + max_tokens: Maximum tokens in completion - max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, - including visible output and reasoning tokens. + max_turns: Maximum conversation turns. - max_tokens: The maximum number of tokens that can be generated in the chat completion. This - value can be used to control costs for text generated via API. This value is now - deprecated in favor of 'max_completion_tokens' and is not compatible with - o-series models. + mcp_servers: MCP server identifiers. Accepts marketplace slugs, URLs, or MCPServerSpec + objects. MCP tools are executed server-side and billed separately. - max_turns: Maximum number of turns for agent execution before terminating (default: 10). - Each turn represents one model inference cycle. Higher values allow more complex - reasoning but increase cost and latency. + messages: Conversation history (OpenAI: messages, Google: contents, Responses: input) - mcp_servers: MCP (Model Context Protocol) server addresses to make available for server-side - tool execution. Entries can be URLs (e.g., 'https://mcp.example.com'), slugs - (e.g., 'dedalus-labs/brave-search'), or structured objects specifying - slug/version/url. MCP tools are executed server-side and billed separately. + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. Keys are strings with a maximum + length of 64 characters. Values are strings with a maximum length of 512 + characters. - messages: Conversation history. Accepts either a list of message objects or a string, - which is treated as a single user message. Optional if `input` or `instructions` - is provided. + modalities: Output types that you would like the model to generate. Most models are capable + of generating text, which is the default: `["text"]` The `gpt-4o-audio-preview` + model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that + this model generate both text and audio responses, you can use: + `["text", "audio"]` - metadata: Set of up to 16 key-value string pairs that can be attached to the request for - structured metadata. + model_attributes: Model attributes for routing. Maps model IDs to attribute dictionaries with + values in [0.0, 1.0]. - modalities: Output types you would like the model to generate. Most models default to - ['text']; some support ['text', 'audio']. + n: How many chat completion choices to generate for each input message. Note that + you will be charged based on the number of generated tokens across all of the + choices. Keep `n` as `1` to minimize costs. - model_attributes: Attributes for individual models used in routing decisions during multi-model - execution. Format: {'model_name': {'attribute': value}}, where values are - 0.0-1.0. Common attributes: 'intelligence', 'speed', 'cost', 'creativity', - 'accuracy'. Used by agent to select optimal model based on task requirements. + parallel_tool_calls: Whether to enable parallel tool calls (Anthropic uses inverted polarity) - n: How many chat completion choices to generate for each input message. Keep 'n' as - 1 to minimize costs. + prediction: Static predicted output content, such as the content of a text file that is + being regenerated. - parallel_tool_calls: Whether to enable parallel function calling during tool use. + Fields: - prediction: Configuration for predicted outputs. Improves response times when you already - know large portions of the response content. + - type (required): Literal["content"] + - content (required): str | + Annotated[list[ChatCompletionRequestMessageContentPartText], MinLen(1), + ArrayTitle("PredictionContentArray")] presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - prompt_cache_key: Used by OpenAI to cache responses for similar requests and optimize cache hit - rates. Replaces the legacy 'user' field for caching. + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). - reasoning_effort: Constrains effort on reasoning for supported reasoning models. Higher values use - more compute, potentially improving reasoning quality at the cost of latency and - tokens. + prompt_mode: Allows toggling between the reasoning mode and no system prompt. When set to + `reasoning` the system prompt for reasoning models will be used. - response_format: - An object specifying the format that the model must output. Use {'type': - 'json_schema', 'json_schema': {...}} for structured outputs or {'type': - 'json_object'} for the legacy JSON mode. Currently only OpenAI-prefixed models - honour this field; Anthropic and Google requests will return an - invalid_request_error if it is supplied. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, and `high`. Reducing + reasoning effort can result in faster responses and fewer tokens used on + reasoning in a response. - `gpt-5.1` defaults to `none`, which does not perform + reasoning. The supported reasoning values for `gpt-5.1` are `none`, `low`, + `medium`, and `high`. Tool calls are supported for all reasoning values in + gpt-5.1. - All models before `gpt-5.1` default to `medium` reasoning effort, and + do not support `none`. - The `gpt-5-pro` model defaults to (and only supports) + `high` reasoning effort. - safety_identifier: Stable identifier used to help detect users who might violate OpenAI usage - policies. Consider hashing end-user identifiers before sending. + response_format: An object specifying the format that the model must output. Setting to + `{ "type": "json_schema", "json_schema": {...} }` enables Structured Outputs + which ensures the model will match your supplied JSON schema. Learn more in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. - safety_settings: Google safety settings (harm categories and thresholds). + safe_prompt: Whether to inject a safety prompt before all conversations. - search_parameters: xAI-specific parameter for configuring web search data acquisition. If not set, - no data will be acquired by the model. + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user. We recommend hashing their username or email address, in + order to avoid sending us any identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). - seed: If specified, system will make a best effort to sample deterministically. - Determinism is not guaranteed for the same seed across different models or API - versions. + safety_settings: Safety/content filtering settings (Google-specific) - service_tier: Specifies the processing tier used for the request. 'auto' uses project - defaults, while 'default' forces standard pricing and performance. + search_parameters: Set the parameters to be used for searched data. If not set, no data will be + acquired by the model. - stop: Not supported with latest reasoning models 'o3' and 'o4-mini'. + seed: Random seed for deterministic output - Up to 4 sequences where the API will stop generating further tokens; the returned text will not contain the stop sequence. + service_tier: Service tier for request processing - store: Whether to store the output of this chat completion request for OpenAI model - distillation or eval products. Image inputs over 8MB are dropped if storage is - enabled. + stop: Sequences that stop generation - stream: If true, the model response data is streamed to the client as it is generated - using Server-Sent Events. + store: Whether or not to store the output of this chat completion request for use in + our [model distillation](https://platform.openai.com/docs/guides/distillation) + or [evals](https://platform.openai.com/docs/guides/evals) products. Supports + text and image inputs. Note: image inputs over 8MB will be dropped. - stream_options: Options for streaming responses. Only set when 'stream' is true (supports - 'include_usage' and 'include_obfuscation'). + stream: Enable streaming response - system: System prompt/instructions. Anthropic: pass-through. Google: converted to - systemInstruction. OpenAI: extracted from messages. + stream_options: Options for streaming response. Only set this when you set `stream: true`. - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 make - the output more random, while lower values like 0.2 make it more focused and - deterministic. We generally recommend altering this or 'top_p' but not both. + system_instruction: System instruction/prompt - thinking: Extended thinking configuration (Anthropic only). Enables thinking blocks - showing reasoning process. Requires min 1,024 token budget. + temperature: Sampling temperature (0-2 for most providers) - tool_choice: Controls which (if any) tool is called by the model. 'none' stops tool calling, - 'auto' lets the model decide, and 'required' forces at least one tool - invocation. Specific tool payloads force that tool. + thinking: Extended thinking configuration (Anthropic-specific) - tool_config: Google tool configuration (function calling mode, etc.). + tool_choice: Controls which (if any) tool is called by the model. `none` means the model will + not call any tool and instead generates a message. `auto` means the model can + pick between generating a message or calling one or more tools. `required` means + the model must call one or more tools. Specifying a particular tool via + `{"type": "function", "function": {"name": "my_function"}}` forces the model to + call that tool. `none` is the default when no tools are present. `auto` is the + default if tools are present. - tools: A list of tools the model may call. Supports OpenAI function tools and custom - tools; use 'mcp_servers' for Dedalus-managed server-side tools. + tool_config: Tool calling configuration (Google-specific) - top_k: Top-k sampling. Anthropic: pass-through. Google: injected into - generationConfig.topK. + tools: Available tools/functions for the model - top_logprobs: An integer between 0 and 20 specifying how many of the most likely tokens to - return at each position, with log probabilities. Requires 'logprobs' to be true. + top_k: Top-k sampling parameter - top_p: An alternative to sampling with temperature, called nucleus sampling, where the - model considers the results of the tokens with top_p probability mass. So 0.1 - means only the tokens comprising the top 10% probability mass are considered. We - generally recommend altering this or 'temperature' but not both. + top_logprobs: An integer between 0 and 20 specifying the number of most likely tokens to + return at each token position, each with an associated log probability. + `logprobs` must be set to `true` if this parameter is used. - user: Stable identifier for your end-users. Helps OpenAI detect and prevent abuse and - may boost cache hit rates. This field is being replaced by 'safety_identifier' - and 'prompt_cache_key'. + top_p: Nucleus sampling threshold - verbosity: Constrains the verbosity of the model's response. Lower values produce concise - answers, higher values allow more detail. + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). - web_search_options: Configuration for OpenAI's web search tool. Learn more at - https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat. + verbosity: Constrains the verbosity of the model's response. Lower values will result in + more concise responses, while higher values will result in more verbose + responses. Currently supported values are `low`, `medium`, and `high`. + + web_search_options: This tool searches the web for relevant results to use in a response. Learn more + about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). extra_headers: Send extra headers @@ -390,55 +399,57 @@ def create( model: completion_create_params.Model, stream: Literal[True], agent_attributes: Optional[Dict[str, float]] | Omit = omit, - audio: Optional[Dict[str, object]] | Omit = omit, - auto_execute_tools: bool | Omit = omit, + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + automatic_tool_execution: bool | Omit = omit, + cached_content: Optional[str] | Omit = omit, + credentials: Optional[completion_create_params.Credentials] | Omit = omit, deferred: Optional[bool] | Omit = omit, - disable_automatic_function_calling: Optional[bool] | Omit = omit, frequency_penalty: Optional[float] | Omit = omit, - function_call: Union[str, Dict[str, object], None] | Omit = omit, - functions: Optional[Iterable[Dict[str, object]]] | Omit = omit, - generation_config: Optional[Dict[str, object]] | Omit = omit, + function_call: Optional[str] | Omit = omit, + functions: Optional[Iterable[ChatCompletionFunctionsParam]] | Omit = omit, + generation_config: Optional[JSONObjectInput] | Omit = omit, guardrails: Optional[Iterable[Dict[str, object]]] | Omit = omit, handoff_config: Optional[Dict[str, object]] | Omit = omit, - input: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - instructions: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, logit_bias: Optional[Dict[str, int]] | Omit = omit, logprobs: Optional[bool] | Omit = omit, max_completion_tokens: Optional[int] | Omit = omit, max_tokens: Optional[int] | Omit = omit, max_turns: Optional[int] | Omit = omit, - mcp_servers: Union[str, SequenceNotStr[str], None] | Omit = omit, - messages: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - metadata: Optional[Dict[str, str]] | Omit = omit, + mcp_servers: Optional[completion_create_params.MCPServers] | Omit = omit, + messages: Optional[Iterable[completion_create_params.Message]] | Omit = omit, + metadata: Optional[JSONObjectInput] | Omit = omit, modalities: Optional[SequenceNotStr[str]] | Omit = omit, model_attributes: Optional[Dict[str, Dict[str, float]]] | Omit = omit, n: Optional[int] | Omit = omit, parallel_tool_calls: Optional[bool] | Omit = omit, - prediction: Optional[Dict[str, object]] | Omit = omit, + prediction: Optional[PredictionContentParam] | Omit = omit, presence_penalty: Optional[float] | Omit = omit, prompt_cache_key: Optional[str] | Omit = omit, - reasoning_effort: Optional[Literal["low", "medium", "high"]] | Omit = omit, + prompt_cache_retention: Optional[str] | Omit = omit, + prompt_mode: Optional[Literal["reasoning"]] | Omit = omit, + reasoning_effort: Optional[str] | Omit = omit, response_format: Optional[completion_create_params.ResponseFormat] | Omit = omit, + safe_prompt: Optional[bool] | Omit = omit, safety_identifier: Optional[str] | Omit = omit, - safety_settings: Optional[Iterable[Dict[str, object]]] | Omit = omit, - search_parameters: Optional[Dict[str, object]] | Omit = omit, + safety_settings: Optional[Iterable[completion_create_params.SafetySetting]] | Omit = omit, + search_parameters: Optional[JSONObjectInput] | Omit = omit, seed: Optional[int] | Omit = omit, - service_tier: Optional[Literal["auto", "default"]] | Omit = omit, - stop: Optional[SequenceNotStr[str]] | Omit = omit, + service_tier: Optional[str] | Omit = omit, + stop: Union[SequenceNotStr[str], str, None] | Omit = omit, store: Optional[bool] | Omit = omit, - stream_options: Optional[Dict[str, object]] | Omit = omit, - system: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, + stream_options: Optional[JSONObjectInput] | Omit = omit, + system_instruction: Union[JSONObjectInput, str, None] | Omit = omit, temperature: Optional[float] | Omit = omit, thinking: Optional[completion_create_params.Thinking] | Omit = omit, - tool_choice: Union[str, Dict[str, object], None] | Omit = omit, - tool_config: Optional[Dict[str, object]] | Omit = omit, - tools: Optional[Iterable[Dict[str, object]]] | Omit = omit, + tool_choice: Optional[completion_create_params.ToolChoice] | Omit = omit, + tool_config: Optional[JSONObjectInput] | Omit = omit, + tools: Optional[Iterable[completion_create_params.Tool]] | Omit = omit, top_k: Optional[int] | Omit = omit, top_logprobs: Optional[int] | Omit = omit, top_p: Optional[float] | Omit = omit, user: Optional[str] | Omit = omit, - verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, - web_search_options: Optional[Dict[str, object]] | Omit = omit, + verbosity: Optional[str] | Omit = omit, + web_search_options: Optional[JSONObjectInput] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -446,23 +457,12 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, idempotency_key: str | None = None, - ) -> Stream[StreamChunk]: + ) -> Stream[ChatCompletionChunk]: """ Create a chat completion. - Unified chat-completions endpoint that works across many model providers. - Supports optional MCP integration, multi-model routing with agentic handoffs, - server- or client-executed tools, and both streaming and non-streaming delivery. - - Request body: - - - messages: ordered list of chat turns. - - model: identifier or a list of identifiers for routing. - - tools: optional tool declarations available to the model. - - mcp_servers: optional list of MCP server slugs to enable during the run. - - stream: boolean to request incremental output. - - config: optional generation parameters (e.g., temperature, max_tokens, - metadata). + Generates a model response for the given conversation and configuration. + Supports OpenAI-compatible parameters and provider-specific extensions. Headers: @@ -471,8 +471,8 @@ def create( Behavior: - - If multiple models are supplied, the router may select or hand off across - them. + - If multiple models are supplied, the first one is used, and the agent may hand + off to another model. - Tools may be invoked on the server or signaled for the client to run. - Streaming responses emit incremental deltas; non-streaming returns a single object. @@ -512,190 +512,204 @@ def create( [DONE] Args: - model: Model(s) to use for completion. Can be a single model ID, a DedalusModel object, - or a list for multi-model routing. Single model: 'openai/gpt-4', - 'anthropic/claude-3-5-sonnet-20241022', 'openai/gpt-4o-mini', or a DedalusModel - instance. Multi-model routing: ['openai/gpt-4o-mini', 'openai/gpt-4', - 'anthropic/claude-3-5-sonnet'] or list of DedalusModel objects - agent will - choose optimal model based on task complexity. + model: Model identifier. Accepts model ID strings, lists for routing, or DedalusModel + objects with per-model settings. + + stream: Enable streaming response - stream: If true, the model response data is streamed to the client as it is generated - using Server-Sent Events. + agent_attributes: Agent attributes. Values in [0.0, 1.0]. - agent_attributes: Attributes for the agent itself, influencing behavior and model selection. - Format: {'attribute': value}, where values are 0.0-1.0. Common attributes: - 'complexity', 'accuracy', 'efficiency', 'creativity', 'friendliness'. Higher - values indicate stronger preference for that characteristic. + audio: Parameters for audio output. Required when audio output is requested with + `modalities: ["audio"]`. + [Learn more](https://platform.openai.com/docs/guides/audio). - audio: Parameters for audio output. Required when requesting audio responses (for - example, modalities including 'audio'). + Fields: - auto_execute_tools: When False, skip server-side tool execution and return raw OpenAI-style - tool_calls in the response. + - voice (required): VoiceIdsShared + - format (required): Literal["wav", "aac", "mp3", "flac", "opus", "pcm16"] - deferred: xAI-specific parameter. If set to true, the request returns a request_id for - async completion retrieval via GET /v1/chat/deferred-completion/{request_id}. + automatic_tool_execution: Execute tools server-side. If false, returns raw tool calls for manual handling. - disable_automatic_function_calling: Google-only flag to disable the SDK's automatic function execution. When true, - the model returns function calls for the client to execute manually. + cached_content: Optional. The name of the content + [cached](https://ai.google.dev/gemini-api/docs/caching) to use as context to + serve the prediction. Format: `cachedContents/{cachedContent}` + + credentials: Credentials for MCP server authentication. Each credential is matched to servers + by connection name. + + deferred: If set to `true`, the request returns a `request_id`. You can then get the + deferred response by GET `/v1/chat/deferred-completion/{request_id}`. frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - function_call: Deprecated in favor of 'tool_choice'. Controls which function is called by the - model (none, auto, or specific name). + function_call: Wrapper for union variant: function call mode. - functions: Deprecated in favor of 'tools'. Legacy list of function definitions the model - may generate JSON inputs for. + functions: Deprecated in favor of `tools`. A list of functions the model may generate JSON + inputs for. - generation_config: Google generationConfig object. Merged with auto-generated config. Use for - Google-specific params (candidateCount, responseMimeType, etc.). + generation_config: Generation parameters wrapper (Google-specific) - guardrails: Guardrails to apply to the agent for input/output validation and safety checks. - Reserved for future use - guardrails configuration format not yet finalized. + guardrails: Content filtering and safety policy configuration. - handoff_config: Configuration for multi-model handoffs and agent orchestration. Reserved for - future use - handoff configuration format not yet finalized. + handoff_config: Configuration for multi-model handoffs. - input: Convenience alias for Responses-style `input`. Used when `messages` is omitted - to provide the user prompt directly. + logit_bias: Modify the likelihood of specified tokens appearing in the completion. Accepts a + JSON object that maps tokens (specified by their token ID in the tokenizer) to + an associated bias value from -100 to 100. Mathematically, the bias is added to + the logits generated by the model prior to sampling. The exact effect will vary + per model, but values between -1 and 1 should decrease or increase likelihood of + selection; values like -100 or 100 should result in a ban or exclusive selection + of the relevant token. - instructions: Convenience alias for Responses-style `instructions`. Takes precedence over - `system` and over system-role messages when provided. + logprobs: Whether to return log probabilities of the output tokens or not. If true, + returns the log probabilities of each output token returned in the `content` of + `message`. - logit_bias: Modify the likelihood of specified tokens appearing in the completion. Accepts a - JSON object mapping token IDs (as strings) to bias values from -100 to 100. The - bias is added to the logits before sampling; values between -1 and 1 nudge - selection probability, while values like -100 or 100 effectively ban or require - a token. + max_completion_tokens: Maximum tokens in completion (newer parameter name) - logprobs: Whether to return log probabilities of the output tokens. If true, returns the - log probabilities for each token in the response content. + max_tokens: Maximum tokens in completion - max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, - including visible output and reasoning tokens. + max_turns: Maximum conversation turns. - max_tokens: The maximum number of tokens that can be generated in the chat completion. This - value can be used to control costs for text generated via API. This value is now - deprecated in favor of 'max_completion_tokens' and is not compatible with - o-series models. + mcp_servers: MCP server identifiers. Accepts marketplace slugs, URLs, or MCPServerSpec + objects. MCP tools are executed server-side and billed separately. - max_turns: Maximum number of turns for agent execution before terminating (default: 10). - Each turn represents one model inference cycle. Higher values allow more complex - reasoning but increase cost and latency. + messages: Conversation history (OpenAI: messages, Google: contents, Responses: input) - mcp_servers: MCP (Model Context Protocol) server addresses to make available for server-side - tool execution. Entries can be URLs (e.g., 'https://mcp.example.com'), slugs - (e.g., 'dedalus-labs/brave-search'), or structured objects specifying - slug/version/url. MCP tools are executed server-side and billed separately. + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. Keys are strings with a maximum + length of 64 characters. Values are strings with a maximum length of 512 + characters. - messages: Conversation history. Accepts either a list of message objects or a string, - which is treated as a single user message. Optional if `input` or `instructions` - is provided. + modalities: Output types that you would like the model to generate. Most models are capable + of generating text, which is the default: `["text"]` The `gpt-4o-audio-preview` + model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that + this model generate both text and audio responses, you can use: + `["text", "audio"]` - metadata: Set of up to 16 key-value string pairs that can be attached to the request for - structured metadata. + model_attributes: Model attributes for routing. Maps model IDs to attribute dictionaries with + values in [0.0, 1.0]. - modalities: Output types you would like the model to generate. Most models default to - ['text']; some support ['text', 'audio']. + n: How many chat completion choices to generate for each input message. Note that + you will be charged based on the number of generated tokens across all of the + choices. Keep `n` as `1` to minimize costs. - model_attributes: Attributes for individual models used in routing decisions during multi-model - execution. Format: {'model_name': {'attribute': value}}, where values are - 0.0-1.0. Common attributes: 'intelligence', 'speed', 'cost', 'creativity', - 'accuracy'. Used by agent to select optimal model based on task requirements. + parallel_tool_calls: Whether to enable parallel tool calls (Anthropic uses inverted polarity) - n: How many chat completion choices to generate for each input message. Keep 'n' as - 1 to minimize costs. + prediction: Static predicted output content, such as the content of a text file that is + being regenerated. - parallel_tool_calls: Whether to enable parallel function calling during tool use. + Fields: - prediction: Configuration for predicted outputs. Improves response times when you already - know large portions of the response content. + - type (required): Literal["content"] + - content (required): str | + Annotated[list[ChatCompletionRequestMessageContentPartText], MinLen(1), + ArrayTitle("PredictionContentArray")] presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - prompt_cache_key: Used by OpenAI to cache responses for similar requests and optimize cache hit - rates. Replaces the legacy 'user' field for caching. + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + + prompt_mode: Allows toggling between the reasoning mode and no system prompt. When set to + `reasoning` the system prompt for reasoning models will be used. - reasoning_effort: Constrains effort on reasoning for supported reasoning models. Higher values use - more compute, potentially improving reasoning quality at the cost of latency and - tokens. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, and `high`. Reducing + reasoning effort can result in faster responses and fewer tokens used on + reasoning in a response. - `gpt-5.1` defaults to `none`, which does not perform + reasoning. The supported reasoning values for `gpt-5.1` are `none`, `low`, + `medium`, and `high`. Tool calls are supported for all reasoning values in + gpt-5.1. - All models before `gpt-5.1` default to `medium` reasoning effort, and + do not support `none`. - The `gpt-5-pro` model defaults to (and only supports) + `high` reasoning effort. - response_format: - An object specifying the format that the model must output. Use {'type': - 'json_schema', 'json_schema': {...}} for structured outputs or {'type': - 'json_object'} for the legacy JSON mode. Currently only OpenAI-prefixed models - honour this field; Anthropic and Google requests will return an - invalid_request_error if it is supplied. + response_format: An object specifying the format that the model must output. Setting to + `{ "type": "json_schema", "json_schema": {...} }` enables Structured Outputs + which ensures the model will match your supplied JSON schema. Learn more in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. - safety_identifier: Stable identifier used to help detect users who might violate OpenAI usage - policies. Consider hashing end-user identifiers before sending. + safe_prompt: Whether to inject a safety prompt before all conversations. - safety_settings: Google safety settings (harm categories and thresholds). + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user. We recommend hashing their username or email address, in + order to avoid sending us any identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). - search_parameters: xAI-specific parameter for configuring web search data acquisition. If not set, - no data will be acquired by the model. + safety_settings: Safety/content filtering settings (Google-specific) - seed: If specified, system will make a best effort to sample deterministically. - Determinism is not guaranteed for the same seed across different models or API - versions. + search_parameters: Set the parameters to be used for searched data. If not set, no data will be + acquired by the model. - service_tier: Specifies the processing tier used for the request. 'auto' uses project - defaults, while 'default' forces standard pricing and performance. + seed: Random seed for deterministic output - stop: Not supported with latest reasoning models 'o3' and 'o4-mini'. + service_tier: Service tier for request processing - Up to 4 sequences where the API will stop generating further tokens; the returned text will not contain the stop sequence. + stop: Sequences that stop generation - store: Whether to store the output of this chat completion request for OpenAI model - distillation or eval products. Image inputs over 8MB are dropped if storage is - enabled. + store: Whether or not to store the output of this chat completion request for use in + our [model distillation](https://platform.openai.com/docs/guides/distillation) + or [evals](https://platform.openai.com/docs/guides/evals) products. Supports + text and image inputs. Note: image inputs over 8MB will be dropped. - stream_options: Options for streaming responses. Only set when 'stream' is true (supports - 'include_usage' and 'include_obfuscation'). + stream_options: Options for streaming response. Only set this when you set `stream: true`. - system: System prompt/instructions. Anthropic: pass-through. Google: converted to - systemInstruction. OpenAI: extracted from messages. + system_instruction: System instruction/prompt - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 make - the output more random, while lower values like 0.2 make it more focused and - deterministic. We generally recommend altering this or 'top_p' but not both. + temperature: Sampling temperature (0-2 for most providers) - thinking: Extended thinking configuration (Anthropic only). Enables thinking blocks - showing reasoning process. Requires min 1,024 token budget. + thinking: Extended thinking configuration (Anthropic-specific) - tool_choice: Controls which (if any) tool is called by the model. 'none' stops tool calling, - 'auto' lets the model decide, and 'required' forces at least one tool - invocation. Specific tool payloads force that tool. + tool_choice: Controls which (if any) tool is called by the model. `none` means the model will + not call any tool and instead generates a message. `auto` means the model can + pick between generating a message or calling one or more tools. `required` means + the model must call one or more tools. Specifying a particular tool via + `{"type": "function", "function": {"name": "my_function"}}` forces the model to + call that tool. `none` is the default when no tools are present. `auto` is the + default if tools are present. - tool_config: Google tool configuration (function calling mode, etc.). + tool_config: Tool calling configuration (Google-specific) - tools: A list of tools the model may call. Supports OpenAI function tools and custom - tools; use 'mcp_servers' for Dedalus-managed server-side tools. + tools: Available tools/functions for the model - top_k: Top-k sampling. Anthropic: pass-through. Google: injected into - generationConfig.topK. + top_k: Top-k sampling parameter - top_logprobs: An integer between 0 and 20 specifying how many of the most likely tokens to - return at each position, with log probabilities. Requires 'logprobs' to be true. + top_logprobs: An integer between 0 and 20 specifying the number of most likely tokens to + return at each token position, each with an associated log probability. + `logprobs` must be set to `true` if this parameter is used. - top_p: An alternative to sampling with temperature, called nucleus sampling, where the - model considers the results of the tokens with top_p probability mass. So 0.1 - means only the tokens comprising the top 10% probability mass are considered. We - generally recommend altering this or 'temperature' but not both. + top_p: Nucleus sampling threshold - user: Stable identifier for your end-users. Helps OpenAI detect and prevent abuse and - may boost cache hit rates. This field is being replaced by 'safety_identifier' - and 'prompt_cache_key'. + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). - verbosity: Constrains the verbosity of the model's response. Lower values produce concise - answers, higher values allow more detail. + verbosity: Constrains the verbosity of the model's response. Lower values will result in + more concise responses, while higher values will result in more verbose + responses. Currently supported values are `low`, `medium`, and `high`. - web_search_options: Configuration for OpenAI's web search tool. Learn more at - https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat. + web_search_options: This tool searches the web for relevant results to use in a response. Learn more + about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). extra_headers: Send extra headers @@ -716,55 +730,57 @@ def create( model: completion_create_params.Model, stream: bool, agent_attributes: Optional[Dict[str, float]] | Omit = omit, - audio: Optional[Dict[str, object]] | Omit = omit, - auto_execute_tools: bool | Omit = omit, + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + automatic_tool_execution: bool | Omit = omit, + cached_content: Optional[str] | Omit = omit, + credentials: Optional[completion_create_params.Credentials] | Omit = omit, deferred: Optional[bool] | Omit = omit, - disable_automatic_function_calling: Optional[bool] | Omit = omit, frequency_penalty: Optional[float] | Omit = omit, - function_call: Union[str, Dict[str, object], None] | Omit = omit, - functions: Optional[Iterable[Dict[str, object]]] | Omit = omit, - generation_config: Optional[Dict[str, object]] | Omit = omit, + function_call: Optional[str] | Omit = omit, + functions: Optional[Iterable[ChatCompletionFunctionsParam]] | Omit = omit, + generation_config: Optional[JSONObjectInput] | Omit = omit, guardrails: Optional[Iterable[Dict[str, object]]] | Omit = omit, handoff_config: Optional[Dict[str, object]] | Omit = omit, - input: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - instructions: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, logit_bias: Optional[Dict[str, int]] | Omit = omit, logprobs: Optional[bool] | Omit = omit, max_completion_tokens: Optional[int] | Omit = omit, max_tokens: Optional[int] | Omit = omit, max_turns: Optional[int] | Omit = omit, - mcp_servers: Union[str, SequenceNotStr[str], None] | Omit = omit, - messages: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - metadata: Optional[Dict[str, str]] | Omit = omit, + mcp_servers: Optional[completion_create_params.MCPServers] | Omit = omit, + messages: Optional[Iterable[completion_create_params.Message]] | Omit = omit, + metadata: Optional[JSONObjectInput] | Omit = omit, modalities: Optional[SequenceNotStr[str]] | Omit = omit, model_attributes: Optional[Dict[str, Dict[str, float]]] | Omit = omit, n: Optional[int] | Omit = omit, parallel_tool_calls: Optional[bool] | Omit = omit, - prediction: Optional[Dict[str, object]] | Omit = omit, + prediction: Optional[PredictionContentParam] | Omit = omit, presence_penalty: Optional[float] | Omit = omit, prompt_cache_key: Optional[str] | Omit = omit, - reasoning_effort: Optional[Literal["low", "medium", "high"]] | Omit = omit, + prompt_cache_retention: Optional[str] | Omit = omit, + prompt_mode: Optional[Literal["reasoning"]] | Omit = omit, + reasoning_effort: Optional[str] | Omit = omit, response_format: Optional[completion_create_params.ResponseFormat] | Omit = omit, + safe_prompt: Optional[bool] | Omit = omit, safety_identifier: Optional[str] | Omit = omit, - safety_settings: Optional[Iterable[Dict[str, object]]] | Omit = omit, - search_parameters: Optional[Dict[str, object]] | Omit = omit, + safety_settings: Optional[Iterable[completion_create_params.SafetySetting]] | Omit = omit, + search_parameters: Optional[JSONObjectInput] | Omit = omit, seed: Optional[int] | Omit = omit, - service_tier: Optional[Literal["auto", "default"]] | Omit = omit, - stop: Optional[SequenceNotStr[str]] | Omit = omit, + service_tier: Optional[str] | Omit = omit, + stop: Union[SequenceNotStr[str], str, None] | Omit = omit, store: Optional[bool] | Omit = omit, - stream_options: Optional[Dict[str, object]] | Omit = omit, - system: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, + stream_options: Optional[JSONObjectInput] | Omit = omit, + system_instruction: Union[JSONObjectInput, str, None] | Omit = omit, temperature: Optional[float] | Omit = omit, thinking: Optional[completion_create_params.Thinking] | Omit = omit, - tool_choice: Union[str, Dict[str, object], None] | Omit = omit, - tool_config: Optional[Dict[str, object]] | Omit = omit, - tools: Optional[Iterable[Dict[str, object]]] | Omit = omit, + tool_choice: Optional[completion_create_params.ToolChoice] | Omit = omit, + tool_config: Optional[JSONObjectInput] | Omit = omit, + tools: Optional[Iterable[completion_create_params.Tool]] | Omit = omit, top_k: Optional[int] | Omit = omit, top_logprobs: Optional[int] | Omit = omit, top_p: Optional[float] | Omit = omit, user: Optional[str] | Omit = omit, - verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, - web_search_options: Optional[Dict[str, object]] | Omit = omit, + verbosity: Optional[str] | Omit = omit, + web_search_options: Optional[JSONObjectInput] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -772,23 +788,12 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, idempotency_key: str | None = None, - ) -> Completion | Stream[StreamChunk]: + ) -> ChatCompletion | Stream[ChatCompletionChunk]: """ Create a chat completion. - Unified chat-completions endpoint that works across many model providers. - Supports optional MCP integration, multi-model routing with agentic handoffs, - server- or client-executed tools, and both streaming and non-streaming delivery. - - Request body: - - - messages: ordered list of chat turns. - - model: identifier or a list of identifiers for routing. - - tools: optional tool declarations available to the model. - - mcp_servers: optional list of MCP server slugs to enable during the run. - - stream: boolean to request incremental output. - - config: optional generation parameters (e.g., temperature, max_tokens, - metadata). + Generates a model response for the given conversation and configuration. + Supports OpenAI-compatible parameters and provider-specific extensions. Headers: @@ -797,8 +802,8 @@ def create( Behavior: - - If multiple models are supplied, the router may select or hand off across - them. + - If multiple models are supplied, the first one is used, and the agent may hand + off to another model. - Tools may be invoked on the server or signaled for the client to run. - Streaming responses emit incremental deltas; non-streaming returns a single object. @@ -838,190 +843,204 @@ def create( [DONE] Args: - model: Model(s) to use for completion. Can be a single model ID, a DedalusModel object, - or a list for multi-model routing. Single model: 'openai/gpt-4', - 'anthropic/claude-3-5-sonnet-20241022', 'openai/gpt-4o-mini', or a DedalusModel - instance. Multi-model routing: ['openai/gpt-4o-mini', 'openai/gpt-4', - 'anthropic/claude-3-5-sonnet'] or list of DedalusModel objects - agent will - choose optimal model based on task complexity. + model: Model identifier. Accepts model ID strings, lists for routing, or DedalusModel + objects with per-model settings. + + stream: Enable streaming response + + agent_attributes: Agent attributes. Values in [0.0, 1.0]. - stream: If true, the model response data is streamed to the client as it is generated - using Server-Sent Events. + audio: Parameters for audio output. Required when audio output is requested with + `modalities: ["audio"]`. + [Learn more](https://platform.openai.com/docs/guides/audio). - agent_attributes: Attributes for the agent itself, influencing behavior and model selection. - Format: {'attribute': value}, where values are 0.0-1.0. Common attributes: - 'complexity', 'accuracy', 'efficiency', 'creativity', 'friendliness'. Higher - values indicate stronger preference for that characteristic. + Fields: - audio: Parameters for audio output. Required when requesting audio responses (for - example, modalities including 'audio'). + - voice (required): VoiceIdsShared + - format (required): Literal["wav", "aac", "mp3", "flac", "opus", "pcm16"] - auto_execute_tools: When False, skip server-side tool execution and return raw OpenAI-style - tool_calls in the response. + automatic_tool_execution: Execute tools server-side. If false, returns raw tool calls for manual handling. - deferred: xAI-specific parameter. If set to true, the request returns a request_id for - async completion retrieval via GET /v1/chat/deferred-completion/{request_id}. + cached_content: Optional. The name of the content + [cached](https://ai.google.dev/gemini-api/docs/caching) to use as context to + serve the prediction. Format: `cachedContents/{cachedContent}` - disable_automatic_function_calling: Google-only flag to disable the SDK's automatic function execution. When true, - the model returns function calls for the client to execute manually. + credentials: Credentials for MCP server authentication. Each credential is matched to servers + by connection name. + + deferred: If set to `true`, the request returns a `request_id`. You can then get the + deferred response by GET `/v1/chat/deferred-completion/{request_id}`. frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - function_call: Deprecated in favor of 'tool_choice'. Controls which function is called by the - model (none, auto, or specific name). + function_call: Wrapper for union variant: function call mode. - functions: Deprecated in favor of 'tools'. Legacy list of function definitions the model - may generate JSON inputs for. + functions: Deprecated in favor of `tools`. A list of functions the model may generate JSON + inputs for. - generation_config: Google generationConfig object. Merged with auto-generated config. Use for - Google-specific params (candidateCount, responseMimeType, etc.). + generation_config: Generation parameters wrapper (Google-specific) - guardrails: Guardrails to apply to the agent for input/output validation and safety checks. - Reserved for future use - guardrails configuration format not yet finalized. + guardrails: Content filtering and safety policy configuration. - handoff_config: Configuration for multi-model handoffs and agent orchestration. Reserved for - future use - handoff configuration format not yet finalized. + handoff_config: Configuration for multi-model handoffs. - input: Convenience alias for Responses-style `input`. Used when `messages` is omitted - to provide the user prompt directly. + logit_bias: Modify the likelihood of specified tokens appearing in the completion. Accepts a + JSON object that maps tokens (specified by their token ID in the tokenizer) to + an associated bias value from -100 to 100. Mathematically, the bias is added to + the logits generated by the model prior to sampling. The exact effect will vary + per model, but values between -1 and 1 should decrease or increase likelihood of + selection; values like -100 or 100 should result in a ban or exclusive selection + of the relevant token. - instructions: Convenience alias for Responses-style `instructions`. Takes precedence over - `system` and over system-role messages when provided. + logprobs: Whether to return log probabilities of the output tokens or not. If true, + returns the log probabilities of each output token returned in the `content` of + `message`. - logit_bias: Modify the likelihood of specified tokens appearing in the completion. Accepts a - JSON object mapping token IDs (as strings) to bias values from -100 to 100. The - bias is added to the logits before sampling; values between -1 and 1 nudge - selection probability, while values like -100 or 100 effectively ban or require - a token. + max_completion_tokens: Maximum tokens in completion (newer parameter name) - logprobs: Whether to return log probabilities of the output tokens. If true, returns the - log probabilities for each token in the response content. + max_tokens: Maximum tokens in completion - max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, - including visible output and reasoning tokens. + max_turns: Maximum conversation turns. - max_tokens: The maximum number of tokens that can be generated in the chat completion. This - value can be used to control costs for text generated via API. This value is now - deprecated in favor of 'max_completion_tokens' and is not compatible with - o-series models. + mcp_servers: MCP server identifiers. Accepts marketplace slugs, URLs, or MCPServerSpec + objects. MCP tools are executed server-side and billed separately. - max_turns: Maximum number of turns for agent execution before terminating (default: 10). - Each turn represents one model inference cycle. Higher values allow more complex - reasoning but increase cost and latency. + messages: Conversation history (OpenAI: messages, Google: contents, Responses: input) - mcp_servers: MCP (Model Context Protocol) server addresses to make available for server-side - tool execution. Entries can be URLs (e.g., 'https://mcp.example.com'), slugs - (e.g., 'dedalus-labs/brave-search'), or structured objects specifying - slug/version/url. MCP tools are executed server-side and billed separately. + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. Keys are strings with a maximum + length of 64 characters. Values are strings with a maximum length of 512 + characters. - messages: Conversation history. Accepts either a list of message objects or a string, - which is treated as a single user message. Optional if `input` or `instructions` - is provided. + modalities: Output types that you would like the model to generate. Most models are capable + of generating text, which is the default: `["text"]` The `gpt-4o-audio-preview` + model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that + this model generate both text and audio responses, you can use: + `["text", "audio"]` - metadata: Set of up to 16 key-value string pairs that can be attached to the request for - structured metadata. + model_attributes: Model attributes for routing. Maps model IDs to attribute dictionaries with + values in [0.0, 1.0]. - modalities: Output types you would like the model to generate. Most models default to - ['text']; some support ['text', 'audio']. + n: How many chat completion choices to generate for each input message. Note that + you will be charged based on the number of generated tokens across all of the + choices. Keep `n` as `1` to minimize costs. - model_attributes: Attributes for individual models used in routing decisions during multi-model - execution. Format: {'model_name': {'attribute': value}}, where values are - 0.0-1.0. Common attributes: 'intelligence', 'speed', 'cost', 'creativity', - 'accuracy'. Used by agent to select optimal model based on task requirements. + parallel_tool_calls: Whether to enable parallel tool calls (Anthropic uses inverted polarity) - n: How many chat completion choices to generate for each input message. Keep 'n' as - 1 to minimize costs. + prediction: Static predicted output content, such as the content of a text file that is + being regenerated. - parallel_tool_calls: Whether to enable parallel function calling during tool use. + Fields: - prediction: Configuration for predicted outputs. Improves response times when you already - know large portions of the response content. + - type (required): Literal["content"] + - content (required): str | + Annotated[list[ChatCompletionRequestMessageContentPartText], MinLen(1), + ArrayTitle("PredictionContentArray")] presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - prompt_cache_key: Used by OpenAI to cache responses for similar requests and optimize cache hit - rates. Replaces the legacy 'user' field for caching. + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + + prompt_mode: Allows toggling between the reasoning mode and no system prompt. When set to + `reasoning` the system prompt for reasoning models will be used. - reasoning_effort: Constrains effort on reasoning for supported reasoning models. Higher values use - more compute, potentially improving reasoning quality at the cost of latency and - tokens. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, and `high`. Reducing + reasoning effort can result in faster responses and fewer tokens used on + reasoning in a response. - `gpt-5.1` defaults to `none`, which does not perform + reasoning. The supported reasoning values for `gpt-5.1` are `none`, `low`, + `medium`, and `high`. Tool calls are supported for all reasoning values in + gpt-5.1. - All models before `gpt-5.1` default to `medium` reasoning effort, and + do not support `none`. - The `gpt-5-pro` model defaults to (and only supports) + `high` reasoning effort. - response_format: - An object specifying the format that the model must output. Use {'type': - 'json_schema', 'json_schema': {...}} for structured outputs or {'type': - 'json_object'} for the legacy JSON mode. Currently only OpenAI-prefixed models - honour this field; Anthropic and Google requests will return an - invalid_request_error if it is supplied. + response_format: An object specifying the format that the model must output. Setting to + `{ "type": "json_schema", "json_schema": {...} }` enables Structured Outputs + which ensures the model will match your supplied JSON schema. Learn more in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. - safety_identifier: Stable identifier used to help detect users who might violate OpenAI usage - policies. Consider hashing end-user identifiers before sending. + safe_prompt: Whether to inject a safety prompt before all conversations. - safety_settings: Google safety settings (harm categories and thresholds). + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user. We recommend hashing their username or email address, in + order to avoid sending us any identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). - search_parameters: xAI-specific parameter for configuring web search data acquisition. If not set, - no data will be acquired by the model. + safety_settings: Safety/content filtering settings (Google-specific) - seed: If specified, system will make a best effort to sample deterministically. - Determinism is not guaranteed for the same seed across different models or API - versions. + search_parameters: Set the parameters to be used for searched data. If not set, no data will be + acquired by the model. - service_tier: Specifies the processing tier used for the request. 'auto' uses project - defaults, while 'default' forces standard pricing and performance. + seed: Random seed for deterministic output - stop: Not supported with latest reasoning models 'o3' and 'o4-mini'. + service_tier: Service tier for request processing - Up to 4 sequences where the API will stop generating further tokens; the returned text will not contain the stop sequence. + stop: Sequences that stop generation - store: Whether to store the output of this chat completion request for OpenAI model - distillation or eval products. Image inputs over 8MB are dropped if storage is - enabled. + store: Whether or not to store the output of this chat completion request for use in + our [model distillation](https://platform.openai.com/docs/guides/distillation) + or [evals](https://platform.openai.com/docs/guides/evals) products. Supports + text and image inputs. Note: image inputs over 8MB will be dropped. - stream_options: Options for streaming responses. Only set when 'stream' is true (supports - 'include_usage' and 'include_obfuscation'). + stream_options: Options for streaming response. Only set this when you set `stream: true`. - system: System prompt/instructions. Anthropic: pass-through. Google: converted to - systemInstruction. OpenAI: extracted from messages. + system_instruction: System instruction/prompt - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 make - the output more random, while lower values like 0.2 make it more focused and - deterministic. We generally recommend altering this or 'top_p' but not both. + temperature: Sampling temperature (0-2 for most providers) - thinking: Extended thinking configuration (Anthropic only). Enables thinking blocks - showing reasoning process. Requires min 1,024 token budget. + thinking: Extended thinking configuration (Anthropic-specific) - tool_choice: Controls which (if any) tool is called by the model. 'none' stops tool calling, - 'auto' lets the model decide, and 'required' forces at least one tool - invocation. Specific tool payloads force that tool. + tool_choice: Controls which (if any) tool is called by the model. `none` means the model will + not call any tool and instead generates a message. `auto` means the model can + pick between generating a message or calling one or more tools. `required` means + the model must call one or more tools. Specifying a particular tool via + `{"type": "function", "function": {"name": "my_function"}}` forces the model to + call that tool. `none` is the default when no tools are present. `auto` is the + default if tools are present. - tool_config: Google tool configuration (function calling mode, etc.). + tool_config: Tool calling configuration (Google-specific) - tools: A list of tools the model may call. Supports OpenAI function tools and custom - tools; use 'mcp_servers' for Dedalus-managed server-side tools. + tools: Available tools/functions for the model - top_k: Top-k sampling. Anthropic: pass-through. Google: injected into - generationConfig.topK. + top_k: Top-k sampling parameter - top_logprobs: An integer between 0 and 20 specifying how many of the most likely tokens to - return at each position, with log probabilities. Requires 'logprobs' to be true. + top_logprobs: An integer between 0 and 20 specifying the number of most likely tokens to + return at each token position, each with an associated log probability. + `logprobs` must be set to `true` if this parameter is used. - top_p: An alternative to sampling with temperature, called nucleus sampling, where the - model considers the results of the tokens with top_p probability mass. So 0.1 - means only the tokens comprising the top 10% probability mass are considered. We - generally recommend altering this or 'temperature' but not both. + top_p: Nucleus sampling threshold - user: Stable identifier for your end-users. Helps OpenAI detect and prevent abuse and - may boost cache hit rates. This field is being replaced by 'safety_identifier' - and 'prompt_cache_key'. + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). - verbosity: Constrains the verbosity of the model's response. Lower values produce concise - answers, higher values allow more detail. + verbosity: Constrains the verbosity of the model's response. Lower values will result in + more concise responses, while higher values will result in more verbose + responses. Currently supported values are `low`, `medium`, and `high`. - web_search_options: Configuration for OpenAI's web search tool. Learn more at - https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat. + web_search_options: This tool searches the web for relevant results to use in a response. Learn more + about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). extra_headers: Send extra headers @@ -1041,56 +1060,58 @@ def create( *, model: completion_create_params.Model, agent_attributes: Optional[Dict[str, float]] | Omit = omit, - audio: Optional[Dict[str, object]] | Omit = omit, - auto_execute_tools: bool | Omit = omit, + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + automatic_tool_execution: bool | Omit = omit, + cached_content: Optional[str] | Omit = omit, + credentials: Optional[completion_create_params.Credentials] | Omit = omit, deferred: Optional[bool] | Omit = omit, - disable_automatic_function_calling: Optional[bool] | Omit = omit, frequency_penalty: Optional[float] | Omit = omit, - function_call: Union[str, Dict[str, object], None] | Omit = omit, - functions: Optional[Iterable[Dict[str, object]]] | Omit = omit, - generation_config: Optional[Dict[str, object]] | Omit = omit, + function_call: Optional[str] | Omit = omit, + functions: Optional[Iterable[ChatCompletionFunctionsParam]] | Omit = omit, + generation_config: Optional[JSONObjectInput] | Omit = omit, guardrails: Optional[Iterable[Dict[str, object]]] | Omit = omit, handoff_config: Optional[Dict[str, object]] | Omit = omit, - input: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - instructions: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, logit_bias: Optional[Dict[str, int]] | Omit = omit, logprobs: Optional[bool] | Omit = omit, max_completion_tokens: Optional[int] | Omit = omit, max_tokens: Optional[int] | Omit = omit, max_turns: Optional[int] | Omit = omit, - mcp_servers: Union[str, SequenceNotStr[str], None] | Omit = omit, - messages: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - metadata: Optional[Dict[str, str]] | Omit = omit, + mcp_servers: Optional[completion_create_params.MCPServers] | Omit = omit, + messages: Optional[Iterable[completion_create_params.Message]] | Omit = omit, + metadata: Optional[JSONObjectInput] | Omit = omit, modalities: Optional[SequenceNotStr[str]] | Omit = omit, model_attributes: Optional[Dict[str, Dict[str, float]]] | Omit = omit, n: Optional[int] | Omit = omit, parallel_tool_calls: Optional[bool] | Omit = omit, - prediction: Optional[Dict[str, object]] | Omit = omit, + prediction: Optional[PredictionContentParam] | Omit = omit, presence_penalty: Optional[float] | Omit = omit, prompt_cache_key: Optional[str] | Omit = omit, - reasoning_effort: Optional[Literal["low", "medium", "high"]] | Omit = omit, + prompt_cache_retention: Optional[str] | Omit = omit, + prompt_mode: Optional[Literal["reasoning"]] | Omit = omit, + reasoning_effort: Optional[str] | Omit = omit, response_format: Optional[completion_create_params.ResponseFormat] | Omit = omit, + safe_prompt: Optional[bool] | Omit = omit, safety_identifier: Optional[str] | Omit = omit, - safety_settings: Optional[Iterable[Dict[str, object]]] | Omit = omit, - search_parameters: Optional[Dict[str, object]] | Omit = omit, + safety_settings: Optional[Iterable[completion_create_params.SafetySetting]] | Omit = omit, + search_parameters: Optional[JSONObjectInput] | Omit = omit, seed: Optional[int] | Omit = omit, - service_tier: Optional[Literal["auto", "default"]] | Omit = omit, - stop: Optional[SequenceNotStr[str]] | Omit = omit, + service_tier: Optional[str] | Omit = omit, + stop: Union[SequenceNotStr[str], str, None] | Omit = omit, store: Optional[bool] | Omit = omit, - stream: Literal[False] | Literal[True] | Omit = omit, - stream_options: Optional[Dict[str, object]] | Omit = omit, - system: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + stream_options: Optional[JSONObjectInput] | Omit = omit, + system_instruction: Union[JSONObjectInput, str, None] | Omit = omit, temperature: Optional[float] | Omit = omit, thinking: Optional[completion_create_params.Thinking] | Omit = omit, - tool_choice: Union[str, Dict[str, object], None] | Omit = omit, - tool_config: Optional[Dict[str, object]] | Omit = omit, - tools: Optional[Iterable[Dict[str, object]]] | Omit = omit, + tool_choice: Optional[completion_create_params.ToolChoice] | Omit = omit, + tool_config: Optional[JSONObjectInput] | Omit = omit, + tools: Optional[Iterable[completion_create_params.Tool]] | Omit = omit, top_k: Optional[int] | Omit = omit, top_logprobs: Optional[int] | Omit = omit, top_p: Optional[float] | Omit = omit, user: Optional[str] | Omit = omit, - verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, - web_search_options: Optional[Dict[str, object]] | Omit = omit, + verbosity: Optional[str] | Omit = omit, + web_search_options: Optional[JSONObjectInput] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -1098,13 +1119,17 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, idempotency_key: str | None = None, - ) -> Completion | Stream[StreamChunk]: + ) -> ChatCompletion | Stream[ChatCompletionChunk]: import inspect import pydantic from ..._utils import is_given # Validate response_format is not a Pydantic model - if is_given(response_format) and inspect.isclass(response_format) and issubclass(response_format, pydantic.BaseModel): + if ( + is_given(response_format) + and inspect.isclass(response_format) + and issubclass(response_format, pydantic.BaseModel) + ): raise TypeError( "You tried to pass a `BaseModel` class to `chat.completions.create()`; " "You must use `chat.completions.parse()` instead" @@ -1117,17 +1142,16 @@ def create( "model": model, "agent_attributes": agent_attributes, "audio": audio, - "auto_execute_tools": auto_execute_tools, + "automatic_tool_execution": automatic_tool_execution, + "cached_content": cached_content, + "credentials": credentials, "deferred": deferred, - "disable_automatic_function_calling": disable_automatic_function_calling, "frequency_penalty": frequency_penalty, "function_call": function_call, "functions": functions, "generation_config": generation_config, "guardrails": guardrails, "handoff_config": handoff_config, - "input": input, - "instructions": instructions, "logit_bias": logit_bias, "logprobs": logprobs, "max_completion_tokens": max_completion_tokens, @@ -1143,8 +1167,11 @@ def create( "prediction": prediction, "presence_penalty": presence_penalty, "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "prompt_mode": prompt_mode, "reasoning_effort": reasoning_effort, "response_format": response_format, + "safe_prompt": safe_prompt, "safety_identifier": safety_identifier, "safety_settings": safety_settings, "search_parameters": search_parameters, @@ -1154,7 +1181,7 @@ def create( "store": store, "stream": stream, "stream_options": stream_options, - "system": system, + "system_instruction": system_instruction, "temperature": temperature, "thinking": thinking, "tool_choice": tool_choice, @@ -1178,9 +1205,9 @@ def create( timeout=timeout, idempotency_key=idempotency_key, ), - cast_to=Completion, + cast_to=ChatCompletion, stream=stream or False, - stream_cls=Stream[StreamChunk], + stream_cls=Stream[ChatCompletionChunk], ) def parse( @@ -1193,9 +1220,8 @@ def parse( instructions: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, agent_attributes: Optional[Dict[str, float]] | Omit = omit, audio: Optional[Dict[str, object]] | Omit = omit, - auto_execute_tools: bool | Omit = omit, + automatic_tool_execution: bool | Omit = omit, deferred: Optional[bool] | Omit = omit, - disable_automatic_function_calling: Optional[bool] | Omit = omit, frequency_penalty: Optional[float] | Omit = omit, function_call: Union[str, Dict[str, object], None] | Omit = omit, functions: Optional[Iterable[Dict[str, object]]] | Omit = omit, @@ -1288,9 +1314,8 @@ def parser(raw_completion: Completion): "model": model, "agent_attributes": agent_attributes, "audio": audio, - "auto_execute_tools": auto_execute_tools, + "automatic_tool_execution": automatic_tool_execution, "deferred": deferred, - "disable_automatic_function_calling": disable_automatic_function_calling, "frequency_penalty": frequency_penalty, "function_call": function_call, "functions": functions, @@ -1356,19 +1381,19 @@ def stream( model: completion_create_params.Model, messages: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, response_format: type[ResponseFormatT] | Omit = omit, + input: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, + instructions: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, + system: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, agent_attributes: Optional[Dict[str, float]] | Omit = omit, audio: Optional[Dict[str, object]] | Omit = omit, - auto_execute_tools: bool | Omit = omit, + automatic_tool_execution: bool | Omit = omit, deferred: Optional[bool] | Omit = omit, - disable_automatic_function_calling: Optional[bool] | Omit = omit, frequency_penalty: Optional[float] | Omit = omit, function_call: Union[str, Dict[str, object], None] | Omit = omit, functions: Optional[Iterable[Dict[str, object]]] | Omit = omit, generation_config: Optional[Dict[str, object]] | Omit = omit, guardrails: Optional[Iterable[Dict[str, object]]] | Omit = omit, handoff_config: Optional[Dict[str, object]] | Omit = omit, - input: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - instructions: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, logit_bias: Optional[Dict[str, int]] | Omit = omit, logprobs: Optional[bool] | Omit = omit, max_completion_tokens: Optional[int] | Omit = omit, @@ -1392,7 +1417,6 @@ def stream( stop: Optional[SequenceNotStr[str]] | Omit = omit, store: Optional[bool] | Omit = omit, stream_options: Optional[Dict[str, object]] | Omit = omit, - system: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, temperature: Optional[float] | Omit = omit, thinking: Optional[completion_create_params.Thinking] | Omit = omit, tool_choice: Union[str, Dict[str, object], None] | Omit = omit, @@ -1419,64 +1443,74 @@ def stream( } api_request = partial( - self.create, - model=model, - messages=messages, - agent_attributes=agent_attributes, - audio=audio, - auto_execute_tools=auto_execute_tools, - deferred=deferred, - disable_automatic_function_calling=disable_automatic_function_calling, - frequency_penalty=frequency_penalty, - function_call=function_call, - functions=functions, - generation_config=generation_config, - guardrails=guardrails, - handoff_config=handoff_config, - input=input, - instructions=instructions, - logit_bias=logit_bias, - logprobs=logprobs, - max_completion_tokens=max_completion_tokens, - max_tokens=max_tokens, - max_turns=max_turns, - mcp_servers=mcp_servers, - metadata=metadata, - modalities=modalities, - model_attributes=model_attributes, - n=n, - parallel_tool_calls=parallel_tool_calls, - prediction=prediction, - presence_penalty=presence_penalty, - prompt_cache_key=prompt_cache_key, - reasoning_effort=reasoning_effort, - safety_identifier=safety_identifier, - safety_settings=safety_settings, - search_parameters=search_parameters, - seed=seed, - service_tier=service_tier, - stop=stop, - store=store, + self._post, + "/v1/chat/completions", + body=maybe_transform( + { + "messages": messages, + "model": model, + "agent_attributes": agent_attributes, + "audio": audio, + "automatic_tool_execution": automatic_tool_execution, + "deferred": deferred, + "frequency_penalty": frequency_penalty, + "function_call": function_call, + "functions": functions, + "generation_config": generation_config, + "guardrails": guardrails, + "handoff_config": handoff_config, + "input": input, + "instructions": instructions, + "logit_bias": logit_bias, + "logprobs": logprobs, + "max_completion_tokens": max_completion_tokens, + "max_tokens": max_tokens, + "max_turns": max_turns, + "mcp_servers": mcp_servers, + "metadata": metadata, + "modalities": modalities, + "model_attributes": model_attributes, + "n": n, + "parallel_tool_calls": parallel_tool_calls, + "prediction": prediction, + "presence_penalty": presence_penalty, + "prompt_cache_key": prompt_cache_key, + "reasoning_effort": reasoning_effort, + "response_format": _type_to_response_format(response_format), + "safety_identifier": safety_identifier, + "safety_settings": safety_settings, + "search_parameters": search_parameters, + "seed": seed, + "service_tier": service_tier, + "stop": stop, + "store": store, + "stream": True, + "stream_options": stream_options, + "system": system, + "temperature": temperature, + "thinking": thinking, + "tool_choice": tool_choice, + "tool_config": tool_config, + "tools": tools, + "top_k": top_k, + "top_logprobs": top_logprobs, + "top_p": top_p, + "user": user, + "verbosity": verbosity, + "web_search_options": web_search_options, + }, + completion_create_params.CompletionCreateParamsStreaming, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ChatCompletion, stream=True, - stream_options=stream_options, - system=system, - temperature=temperature, - thinking=thinking, - tool_choice=tool_choice, - tool_config=tool_config, - tools=tools, - top_k=top_k, - top_logprobs=top_logprobs, - top_p=top_p, - user=user, - verbosity=verbosity, - web_search_options=web_search_options, - response_format=_type_to_response_format(response_format), - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - idempotency_key=idempotency_key, + stream_cls=Stream[ChatCompletionChunk], ) return ChatCompletionStreamManager( @@ -1512,56 +1546,58 @@ async def create( *, model: completion_create_params.Model, agent_attributes: Optional[Dict[str, float]] | Omit = omit, - audio: Optional[Dict[str, object]] | Omit = omit, - auto_execute_tools: bool | Omit = omit, + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + automatic_tool_execution: bool | Omit = omit, + cached_content: Optional[str] | Omit = omit, + credentials: Optional[completion_create_params.Credentials] | Omit = omit, deferred: Optional[bool] | Omit = omit, - disable_automatic_function_calling: Optional[bool] | Omit = omit, frequency_penalty: Optional[float] | Omit = omit, - function_call: Union[str, Dict[str, object], None] | Omit = omit, - functions: Optional[Iterable[Dict[str, object]]] | Omit = omit, - generation_config: Optional[Dict[str, object]] | Omit = omit, + function_call: Optional[str] | Omit = omit, + functions: Optional[Iterable[ChatCompletionFunctionsParam]] | Omit = omit, + generation_config: Optional[JSONObjectInput] | Omit = omit, guardrails: Optional[Iterable[Dict[str, object]]] | Omit = omit, handoff_config: Optional[Dict[str, object]] | Omit = omit, - input: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - instructions: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, logit_bias: Optional[Dict[str, int]] | Omit = omit, logprobs: Optional[bool] | Omit = omit, max_completion_tokens: Optional[int] | Omit = omit, max_tokens: Optional[int] | Omit = omit, max_turns: Optional[int] | Omit = omit, - mcp_servers: Union[str, SequenceNotStr[str], None] | Omit = omit, - messages: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - metadata: Optional[Dict[str, str]] | Omit = omit, + mcp_servers: Optional[completion_create_params.MCPServers] | Omit = omit, + messages: Optional[Iterable[completion_create_params.Message]] | Omit = omit, + metadata: Optional[JSONObjectInput] | Omit = omit, modalities: Optional[SequenceNotStr[str]] | Omit = omit, model_attributes: Optional[Dict[str, Dict[str, float]]] | Omit = omit, n: Optional[int] | Omit = omit, parallel_tool_calls: Optional[bool] | Omit = omit, - prediction: Optional[Dict[str, object]] | Omit = omit, + prediction: Optional[PredictionContentParam] | Omit = omit, presence_penalty: Optional[float] | Omit = omit, prompt_cache_key: Optional[str] | Omit = omit, - reasoning_effort: Optional[Literal["low", "medium", "high"]] | Omit = omit, + prompt_cache_retention: Optional[str] | Omit = omit, + prompt_mode: Optional[Literal["reasoning"]] | Omit = omit, + reasoning_effort: Optional[str] | Omit = omit, response_format: Optional[completion_create_params.ResponseFormat] | Omit = omit, + safe_prompt: Optional[bool] | Omit = omit, safety_identifier: Optional[str] | Omit = omit, - safety_settings: Optional[Iterable[Dict[str, object]]] | Omit = omit, - search_parameters: Optional[Dict[str, object]] | Omit = omit, + safety_settings: Optional[Iterable[completion_create_params.SafetySetting]] | Omit = omit, + search_parameters: Optional[JSONObjectInput] | Omit = omit, seed: Optional[int] | Omit = omit, - service_tier: Optional[Literal["auto", "default"]] | Omit = omit, - stop: Optional[SequenceNotStr[str]] | Omit = omit, + service_tier: Optional[str] | Omit = omit, + stop: Union[SequenceNotStr[str], str, None] | Omit = omit, store: Optional[bool] | Omit = omit, - stream: Literal[False] | Omit = omit, - stream_options: Optional[Dict[str, object]] | Omit = omit, - system: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, + stream: Optional[Literal[False]] | Omit = omit, + stream_options: Optional[JSONObjectInput] | Omit = omit, + system_instruction: Union[JSONObjectInput, str, None] | Omit = omit, temperature: Optional[float] | Omit = omit, thinking: Optional[completion_create_params.Thinking] | Omit = omit, - tool_choice: Union[str, Dict[str, object], None] | Omit = omit, - tool_config: Optional[Dict[str, object]] | Omit = omit, - tools: Optional[Iterable[Dict[str, object]]] | Omit = omit, + tool_choice: Optional[completion_create_params.ToolChoice] | Omit = omit, + tool_config: Optional[JSONObjectInput] | Omit = omit, + tools: Optional[Iterable[completion_create_params.Tool]] | Omit = omit, top_k: Optional[int] | Omit = omit, top_logprobs: Optional[int] | Omit = omit, top_p: Optional[float] | Omit = omit, user: Optional[str] | Omit = omit, - verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, - web_search_options: Optional[Dict[str, object]] | Omit = omit, + verbosity: Optional[str] | Omit = omit, + web_search_options: Optional[JSONObjectInput] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -1569,23 +1605,12 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, idempotency_key: str | None = None, - ) -> Completion: + ) -> ChatCompletion: """ Create a chat completion. - Unified chat-completions endpoint that works across many model providers. - Supports optional MCP integration, multi-model routing with agentic handoffs, - server- or client-executed tools, and both streaming and non-streaming delivery. - - Request body: - - - messages: ordered list of chat turns. - - model: identifier or a list of identifiers for routing. - - tools: optional tool declarations available to the model. - - mcp_servers: optional list of MCP server slugs to enable during the run. - - stream: boolean to request incremental output. - - config: optional generation parameters (e.g., temperature, max_tokens, - metadata). + Generates a model response for the given conversation and configuration. + Supports OpenAI-compatible parameters and provider-specific extensions. Headers: @@ -1594,8 +1619,8 @@ async def create( Behavior: - - If multiple models are supplied, the router may select or hand off across - them. + - If multiple models are supplied, the first one is used, and the agent may hand + off to another model. - Tools may be invoked on the server or signaled for the client to run. - Streaming responses emit incremental deltas; non-streaming returns a single object. @@ -1635,190 +1660,204 @@ async def create( [DONE] Args: - model: Model(s) to use for completion. Can be a single model ID, a DedalusModel object, - or a list for multi-model routing. Single model: 'openai/gpt-4', - 'anthropic/claude-3-5-sonnet-20241022', 'openai/gpt-4o-mini', or a DedalusModel - instance. Multi-model routing: ['openai/gpt-4o-mini', 'openai/gpt-4', - 'anthropic/claude-3-5-sonnet'] or list of DedalusModel objects - agent will - choose optimal model based on task complexity. + model: Model identifier. Accepts model ID strings, lists for routing, or DedalusModel + objects with per-model settings. - agent_attributes: Attributes for the agent itself, influencing behavior and model selection. - Format: {'attribute': value}, where values are 0.0-1.0. Common attributes: - 'complexity', 'accuracy', 'efficiency', 'creativity', 'friendliness'. Higher - values indicate stronger preference for that characteristic. + agent_attributes: Agent attributes. Values in [0.0, 1.0]. - audio: Parameters for audio output. Required when requesting audio responses (for - example, modalities including 'audio'). + audio: Parameters for audio output. Required when audio output is requested with + `modalities: ["audio"]`. + [Learn more](https://platform.openai.com/docs/guides/audio). - auto_execute_tools: When False, skip server-side tool execution and return raw OpenAI-style - tool_calls in the response. + Fields: - deferred: xAI-specific parameter. If set to true, the request returns a request_id for - async completion retrieval via GET /v1/chat/deferred-completion/{request_id}. + - voice (required): VoiceIdsShared + - format (required): Literal["wav", "aac", "mp3", "flac", "opus", "pcm16"] - disable_automatic_function_calling: Google-only flag to disable the SDK's automatic function execution. When true, - the model returns function calls for the client to execute manually. + automatic_tool_execution: Execute tools server-side. If false, returns raw tool calls for manual handling. + + cached_content: Optional. The name of the content + [cached](https://ai.google.dev/gemini-api/docs/caching) to use as context to + serve the prediction. Format: `cachedContents/{cachedContent}` + + credentials: Credentials for MCP server authentication. Each credential is matched to servers + by connection name. + + deferred: If set to `true`, the request returns a `request_id`. You can then get the + deferred response by GET `/v1/chat/deferred-completion/{request_id}`. frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - function_call: Deprecated in favor of 'tool_choice'. Controls which function is called by the - model (none, auto, or specific name). + function_call: Wrapper for union variant: function call mode. - functions: Deprecated in favor of 'tools'. Legacy list of function definitions the model - may generate JSON inputs for. + functions: Deprecated in favor of `tools`. A list of functions the model may generate JSON + inputs for. - generation_config: Google generationConfig object. Merged with auto-generated config. Use for - Google-specific params (candidateCount, responseMimeType, etc.). + generation_config: Generation parameters wrapper (Google-specific) - guardrails: Guardrails to apply to the agent for input/output validation and safety checks. - Reserved for future use - guardrails configuration format not yet finalized. + guardrails: Content filtering and safety policy configuration. - handoff_config: Configuration for multi-model handoffs and agent orchestration. Reserved for - future use - handoff configuration format not yet finalized. + handoff_config: Configuration for multi-model handoffs. - input: Convenience alias for Responses-style `input`. Used when `messages` is omitted - to provide the user prompt directly. + logit_bias: Modify the likelihood of specified tokens appearing in the completion. Accepts a + JSON object that maps tokens (specified by their token ID in the tokenizer) to + an associated bias value from -100 to 100. Mathematically, the bias is added to + the logits generated by the model prior to sampling. The exact effect will vary + per model, but values between -1 and 1 should decrease or increase likelihood of + selection; values like -100 or 100 should result in a ban or exclusive selection + of the relevant token. - instructions: Convenience alias for Responses-style `instructions`. Takes precedence over - `system` and over system-role messages when provided. + logprobs: Whether to return log probabilities of the output tokens or not. If true, + returns the log probabilities of each output token returned in the `content` of + `message`. - logit_bias: Modify the likelihood of specified tokens appearing in the completion. Accepts a - JSON object mapping token IDs (as strings) to bias values from -100 to 100. The - bias is added to the logits before sampling; values between -1 and 1 nudge - selection probability, while values like -100 or 100 effectively ban or require - a token. + max_completion_tokens: Maximum tokens in completion (newer parameter name) - logprobs: Whether to return log probabilities of the output tokens. If true, returns the - log probabilities for each token in the response content. + max_tokens: Maximum tokens in completion - max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, - including visible output and reasoning tokens. + max_turns: Maximum conversation turns. - max_tokens: The maximum number of tokens that can be generated in the chat completion. This - value can be used to control costs for text generated via API. This value is now - deprecated in favor of 'max_completion_tokens' and is not compatible with - o-series models. + mcp_servers: MCP server identifiers. Accepts marketplace slugs, URLs, or MCPServerSpec + objects. MCP tools are executed server-side and billed separately. - max_turns: Maximum number of turns for agent execution before terminating (default: 10). - Each turn represents one model inference cycle. Higher values allow more complex - reasoning but increase cost and latency. + messages: Conversation history (OpenAI: messages, Google: contents, Responses: input) - mcp_servers: MCP (Model Context Protocol) server addresses to make available for server-side - tool execution. Entries can be URLs (e.g., 'https://mcp.example.com'), slugs - (e.g., 'dedalus-labs/brave-search'), or structured objects specifying - slug/version/url. MCP tools are executed server-side and billed separately. + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. Keys are strings with a maximum + length of 64 characters. Values are strings with a maximum length of 512 + characters. - messages: Conversation history. Accepts either a list of message objects or a string, - which is treated as a single user message. Optional if `input` or `instructions` - is provided. + modalities: Output types that you would like the model to generate. Most models are capable + of generating text, which is the default: `["text"]` The `gpt-4o-audio-preview` + model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that + this model generate both text and audio responses, you can use: + `["text", "audio"]` - metadata: Set of up to 16 key-value string pairs that can be attached to the request for - structured metadata. + model_attributes: Model attributes for routing. Maps model IDs to attribute dictionaries with + values in [0.0, 1.0]. - modalities: Output types you would like the model to generate. Most models default to - ['text']; some support ['text', 'audio']. + n: How many chat completion choices to generate for each input message. Note that + you will be charged based on the number of generated tokens across all of the + choices. Keep `n` as `1` to minimize costs. - model_attributes: Attributes for individual models used in routing decisions during multi-model - execution. Format: {'model_name': {'attribute': value}}, where values are - 0.0-1.0. Common attributes: 'intelligence', 'speed', 'cost', 'creativity', - 'accuracy'. Used by agent to select optimal model based on task requirements. + parallel_tool_calls: Whether to enable parallel tool calls (Anthropic uses inverted polarity) - n: How many chat completion choices to generate for each input message. Keep 'n' as - 1 to minimize costs. + prediction: Static predicted output content, such as the content of a text file that is + being regenerated. - parallel_tool_calls: Whether to enable parallel function calling during tool use. + Fields: - prediction: Configuration for predicted outputs. Improves response times when you already - know large portions of the response content. + - type (required): Literal["content"] + - content (required): str | + Annotated[list[ChatCompletionRequestMessageContentPartText], MinLen(1), + ArrayTitle("PredictionContentArray")] presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - prompt_cache_key: Used by OpenAI to cache responses for similar requests and optimize cache hit - rates. Replaces the legacy 'user' field for caching. + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). - reasoning_effort: Constrains effort on reasoning for supported reasoning models. Higher values use - more compute, potentially improving reasoning quality at the cost of latency and - tokens. + prompt_mode: Allows toggling between the reasoning mode and no system prompt. When set to + `reasoning` the system prompt for reasoning models will be used. - response_format: - An object specifying the format that the model must output. Use {'type': - 'json_schema', 'json_schema': {...}} for structured outputs or {'type': - 'json_object'} for the legacy JSON mode. Currently only OpenAI-prefixed models - honour this field; Anthropic and Google requests will return an - invalid_request_error if it is supplied. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, and `high`. Reducing + reasoning effort can result in faster responses and fewer tokens used on + reasoning in a response. - `gpt-5.1` defaults to `none`, which does not perform + reasoning. The supported reasoning values for `gpt-5.1` are `none`, `low`, + `medium`, and `high`. Tool calls are supported for all reasoning values in + gpt-5.1. - All models before `gpt-5.1` default to `medium` reasoning effort, and + do not support `none`. - The `gpt-5-pro` model defaults to (and only supports) + `high` reasoning effort. - safety_identifier: Stable identifier used to help detect users who might violate OpenAI usage - policies. Consider hashing end-user identifiers before sending. + response_format: An object specifying the format that the model must output. Setting to + `{ "type": "json_schema", "json_schema": {...} }` enables Structured Outputs + which ensures the model will match your supplied JSON schema. Learn more in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. - safety_settings: Google safety settings (harm categories and thresholds). + safe_prompt: Whether to inject a safety prompt before all conversations. - search_parameters: xAI-specific parameter for configuring web search data acquisition. If not set, - no data will be acquired by the model. + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user. We recommend hashing their username or email address, in + order to avoid sending us any identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). - seed: If specified, system will make a best effort to sample deterministically. - Determinism is not guaranteed for the same seed across different models or API - versions. + safety_settings: Safety/content filtering settings (Google-specific) - service_tier: Specifies the processing tier used for the request. 'auto' uses project - defaults, while 'default' forces standard pricing and performance. + search_parameters: Set the parameters to be used for searched data. If not set, no data will be + acquired by the model. - stop: Not supported with latest reasoning models 'o3' and 'o4-mini'. + seed: Random seed for deterministic output - Up to 4 sequences where the API will stop generating further tokens; the returned text will not contain the stop sequence. + service_tier: Service tier for request processing - store: Whether to store the output of this chat completion request for OpenAI model - distillation or eval products. Image inputs over 8MB are dropped if storage is - enabled. + stop: Sequences that stop generation - stream: If true, the model response data is streamed to the client as it is generated - using Server-Sent Events. + store: Whether or not to store the output of this chat completion request for use in + our [model distillation](https://platform.openai.com/docs/guides/distillation) + or [evals](https://platform.openai.com/docs/guides/evals) products. Supports + text and image inputs. Note: image inputs over 8MB will be dropped. - stream_options: Options for streaming responses. Only set when 'stream' is true (supports - 'include_usage' and 'include_obfuscation'). + stream: Enable streaming response - system: System prompt/instructions. Anthropic: pass-through. Google: converted to - systemInstruction. OpenAI: extracted from messages. + stream_options: Options for streaming response. Only set this when you set `stream: true`. - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 make - the output more random, while lower values like 0.2 make it more focused and - deterministic. We generally recommend altering this or 'top_p' but not both. + system_instruction: System instruction/prompt - thinking: Extended thinking configuration (Anthropic only). Enables thinking blocks - showing reasoning process. Requires min 1,024 token budget. + temperature: Sampling temperature (0-2 for most providers) - tool_choice: Controls which (if any) tool is called by the model. 'none' stops tool calling, - 'auto' lets the model decide, and 'required' forces at least one tool - invocation. Specific tool payloads force that tool. + thinking: Extended thinking configuration (Anthropic-specific) - tool_config: Google tool configuration (function calling mode, etc.). + tool_choice: Controls which (if any) tool is called by the model. `none` means the model will + not call any tool and instead generates a message. `auto` means the model can + pick between generating a message or calling one or more tools. `required` means + the model must call one or more tools. Specifying a particular tool via + `{"type": "function", "function": {"name": "my_function"}}` forces the model to + call that tool. `none` is the default when no tools are present. `auto` is the + default if tools are present. - tools: A list of tools the model may call. Supports OpenAI function tools and custom - tools; use 'mcp_servers' for Dedalus-managed server-side tools. + tool_config: Tool calling configuration (Google-specific) - top_k: Top-k sampling. Anthropic: pass-through. Google: injected into - generationConfig.topK. + tools: Available tools/functions for the model - top_logprobs: An integer between 0 and 20 specifying how many of the most likely tokens to - return at each position, with log probabilities. Requires 'logprobs' to be true. + top_k: Top-k sampling parameter - top_p: An alternative to sampling with temperature, called nucleus sampling, where the - model considers the results of the tokens with top_p probability mass. So 0.1 - means only the tokens comprising the top 10% probability mass are considered. We - generally recommend altering this or 'temperature' but not both. + top_logprobs: An integer between 0 and 20 specifying the number of most likely tokens to + return at each token position, each with an associated log probability. + `logprobs` must be set to `true` if this parameter is used. - user: Stable identifier for your end-users. Helps OpenAI detect and prevent abuse and - may boost cache hit rates. This field is being replaced by 'safety_identifier' - and 'prompt_cache_key'. + top_p: Nucleus sampling threshold - verbosity: Constrains the verbosity of the model's response. Lower values produce concise - answers, higher values allow more detail. + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). - web_search_options: Configuration for OpenAI's web search tool. Learn more at - https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat. + verbosity: Constrains the verbosity of the model's response. Lower values will result in + more concise responses, while higher values will result in more verbose + responses. Currently supported values are `low`, `medium`, and `high`. + + web_search_options: This tool searches the web for relevant results to use in a response. Learn more + about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). extra_headers: Send extra headers @@ -1839,55 +1878,57 @@ async def create( model: completion_create_params.Model, stream: Literal[True], agent_attributes: Optional[Dict[str, float]] | Omit = omit, - audio: Optional[Dict[str, object]] | Omit = omit, - auto_execute_tools: bool | Omit = omit, + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + automatic_tool_execution: bool | Omit = omit, + cached_content: Optional[str] | Omit = omit, + credentials: Optional[completion_create_params.Credentials] | Omit = omit, deferred: Optional[bool] | Omit = omit, - disable_automatic_function_calling: Optional[bool] | Omit = omit, frequency_penalty: Optional[float] | Omit = omit, - function_call: Union[str, Dict[str, object], None] | Omit = omit, - functions: Optional[Iterable[Dict[str, object]]] | Omit = omit, - generation_config: Optional[Dict[str, object]] | Omit = omit, + function_call: Optional[str] | Omit = omit, + functions: Optional[Iterable[ChatCompletionFunctionsParam]] | Omit = omit, + generation_config: Optional[JSONObjectInput] | Omit = omit, guardrails: Optional[Iterable[Dict[str, object]]] | Omit = omit, handoff_config: Optional[Dict[str, object]] | Omit = omit, - input: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - instructions: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, logit_bias: Optional[Dict[str, int]] | Omit = omit, logprobs: Optional[bool] | Omit = omit, max_completion_tokens: Optional[int] | Omit = omit, max_tokens: Optional[int] | Omit = omit, max_turns: Optional[int] | Omit = omit, - mcp_servers: Union[str, SequenceNotStr[str], None] | Omit = omit, - messages: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - metadata: Optional[Dict[str, str]] | Omit = omit, + mcp_servers: Optional[completion_create_params.MCPServers] | Omit = omit, + messages: Optional[Iterable[completion_create_params.Message]] | Omit = omit, + metadata: Optional[JSONObjectInput] | Omit = omit, modalities: Optional[SequenceNotStr[str]] | Omit = omit, model_attributes: Optional[Dict[str, Dict[str, float]]] | Omit = omit, n: Optional[int] | Omit = omit, parallel_tool_calls: Optional[bool] | Omit = omit, - prediction: Optional[Dict[str, object]] | Omit = omit, + prediction: Optional[PredictionContentParam] | Omit = omit, presence_penalty: Optional[float] | Omit = omit, prompt_cache_key: Optional[str] | Omit = omit, - reasoning_effort: Optional[Literal["low", "medium", "high"]] | Omit = omit, + prompt_cache_retention: Optional[str] | Omit = omit, + prompt_mode: Optional[Literal["reasoning"]] | Omit = omit, + reasoning_effort: Optional[str] | Omit = omit, response_format: Optional[completion_create_params.ResponseFormat] | Omit = omit, + safe_prompt: Optional[bool] | Omit = omit, safety_identifier: Optional[str] | Omit = omit, - safety_settings: Optional[Iterable[Dict[str, object]]] | Omit = omit, - search_parameters: Optional[Dict[str, object]] | Omit = omit, + safety_settings: Optional[Iterable[completion_create_params.SafetySetting]] | Omit = omit, + search_parameters: Optional[JSONObjectInput] | Omit = omit, seed: Optional[int] | Omit = omit, - service_tier: Optional[Literal["auto", "default"]] | Omit = omit, - stop: Optional[SequenceNotStr[str]] | Omit = omit, + service_tier: Optional[str] | Omit = omit, + stop: Union[SequenceNotStr[str], str, None] | Omit = omit, store: Optional[bool] | Omit = omit, - stream_options: Optional[Dict[str, object]] | Omit = omit, - system: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, + stream_options: Optional[JSONObjectInput] | Omit = omit, + system_instruction: Union[JSONObjectInput, str, None] | Omit = omit, temperature: Optional[float] | Omit = omit, thinking: Optional[completion_create_params.Thinking] | Omit = omit, - tool_choice: Union[str, Dict[str, object], None] | Omit = omit, - tool_config: Optional[Dict[str, object]] | Omit = omit, - tools: Optional[Iterable[Dict[str, object]]] | Omit = omit, + tool_choice: Optional[completion_create_params.ToolChoice] | Omit = omit, + tool_config: Optional[JSONObjectInput] | Omit = omit, + tools: Optional[Iterable[completion_create_params.Tool]] | Omit = omit, top_k: Optional[int] | Omit = omit, top_logprobs: Optional[int] | Omit = omit, top_p: Optional[float] | Omit = omit, user: Optional[str] | Omit = omit, - verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, - web_search_options: Optional[Dict[str, object]] | Omit = omit, + verbosity: Optional[str] | Omit = omit, + web_search_options: Optional[JSONObjectInput] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -1895,23 +1936,12 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, idempotency_key: str | None = None, - ) -> AsyncStream[StreamChunk]: + ) -> AsyncStream[ChatCompletionChunk]: """ Create a chat completion. - Unified chat-completions endpoint that works across many model providers. - Supports optional MCP integration, multi-model routing with agentic handoffs, - server- or client-executed tools, and both streaming and non-streaming delivery. - - Request body: - - - messages: ordered list of chat turns. - - model: identifier or a list of identifiers for routing. - - tools: optional tool declarations available to the model. - - mcp_servers: optional list of MCP server slugs to enable during the run. - - stream: boolean to request incremental output. - - config: optional generation parameters (e.g., temperature, max_tokens, - metadata). + Generates a model response for the given conversation and configuration. + Supports OpenAI-compatible parameters and provider-specific extensions. Headers: @@ -1920,8 +1950,8 @@ async def create( Behavior: - - If multiple models are supplied, the router may select or hand off across - them. + - If multiple models are supplied, the first one is used, and the agent may hand + off to another model. - Tools may be invoked on the server or signaled for the client to run. - Streaming responses emit incremental deltas; non-streaming returns a single object. @@ -1961,190 +1991,204 @@ async def create( [DONE] Args: - model: Model(s) to use for completion. Can be a single model ID, a DedalusModel object, - or a list for multi-model routing. Single model: 'openai/gpt-4', - 'anthropic/claude-3-5-sonnet-20241022', 'openai/gpt-4o-mini', or a DedalusModel - instance. Multi-model routing: ['openai/gpt-4o-mini', 'openai/gpt-4', - 'anthropic/claude-3-5-sonnet'] or list of DedalusModel objects - agent will - choose optimal model based on task complexity. + model: Model identifier. Accepts model ID strings, lists for routing, or DedalusModel + objects with per-model settings. + + stream: Enable streaming response - stream: If true, the model response data is streamed to the client as it is generated - using Server-Sent Events. + agent_attributes: Agent attributes. Values in [0.0, 1.0]. - agent_attributes: Attributes for the agent itself, influencing behavior and model selection. - Format: {'attribute': value}, where values are 0.0-1.0. Common attributes: - 'complexity', 'accuracy', 'efficiency', 'creativity', 'friendliness'. Higher - values indicate stronger preference for that characteristic. + audio: Parameters for audio output. Required when audio output is requested with + `modalities: ["audio"]`. + [Learn more](https://platform.openai.com/docs/guides/audio). - audio: Parameters for audio output. Required when requesting audio responses (for - example, modalities including 'audio'). + Fields: - auto_execute_tools: When False, skip server-side tool execution and return raw OpenAI-style - tool_calls in the response. + - voice (required): VoiceIdsShared + - format (required): Literal["wav", "aac", "mp3", "flac", "opus", "pcm16"] - deferred: xAI-specific parameter. If set to true, the request returns a request_id for - async completion retrieval via GET /v1/chat/deferred-completion/{request_id}. + automatic_tool_execution: Execute tools server-side. If false, returns raw tool calls for manual handling. - disable_automatic_function_calling: Google-only flag to disable the SDK's automatic function execution. When true, - the model returns function calls for the client to execute manually. + cached_content: Optional. The name of the content + [cached](https://ai.google.dev/gemini-api/docs/caching) to use as context to + serve the prediction. Format: `cachedContents/{cachedContent}` + + credentials: Credentials for MCP server authentication. Each credential is matched to servers + by connection name. + + deferred: If set to `true`, the request returns a `request_id`. You can then get the + deferred response by GET `/v1/chat/deferred-completion/{request_id}`. frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - function_call: Deprecated in favor of 'tool_choice'. Controls which function is called by the - model (none, auto, or specific name). + function_call: Wrapper for union variant: function call mode. - functions: Deprecated in favor of 'tools'. Legacy list of function definitions the model - may generate JSON inputs for. + functions: Deprecated in favor of `tools`. A list of functions the model may generate JSON + inputs for. - generation_config: Google generationConfig object. Merged with auto-generated config. Use for - Google-specific params (candidateCount, responseMimeType, etc.). + generation_config: Generation parameters wrapper (Google-specific) - guardrails: Guardrails to apply to the agent for input/output validation and safety checks. - Reserved for future use - guardrails configuration format not yet finalized. + guardrails: Content filtering and safety policy configuration. - handoff_config: Configuration for multi-model handoffs and agent orchestration. Reserved for - future use - handoff configuration format not yet finalized. + handoff_config: Configuration for multi-model handoffs. - input: Convenience alias for Responses-style `input`. Used when `messages` is omitted - to provide the user prompt directly. + logit_bias: Modify the likelihood of specified tokens appearing in the completion. Accepts a + JSON object that maps tokens (specified by their token ID in the tokenizer) to + an associated bias value from -100 to 100. Mathematically, the bias is added to + the logits generated by the model prior to sampling. The exact effect will vary + per model, but values between -1 and 1 should decrease or increase likelihood of + selection; values like -100 or 100 should result in a ban or exclusive selection + of the relevant token. - instructions: Convenience alias for Responses-style `instructions`. Takes precedence over - `system` and over system-role messages when provided. + logprobs: Whether to return log probabilities of the output tokens or not. If true, + returns the log probabilities of each output token returned in the `content` of + `message`. - logit_bias: Modify the likelihood of specified tokens appearing in the completion. Accepts a - JSON object mapping token IDs (as strings) to bias values from -100 to 100. The - bias is added to the logits before sampling; values between -1 and 1 nudge - selection probability, while values like -100 or 100 effectively ban or require - a token. + max_completion_tokens: Maximum tokens in completion (newer parameter name) - logprobs: Whether to return log probabilities of the output tokens. If true, returns the - log probabilities for each token in the response content. + max_tokens: Maximum tokens in completion - max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, - including visible output and reasoning tokens. + max_turns: Maximum conversation turns. - max_tokens: The maximum number of tokens that can be generated in the chat completion. This - value can be used to control costs for text generated via API. This value is now - deprecated in favor of 'max_completion_tokens' and is not compatible with - o-series models. + mcp_servers: MCP server identifiers. Accepts marketplace slugs, URLs, or MCPServerSpec + objects. MCP tools are executed server-side and billed separately. - max_turns: Maximum number of turns for agent execution before terminating (default: 10). - Each turn represents one model inference cycle. Higher values allow more complex - reasoning but increase cost and latency. + messages: Conversation history (OpenAI: messages, Google: contents, Responses: input) - mcp_servers: MCP (Model Context Protocol) server addresses to make available for server-side - tool execution. Entries can be URLs (e.g., 'https://mcp.example.com'), slugs - (e.g., 'dedalus-labs/brave-search'), or structured objects specifying - slug/version/url. MCP tools are executed server-side and billed separately. + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. Keys are strings with a maximum + length of 64 characters. Values are strings with a maximum length of 512 + characters. - messages: Conversation history. Accepts either a list of message objects or a string, - which is treated as a single user message. Optional if `input` or `instructions` - is provided. + modalities: Output types that you would like the model to generate. Most models are capable + of generating text, which is the default: `["text"]` The `gpt-4o-audio-preview` + model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that + this model generate both text and audio responses, you can use: + `["text", "audio"]` - metadata: Set of up to 16 key-value string pairs that can be attached to the request for - structured metadata. + model_attributes: Model attributes for routing. Maps model IDs to attribute dictionaries with + values in [0.0, 1.0]. - modalities: Output types you would like the model to generate. Most models default to - ['text']; some support ['text', 'audio']. + n: How many chat completion choices to generate for each input message. Note that + you will be charged based on the number of generated tokens across all of the + choices. Keep `n` as `1` to minimize costs. - model_attributes: Attributes for individual models used in routing decisions during multi-model - execution. Format: {'model_name': {'attribute': value}}, where values are - 0.0-1.0. Common attributes: 'intelligence', 'speed', 'cost', 'creativity', - 'accuracy'. Used by agent to select optimal model based on task requirements. + parallel_tool_calls: Whether to enable parallel tool calls (Anthropic uses inverted polarity) - n: How many chat completion choices to generate for each input message. Keep 'n' as - 1 to minimize costs. + prediction: Static predicted output content, such as the content of a text file that is + being regenerated. - parallel_tool_calls: Whether to enable parallel function calling during tool use. + Fields: - prediction: Configuration for predicted outputs. Improves response times when you already - know large portions of the response content. + - type (required): Literal["content"] + - content (required): str | + Annotated[list[ChatCompletionRequestMessageContentPartText], MinLen(1), + ArrayTitle("PredictionContentArray")] presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - prompt_cache_key: Used by OpenAI to cache responses for similar requests and optimize cache hit - rates. Replaces the legacy 'user' field for caching. + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + + prompt_mode: Allows toggling between the reasoning mode and no system prompt. When set to + `reasoning` the system prompt for reasoning models will be used. - reasoning_effort: Constrains effort on reasoning for supported reasoning models. Higher values use - more compute, potentially improving reasoning quality at the cost of latency and - tokens. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, and `high`. Reducing + reasoning effort can result in faster responses and fewer tokens used on + reasoning in a response. - `gpt-5.1` defaults to `none`, which does not perform + reasoning. The supported reasoning values for `gpt-5.1` are `none`, `low`, + `medium`, and `high`. Tool calls are supported for all reasoning values in + gpt-5.1. - All models before `gpt-5.1` default to `medium` reasoning effort, and + do not support `none`. - The `gpt-5-pro` model defaults to (and only supports) + `high` reasoning effort. - response_format: - An object specifying the format that the model must output. Use {'type': - 'json_schema', 'json_schema': {...}} for structured outputs or {'type': - 'json_object'} for the legacy JSON mode. Currently only OpenAI-prefixed models - honour this field; Anthropic and Google requests will return an - invalid_request_error if it is supplied. + response_format: An object specifying the format that the model must output. Setting to + `{ "type": "json_schema", "json_schema": {...} }` enables Structured Outputs + which ensures the model will match your supplied JSON schema. Learn more in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. - safety_identifier: Stable identifier used to help detect users who might violate OpenAI usage - policies. Consider hashing end-user identifiers before sending. + safe_prompt: Whether to inject a safety prompt before all conversations. - safety_settings: Google safety settings (harm categories and thresholds). + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user. We recommend hashing their username or email address, in + order to avoid sending us any identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). - search_parameters: xAI-specific parameter for configuring web search data acquisition. If not set, - no data will be acquired by the model. + safety_settings: Safety/content filtering settings (Google-specific) - seed: If specified, system will make a best effort to sample deterministically. - Determinism is not guaranteed for the same seed across different models or API - versions. + search_parameters: Set the parameters to be used for searched data. If not set, no data will be + acquired by the model. - service_tier: Specifies the processing tier used for the request. 'auto' uses project - defaults, while 'default' forces standard pricing and performance. + seed: Random seed for deterministic output - stop: Not supported with latest reasoning models 'o3' and 'o4-mini'. + service_tier: Service tier for request processing - Up to 4 sequences where the API will stop generating further tokens; the returned text will not contain the stop sequence. + stop: Sequences that stop generation - store: Whether to store the output of this chat completion request for OpenAI model - distillation or eval products. Image inputs over 8MB are dropped if storage is - enabled. + store: Whether or not to store the output of this chat completion request for use in + our [model distillation](https://platform.openai.com/docs/guides/distillation) + or [evals](https://platform.openai.com/docs/guides/evals) products. Supports + text and image inputs. Note: image inputs over 8MB will be dropped. - stream_options: Options for streaming responses. Only set when 'stream' is true (supports - 'include_usage' and 'include_obfuscation'). + stream_options: Options for streaming response. Only set this when you set `stream: true`. - system: System prompt/instructions. Anthropic: pass-through. Google: converted to - systemInstruction. OpenAI: extracted from messages. + system_instruction: System instruction/prompt - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 make - the output more random, while lower values like 0.2 make it more focused and - deterministic. We generally recommend altering this or 'top_p' but not both. + temperature: Sampling temperature (0-2 for most providers) - thinking: Extended thinking configuration (Anthropic only). Enables thinking blocks - showing reasoning process. Requires min 1,024 token budget. + thinking: Extended thinking configuration (Anthropic-specific) - tool_choice: Controls which (if any) tool is called by the model. 'none' stops tool calling, - 'auto' lets the model decide, and 'required' forces at least one tool - invocation. Specific tool payloads force that tool. + tool_choice: Controls which (if any) tool is called by the model. `none` means the model will + not call any tool and instead generates a message. `auto` means the model can + pick between generating a message or calling one or more tools. `required` means + the model must call one or more tools. Specifying a particular tool via + `{"type": "function", "function": {"name": "my_function"}}` forces the model to + call that tool. `none` is the default when no tools are present. `auto` is the + default if tools are present. - tool_config: Google tool configuration (function calling mode, etc.). + tool_config: Tool calling configuration (Google-specific) - tools: A list of tools the model may call. Supports OpenAI function tools and custom - tools; use 'mcp_servers' for Dedalus-managed server-side tools. + tools: Available tools/functions for the model - top_k: Top-k sampling. Anthropic: pass-through. Google: injected into - generationConfig.topK. + top_k: Top-k sampling parameter - top_logprobs: An integer between 0 and 20 specifying how many of the most likely tokens to - return at each position, with log probabilities. Requires 'logprobs' to be true. + top_logprobs: An integer between 0 and 20 specifying the number of most likely tokens to + return at each token position, each with an associated log probability. + `logprobs` must be set to `true` if this parameter is used. - top_p: An alternative to sampling with temperature, called nucleus sampling, where the - model considers the results of the tokens with top_p probability mass. So 0.1 - means only the tokens comprising the top 10% probability mass are considered. We - generally recommend altering this or 'temperature' but not both. + top_p: Nucleus sampling threshold - user: Stable identifier for your end-users. Helps OpenAI detect and prevent abuse and - may boost cache hit rates. This field is being replaced by 'safety_identifier' - and 'prompt_cache_key'. + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). - verbosity: Constrains the verbosity of the model's response. Lower values produce concise - answers, higher values allow more detail. + verbosity: Constrains the verbosity of the model's response. Lower values will result in + more concise responses, while higher values will result in more verbose + responses. Currently supported values are `low`, `medium`, and `high`. - web_search_options: Configuration for OpenAI's web search tool. Learn more at - https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat. + web_search_options: This tool searches the web for relevant results to use in a response. Learn more + about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). extra_headers: Send extra headers @@ -2165,55 +2209,57 @@ async def create( model: completion_create_params.Model, stream: bool, agent_attributes: Optional[Dict[str, float]] | Omit = omit, - audio: Optional[Dict[str, object]] | Omit = omit, - auto_execute_tools: bool | Omit = omit, + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + automatic_tool_execution: bool | Omit = omit, + cached_content: Optional[str] | Omit = omit, + credentials: Optional[completion_create_params.Credentials] | Omit = omit, deferred: Optional[bool] | Omit = omit, - disable_automatic_function_calling: Optional[bool] | Omit = omit, frequency_penalty: Optional[float] | Omit = omit, - function_call: Union[str, Dict[str, object], None] | Omit = omit, - functions: Optional[Iterable[Dict[str, object]]] | Omit = omit, - generation_config: Optional[Dict[str, object]] | Omit = omit, + function_call: Optional[str] | Omit = omit, + functions: Optional[Iterable[ChatCompletionFunctionsParam]] | Omit = omit, + generation_config: Optional[JSONObjectInput] | Omit = omit, guardrails: Optional[Iterable[Dict[str, object]]] | Omit = omit, handoff_config: Optional[Dict[str, object]] | Omit = omit, - input: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - instructions: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, logit_bias: Optional[Dict[str, int]] | Omit = omit, logprobs: Optional[bool] | Omit = omit, max_completion_tokens: Optional[int] | Omit = omit, max_tokens: Optional[int] | Omit = omit, max_turns: Optional[int] | Omit = omit, - mcp_servers: Union[str, SequenceNotStr[str], None] | Omit = omit, - messages: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - metadata: Optional[Dict[str, str]] | Omit = omit, + mcp_servers: Optional[completion_create_params.MCPServers] | Omit = omit, + messages: Optional[Iterable[completion_create_params.Message]] | Omit = omit, + metadata: Optional[JSONObjectInput] | Omit = omit, modalities: Optional[SequenceNotStr[str]] | Omit = omit, model_attributes: Optional[Dict[str, Dict[str, float]]] | Omit = omit, n: Optional[int] | Omit = omit, parallel_tool_calls: Optional[bool] | Omit = omit, - prediction: Optional[Dict[str, object]] | Omit = omit, + prediction: Optional[PredictionContentParam] | Omit = omit, presence_penalty: Optional[float] | Omit = omit, prompt_cache_key: Optional[str] | Omit = omit, - reasoning_effort: Optional[Literal["low", "medium", "high"]] | Omit = omit, + prompt_cache_retention: Optional[str] | Omit = omit, + prompt_mode: Optional[Literal["reasoning"]] | Omit = omit, + reasoning_effort: Optional[str] | Omit = omit, response_format: Optional[completion_create_params.ResponseFormat] | Omit = omit, + safe_prompt: Optional[bool] | Omit = omit, safety_identifier: Optional[str] | Omit = omit, - safety_settings: Optional[Iterable[Dict[str, object]]] | Omit = omit, - search_parameters: Optional[Dict[str, object]] | Omit = omit, + safety_settings: Optional[Iterable[completion_create_params.SafetySetting]] | Omit = omit, + search_parameters: Optional[JSONObjectInput] | Omit = omit, seed: Optional[int] | Omit = omit, - service_tier: Optional[Literal["auto", "default"]] | Omit = omit, - stop: Optional[SequenceNotStr[str]] | Omit = omit, + service_tier: Optional[str] | Omit = omit, + stop: Union[SequenceNotStr[str], str, None] | Omit = omit, store: Optional[bool] | Omit = omit, - stream_options: Optional[Dict[str, object]] | Omit = omit, - system: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, + stream_options: Optional[JSONObjectInput] | Omit = omit, + system_instruction: Union[JSONObjectInput, str, None] | Omit = omit, temperature: Optional[float] | Omit = omit, thinking: Optional[completion_create_params.Thinking] | Omit = omit, - tool_choice: Union[str, Dict[str, object], None] | Omit = omit, - tool_config: Optional[Dict[str, object]] | Omit = omit, - tools: Optional[Iterable[Dict[str, object]]] | Omit = omit, + tool_choice: Optional[completion_create_params.ToolChoice] | Omit = omit, + tool_config: Optional[JSONObjectInput] | Omit = omit, + tools: Optional[Iterable[completion_create_params.Tool]] | Omit = omit, top_k: Optional[int] | Omit = omit, top_logprobs: Optional[int] | Omit = omit, top_p: Optional[float] | Omit = omit, user: Optional[str] | Omit = omit, - verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, - web_search_options: Optional[Dict[str, object]] | Omit = omit, + verbosity: Optional[str] | Omit = omit, + web_search_options: Optional[JSONObjectInput] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -2221,23 +2267,12 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, idempotency_key: str | None = None, - ) -> Completion | AsyncStream[StreamChunk]: + ) -> ChatCompletion | AsyncStream[ChatCompletionChunk]: """ Create a chat completion. - Unified chat-completions endpoint that works across many model providers. - Supports optional MCP integration, multi-model routing with agentic handoffs, - server- or client-executed tools, and both streaming and non-streaming delivery. - - Request body: - - - messages: ordered list of chat turns. - - model: identifier or a list of identifiers for routing. - - tools: optional tool declarations available to the model. - - mcp_servers: optional list of MCP server slugs to enable during the run. - - stream: boolean to request incremental output. - - config: optional generation parameters (e.g., temperature, max_tokens, - metadata). + Generates a model response for the given conversation and configuration. + Supports OpenAI-compatible parameters and provider-specific extensions. Headers: @@ -2246,8 +2281,8 @@ async def create( Behavior: - - If multiple models are supplied, the router may select or hand off across - them. + - If multiple models are supplied, the first one is used, and the agent may hand + off to another model. - Tools may be invoked on the server or signaled for the client to run. - Streaming responses emit incremental deltas; non-streaming returns a single object. @@ -2287,190 +2322,204 @@ async def create( [DONE] Args: - model: Model(s) to use for completion. Can be a single model ID, a DedalusModel object, - or a list for multi-model routing. Single model: 'openai/gpt-4', - 'anthropic/claude-3-5-sonnet-20241022', 'openai/gpt-4o-mini', or a DedalusModel - instance. Multi-model routing: ['openai/gpt-4o-mini', 'openai/gpt-4', - 'anthropic/claude-3-5-sonnet'] or list of DedalusModel objects - agent will - choose optimal model based on task complexity. + model: Model identifier. Accepts model ID strings, lists for routing, or DedalusModel + objects with per-model settings. + + stream: Enable streaming response + + agent_attributes: Agent attributes. Values in [0.0, 1.0]. - stream: If true, the model response data is streamed to the client as it is generated - using Server-Sent Events. + audio: Parameters for audio output. Required when audio output is requested with + `modalities: ["audio"]`. + [Learn more](https://platform.openai.com/docs/guides/audio). - agent_attributes: Attributes for the agent itself, influencing behavior and model selection. - Format: {'attribute': value}, where values are 0.0-1.0. Common attributes: - 'complexity', 'accuracy', 'efficiency', 'creativity', 'friendliness'. Higher - values indicate stronger preference for that characteristic. + Fields: - audio: Parameters for audio output. Required when requesting audio responses (for - example, modalities including 'audio'). + - voice (required): VoiceIdsShared + - format (required): Literal["wav", "aac", "mp3", "flac", "opus", "pcm16"] - auto_execute_tools: When False, skip server-side tool execution and return raw OpenAI-style - tool_calls in the response. + automatic_tool_execution: Execute tools server-side. If false, returns raw tool calls for manual handling. - deferred: xAI-specific parameter. If set to true, the request returns a request_id for - async completion retrieval via GET /v1/chat/deferred-completion/{request_id}. + cached_content: Optional. The name of the content + [cached](https://ai.google.dev/gemini-api/docs/caching) to use as context to + serve the prediction. Format: `cachedContents/{cachedContent}` - disable_automatic_function_calling: Google-only flag to disable the SDK's automatic function execution. When true, - the model returns function calls for the client to execute manually. + credentials: Credentials for MCP server authentication. Each credential is matched to servers + by connection name. + + deferred: If set to `true`, the request returns a `request_id`. You can then get the + deferred response by GET `/v1/chat/deferred-completion/{request_id}`. frequency_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - function_call: Deprecated in favor of 'tool_choice'. Controls which function is called by the - model (none, auto, or specific name). + function_call: Wrapper for union variant: function call mode. - functions: Deprecated in favor of 'tools'. Legacy list of function definitions the model - may generate JSON inputs for. + functions: Deprecated in favor of `tools`. A list of functions the model may generate JSON + inputs for. - generation_config: Google generationConfig object. Merged with auto-generated config. Use for - Google-specific params (candidateCount, responseMimeType, etc.). + generation_config: Generation parameters wrapper (Google-specific) - guardrails: Guardrails to apply to the agent for input/output validation and safety checks. - Reserved for future use - guardrails configuration format not yet finalized. + guardrails: Content filtering and safety policy configuration. - handoff_config: Configuration for multi-model handoffs and agent orchestration. Reserved for - future use - handoff configuration format not yet finalized. + handoff_config: Configuration for multi-model handoffs. - input: Convenience alias for Responses-style `input`. Used when `messages` is omitted - to provide the user prompt directly. + logit_bias: Modify the likelihood of specified tokens appearing in the completion. Accepts a + JSON object that maps tokens (specified by their token ID in the tokenizer) to + an associated bias value from -100 to 100. Mathematically, the bias is added to + the logits generated by the model prior to sampling. The exact effect will vary + per model, but values between -1 and 1 should decrease or increase likelihood of + selection; values like -100 or 100 should result in a ban or exclusive selection + of the relevant token. - instructions: Convenience alias for Responses-style `instructions`. Takes precedence over - `system` and over system-role messages when provided. + logprobs: Whether to return log probabilities of the output tokens or not. If true, + returns the log probabilities of each output token returned in the `content` of + `message`. - logit_bias: Modify the likelihood of specified tokens appearing in the completion. Accepts a - JSON object mapping token IDs (as strings) to bias values from -100 to 100. The - bias is added to the logits before sampling; values between -1 and 1 nudge - selection probability, while values like -100 or 100 effectively ban or require - a token. + max_completion_tokens: Maximum tokens in completion (newer parameter name) - logprobs: Whether to return log probabilities of the output tokens. If true, returns the - log probabilities for each token in the response content. + max_tokens: Maximum tokens in completion - max_completion_tokens: An upper bound for the number of tokens that can be generated for a completion, - including visible output and reasoning tokens. + max_turns: Maximum conversation turns. - max_tokens: The maximum number of tokens that can be generated in the chat completion. This - value can be used to control costs for text generated via API. This value is now - deprecated in favor of 'max_completion_tokens' and is not compatible with - o-series models. + mcp_servers: MCP server identifiers. Accepts marketplace slugs, URLs, or MCPServerSpec + objects. MCP tools are executed server-side and billed separately. - max_turns: Maximum number of turns for agent execution before terminating (default: 10). - Each turn represents one model inference cycle. Higher values allow more complex - reasoning but increase cost and latency. + messages: Conversation history (OpenAI: messages, Google: contents, Responses: input) - mcp_servers: MCP (Model Context Protocol) server addresses to make available for server-side - tool execution. Entries can be URLs (e.g., 'https://mcp.example.com'), slugs - (e.g., 'dedalus-labs/brave-search'), or structured objects specifying - slug/version/url. MCP tools are executed server-side and billed separately. + metadata: Set of 16 key-value pairs that can be attached to an object. This can be useful + for storing additional information about the object in a structured format, and + querying for objects via API or the dashboard. Keys are strings with a maximum + length of 64 characters. Values are strings with a maximum length of 512 + characters. - messages: Conversation history. Accepts either a list of message objects or a string, - which is treated as a single user message. Optional if `input` or `instructions` - is provided. + modalities: Output types that you would like the model to generate. Most models are capable + of generating text, which is the default: `["text"]` The `gpt-4o-audio-preview` + model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that + this model generate both text and audio responses, you can use: + `["text", "audio"]` - metadata: Set of up to 16 key-value string pairs that can be attached to the request for - structured metadata. + model_attributes: Model attributes for routing. Maps model IDs to attribute dictionaries with + values in [0.0, 1.0]. - modalities: Output types you would like the model to generate. Most models default to - ['text']; some support ['text', 'audio']. + n: How many chat completion choices to generate for each input message. Note that + you will be charged based on the number of generated tokens across all of the + choices. Keep `n` as `1` to minimize costs. - model_attributes: Attributes for individual models used in routing decisions during multi-model - execution. Format: {'model_name': {'attribute': value}}, where values are - 0.0-1.0. Common attributes: 'intelligence', 'speed', 'cost', 'creativity', - 'accuracy'. Used by agent to select optimal model based on task requirements. + parallel_tool_calls: Whether to enable parallel tool calls (Anthropic uses inverted polarity) - n: How many chat completion choices to generate for each input message. Keep 'n' as - 1 to minimize costs. + prediction: Static predicted output content, such as the content of a text file that is + being regenerated. - parallel_tool_calls: Whether to enable parallel function calling during tool use. + Fields: - prediction: Configuration for predicted outputs. Improves response times when you already - know large portions of the response content. + - type (required): Literal["content"] + - content (required): str | + Annotated[list[ChatCompletionRequestMessageContentPartText], MinLen(1), + ArrayTitle("PredictionContentArray")] presence_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - prompt_cache_key: Used by OpenAI to cache responses for similar requests and optimize cache hit - rates. Replaces the legacy 'user' field for caching. + prompt_cache_key: Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). + + prompt_cache_retention: The retention policy for the prompt cache. Set to `24h` to enable extended + prompt caching, which keeps cached prefixes active for longer, up to a maximum + of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + + prompt_mode: Allows toggling between the reasoning mode and no system prompt. When set to + `reasoning` the system prompt for reasoning models will be used. - reasoning_effort: Constrains effort on reasoning for supported reasoning models. Higher values use - more compute, potentially improving reasoning quality at the cost of latency and - tokens. + reasoning_effort: Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, and `high`. Reducing + reasoning effort can result in faster responses and fewer tokens used on + reasoning in a response. - `gpt-5.1` defaults to `none`, which does not perform + reasoning. The supported reasoning values for `gpt-5.1` are `none`, `low`, + `medium`, and `high`. Tool calls are supported for all reasoning values in + gpt-5.1. - All models before `gpt-5.1` default to `medium` reasoning effort, and + do not support `none`. - The `gpt-5-pro` model defaults to (and only supports) + `high` reasoning effort. - response_format: - An object specifying the format that the model must output. Use {'type': - 'json_schema', 'json_schema': {...}} for structured outputs or {'type': - 'json_object'} for the legacy JSON mode. Currently only OpenAI-prefixed models - honour this field; Anthropic and Google requests will return an - invalid_request_error if it is supplied. + response_format: An object specifying the format that the model must output. Setting to + `{ "type": "json_schema", "json_schema": {...} }` enables Structured Outputs + which ensures the model will match your supplied JSON schema. Learn more in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. - safety_identifier: Stable identifier used to help detect users who might violate OpenAI usage - policies. Consider hashing end-user identifiers before sending. + safe_prompt: Whether to inject a safety prompt before all conversations. - safety_settings: Google safety settings (harm categories and thresholds). + safety_identifier: A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user. We recommend hashing their username or email address, in + order to avoid sending us any identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). - search_parameters: xAI-specific parameter for configuring web search data acquisition. If not set, - no data will be acquired by the model. + safety_settings: Safety/content filtering settings (Google-specific) - seed: If specified, system will make a best effort to sample deterministically. - Determinism is not guaranteed for the same seed across different models or API - versions. + search_parameters: Set the parameters to be used for searched data. If not set, no data will be + acquired by the model. - service_tier: Specifies the processing tier used for the request. 'auto' uses project - defaults, while 'default' forces standard pricing and performance. + seed: Random seed for deterministic output - stop: Not supported with latest reasoning models 'o3' and 'o4-mini'. + service_tier: Service tier for request processing - Up to 4 sequences where the API will stop generating further tokens; the returned text will not contain the stop sequence. + stop: Sequences that stop generation - store: Whether to store the output of this chat completion request for OpenAI model - distillation or eval products. Image inputs over 8MB are dropped if storage is - enabled. + store: Whether or not to store the output of this chat completion request for use in + our [model distillation](https://platform.openai.com/docs/guides/distillation) + or [evals](https://platform.openai.com/docs/guides/evals) products. Supports + text and image inputs. Note: image inputs over 8MB will be dropped. - stream_options: Options for streaming responses. Only set when 'stream' is true (supports - 'include_usage' and 'include_obfuscation'). + stream_options: Options for streaming response. Only set this when you set `stream: true`. - system: System prompt/instructions. Anthropic: pass-through. Google: converted to - systemInstruction. OpenAI: extracted from messages. + system_instruction: System instruction/prompt - temperature: What sampling temperature to use, between 0 and 2. Higher values like 0.8 make - the output more random, while lower values like 0.2 make it more focused and - deterministic. We generally recommend altering this or 'top_p' but not both. + temperature: Sampling temperature (0-2 for most providers) - thinking: Extended thinking configuration (Anthropic only). Enables thinking blocks - showing reasoning process. Requires min 1,024 token budget. + thinking: Extended thinking configuration (Anthropic-specific) - tool_choice: Controls which (if any) tool is called by the model. 'none' stops tool calling, - 'auto' lets the model decide, and 'required' forces at least one tool - invocation. Specific tool payloads force that tool. + tool_choice: Controls which (if any) tool is called by the model. `none` means the model will + not call any tool and instead generates a message. `auto` means the model can + pick between generating a message or calling one or more tools. `required` means + the model must call one or more tools. Specifying a particular tool via + `{"type": "function", "function": {"name": "my_function"}}` forces the model to + call that tool. `none` is the default when no tools are present. `auto` is the + default if tools are present. - tool_config: Google tool configuration (function calling mode, etc.). + tool_config: Tool calling configuration (Google-specific) - tools: A list of tools the model may call. Supports OpenAI function tools and custom - tools; use 'mcp_servers' for Dedalus-managed server-side tools. + tools: Available tools/functions for the model - top_k: Top-k sampling. Anthropic: pass-through. Google: injected into - generationConfig.topK. + top_k: Top-k sampling parameter - top_logprobs: An integer between 0 and 20 specifying how many of the most likely tokens to - return at each position, with log probabilities. Requires 'logprobs' to be true. + top_logprobs: An integer between 0 and 20 specifying the number of most likely tokens to + return at each token position, each with an associated log probability. + `logprobs` must be set to `true` if this parameter is used. - top_p: An alternative to sampling with temperature, called nucleus sampling, where the - model considers the results of the tokens with top_p probability mass. So 0.1 - means only the tokens comprising the top 10% probability mass are considered. We - generally recommend altering this or 'temperature' but not both. + top_p: Nucleus sampling threshold - user: Stable identifier for your end-users. Helps OpenAI detect and prevent abuse and - may boost cache hit rates. This field is being replaced by 'safety_identifier' - and 'prompt_cache_key'. + user: This field is being replaced by `safety_identifier` and `prompt_cache_key`. Use + `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). - verbosity: Constrains the verbosity of the model's response. Lower values produce concise - answers, higher values allow more detail. + verbosity: Constrains the verbosity of the model's response. Lower values will result in + more concise responses, while higher values will result in more verbose + responses. Currently supported values are `low`, `medium`, and `high`. - web_search_options: Configuration for OpenAI's web search tool. Learn more at - https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat. + web_search_options: This tool searches the web for relevant results to use in a response. Learn more + about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). extra_headers: Send extra headers @@ -2490,56 +2539,58 @@ async def create( *, model: completion_create_params.Model, agent_attributes: Optional[Dict[str, float]] | Omit = omit, - audio: Optional[Dict[str, object]] | Omit = omit, - auto_execute_tools: bool | Omit = omit, + audio: Optional[ChatCompletionAudioParam] | Omit = omit, + automatic_tool_execution: bool | Omit = omit, + cached_content: Optional[str] | Omit = omit, + credentials: Optional[completion_create_params.Credentials] | Omit = omit, deferred: Optional[bool] | Omit = omit, - disable_automatic_function_calling: Optional[bool] | Omit = omit, frequency_penalty: Optional[float] | Omit = omit, - function_call: Union[str, Dict[str, object], None] | Omit = omit, - functions: Optional[Iterable[Dict[str, object]]] | Omit = omit, - generation_config: Optional[Dict[str, object]] | Omit = omit, + function_call: Optional[str] | Omit = omit, + functions: Optional[Iterable[ChatCompletionFunctionsParam]] | Omit = omit, + generation_config: Optional[JSONObjectInput] | Omit = omit, guardrails: Optional[Iterable[Dict[str, object]]] | Omit = omit, handoff_config: Optional[Dict[str, object]] | Omit = omit, - input: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - instructions: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, logit_bias: Optional[Dict[str, int]] | Omit = omit, logprobs: Optional[bool] | Omit = omit, max_completion_tokens: Optional[int] | Omit = omit, max_tokens: Optional[int] | Omit = omit, max_turns: Optional[int] | Omit = omit, - mcp_servers: Union[str, SequenceNotStr[str], None] | Omit = omit, - messages: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - metadata: Optional[Dict[str, str]] | Omit = omit, + mcp_servers: Optional[completion_create_params.MCPServers] | Omit = omit, + messages: Optional[Iterable[completion_create_params.Message]] | Omit = omit, + metadata: Optional[JSONObjectInput] | Omit = omit, modalities: Optional[SequenceNotStr[str]] | Omit = omit, model_attributes: Optional[Dict[str, Dict[str, float]]] | Omit = omit, n: Optional[int] | Omit = omit, parallel_tool_calls: Optional[bool] | Omit = omit, - prediction: Optional[Dict[str, object]] | Omit = omit, + prediction: Optional[PredictionContentParam] | Omit = omit, presence_penalty: Optional[float] | Omit = omit, prompt_cache_key: Optional[str] | Omit = omit, - reasoning_effort: Optional[Literal["low", "medium", "high"]] | Omit = omit, + prompt_cache_retention: Optional[str] | Omit = omit, + prompt_mode: Optional[Literal["reasoning"]] | Omit = omit, + reasoning_effort: Optional[str] | Omit = omit, response_format: Optional[completion_create_params.ResponseFormat] | Omit = omit, + safe_prompt: Optional[bool] | Omit = omit, safety_identifier: Optional[str] | Omit = omit, - safety_settings: Optional[Iterable[Dict[str, object]]] | Omit = omit, - search_parameters: Optional[Dict[str, object]] | Omit = omit, + safety_settings: Optional[Iterable[completion_create_params.SafetySetting]] | Omit = omit, + search_parameters: Optional[JSONObjectInput] | Omit = omit, seed: Optional[int] | Omit = omit, - service_tier: Optional[Literal["auto", "default"]] | Omit = omit, - stop: Optional[SequenceNotStr[str]] | Omit = omit, + service_tier: Optional[str] | Omit = omit, + stop: Union[SequenceNotStr[str], str, None] | Omit = omit, store: Optional[bool] | Omit = omit, - stream: Literal[False] | Literal[True] | Omit = omit, - stream_options: Optional[Dict[str, object]] | Omit = omit, - system: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, + stream: Optional[Literal[False]] | Literal[True] | Omit = omit, + stream_options: Optional[JSONObjectInput] | Omit = omit, + system_instruction: Union[JSONObjectInput, str, None] | Omit = omit, temperature: Optional[float] | Omit = omit, thinking: Optional[completion_create_params.Thinking] | Omit = omit, - tool_choice: Union[str, Dict[str, object], None] | Omit = omit, - tool_config: Optional[Dict[str, object]] | Omit = omit, - tools: Optional[Iterable[Dict[str, object]]] | Omit = omit, + tool_choice: Optional[completion_create_params.ToolChoice] | Omit = omit, + tool_config: Optional[JSONObjectInput] | Omit = omit, + tools: Optional[Iterable[completion_create_params.Tool]] | Omit = omit, top_k: Optional[int] | Omit = omit, top_logprobs: Optional[int] | Omit = omit, top_p: Optional[float] | Omit = omit, user: Optional[str] | Omit = omit, - verbosity: Optional[Literal["low", "medium", "high"]] | Omit = omit, - web_search_options: Optional[Dict[str, object]] | Omit = omit, + verbosity: Optional[str] | Omit = omit, + web_search_options: Optional[JSONObjectInput] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -2547,13 +2598,17 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, idempotency_key: str | None = None, - ) -> Completion | AsyncStream[StreamChunk]: + ) -> ChatCompletion | AsyncStream[ChatCompletionChunk]: import inspect import pydantic from ..._utils import is_given # Validate response_format is not a Pydantic model - if is_given(response_format) and inspect.isclass(response_format) and issubclass(response_format, pydantic.BaseModel): + if ( + is_given(response_format) + and inspect.isclass(response_format) + and issubclass(response_format, pydantic.BaseModel) + ): raise TypeError( "You tried to pass a `BaseModel` class to `chat.completions.create()`; " "You must use `chat.completions.parse()` instead" @@ -2566,17 +2621,16 @@ async def create( "model": model, "agent_attributes": agent_attributes, "audio": audio, - "auto_execute_tools": auto_execute_tools, + "automatic_tool_execution": automatic_tool_execution, + "cached_content": cached_content, + "credentials": credentials, "deferred": deferred, - "disable_automatic_function_calling": disable_automatic_function_calling, "frequency_penalty": frequency_penalty, "function_call": function_call, "functions": functions, "generation_config": generation_config, "guardrails": guardrails, "handoff_config": handoff_config, - "input": input, - "instructions": instructions, "logit_bias": logit_bias, "logprobs": logprobs, "max_completion_tokens": max_completion_tokens, @@ -2592,8 +2646,11 @@ async def create( "prediction": prediction, "presence_penalty": presence_penalty, "prompt_cache_key": prompt_cache_key, + "prompt_cache_retention": prompt_cache_retention, + "prompt_mode": prompt_mode, "reasoning_effort": reasoning_effort, "response_format": response_format, + "safe_prompt": safe_prompt, "safety_identifier": safety_identifier, "safety_settings": safety_settings, "search_parameters": search_parameters, @@ -2603,7 +2660,7 @@ async def create( "store": store, "stream": stream, "stream_options": stream_options, - "system": system, + "system_instruction": system_instruction, "temperature": temperature, "thinking": thinking, "tool_choice": tool_choice, @@ -2627,9 +2684,9 @@ async def create( timeout=timeout, idempotency_key=idempotency_key, ), - cast_to=Completion, + cast_to=ChatCompletion, stream=stream or False, - stream_cls=AsyncStream[StreamChunk], + stream_cls=AsyncStream[ChatCompletionChunk], ) async def parse( @@ -2642,9 +2699,8 @@ async def parse( instructions: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, agent_attributes: Optional[Dict[str, float]] | Omit = omit, audio: Optional[Dict[str, object]] | Omit = omit, - auto_execute_tools: bool | Omit = omit, + automatic_tool_execution: bool | Omit = omit, deferred: Optional[bool] | Omit = omit, - disable_automatic_function_calling: Optional[bool] | Omit = omit, frequency_penalty: Optional[float] | Omit = omit, function_call: Union[str, Dict[str, object], None] | Omit = omit, functions: Optional[Iterable[Dict[str, object]]] | Omit = omit, @@ -2718,9 +2774,8 @@ def parser(raw_completion: Completion): "model": model, "agent_attributes": agent_attributes, "audio": audio, - "auto_execute_tools": auto_execute_tools, + "automatic_tool_execution": automatic_tool_execution, "deferred": deferred, - "disable_automatic_function_calling": disable_automatic_function_calling, "frequency_penalty": frequency_penalty, "function_call": function_call, "functions": functions, @@ -2786,19 +2841,19 @@ def stream( model: completion_create_params.Model, messages: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, response_format: type[ResponseFormatT] | Omit = omit, + input: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, + instructions: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, + system: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, agent_attributes: Optional[Dict[str, float]] | Omit = omit, audio: Optional[Dict[str, object]] | Omit = omit, - auto_execute_tools: bool | Omit = omit, + automatic_tool_execution: bool | Omit = omit, deferred: Optional[bool] | Omit = omit, - disable_automatic_function_calling: Optional[bool] | Omit = omit, frequency_penalty: Optional[float] | Omit = omit, function_call: Union[str, Dict[str, object], None] | Omit = omit, functions: Optional[Iterable[Dict[str, object]]] | Omit = omit, generation_config: Optional[Dict[str, object]] | Omit = omit, guardrails: Optional[Iterable[Dict[str, object]]] | Omit = omit, handoff_config: Optional[Dict[str, object]] | Omit = omit, - input: Union[Iterable[Dict[str, object]], str, None] | Omit = omit, - instructions: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, logit_bias: Optional[Dict[str, int]] | Omit = omit, logprobs: Optional[bool] | Omit = omit, max_completion_tokens: Optional[int] | Omit = omit, @@ -2822,7 +2877,6 @@ def stream( stop: Optional[SequenceNotStr[str]] | Omit = omit, store: Optional[bool] | Omit = omit, stream_options: Optional[Dict[str, object]] | Omit = omit, - system: Union[str, Iterable[Dict[str, object]], None] | Omit = omit, temperature: Optional[float] | Omit = omit, thinking: Optional[completion_create_params.Thinking] | Omit = omit, tool_choice: Union[str, Dict[str, object], None] | Omit = omit, @@ -2848,64 +2902,74 @@ def stream( **(extra_headers or {}), } - api_request = self.create( - model=model, - messages=messages, - agent_attributes=agent_attributes, - audio=audio, - auto_execute_tools=auto_execute_tools, - deferred=deferred, - disable_automatic_function_calling=disable_automatic_function_calling, - frequency_penalty=frequency_penalty, - function_call=function_call, - functions=functions, - generation_config=generation_config, - guardrails=guardrails, - handoff_config=handoff_config, - input=input, - instructions=instructions, - logit_bias=logit_bias, - logprobs=logprobs, - max_completion_tokens=max_completion_tokens, - max_tokens=max_tokens, - max_turns=max_turns, - mcp_servers=mcp_servers, - metadata=metadata, - modalities=modalities, - model_attributes=model_attributes, - n=n, - parallel_tool_calls=parallel_tool_calls, - prediction=prediction, - presence_penalty=presence_penalty, - prompt_cache_key=prompt_cache_key, - reasoning_effort=reasoning_effort, - safety_identifier=safety_identifier, - safety_settings=safety_settings, - search_parameters=search_parameters, - seed=seed, - service_tier=service_tier, - stop=stop, - store=store, + api_request = self._post( + "/v1/chat/completions", + body=maybe_transform( + { + "messages": messages, + "model": model, + "agent_attributes": agent_attributes, + "audio": audio, + "automatic_tool_execution": automatic_tool_execution, + "deferred": deferred, + "frequency_penalty": frequency_penalty, + "function_call": function_call, + "functions": functions, + "generation_config": generation_config, + "guardrails": guardrails, + "handoff_config": handoff_config, + "input": input, + "instructions": instructions, + "logit_bias": logit_bias, + "logprobs": logprobs, + "max_completion_tokens": max_completion_tokens, + "max_tokens": max_tokens, + "max_turns": max_turns, + "mcp_servers": mcp_servers, + "metadata": metadata, + "modalities": modalities, + "model_attributes": model_attributes, + "n": n, + "parallel_tool_calls": parallel_tool_calls, + "prediction": prediction, + "presence_penalty": presence_penalty, + "prompt_cache_key": prompt_cache_key, + "reasoning_effort": reasoning_effort, + "response_format": _type_to_response_format(response_format), + "safety_identifier": safety_identifier, + "safety_settings": safety_settings, + "search_parameters": search_parameters, + "seed": seed, + "service_tier": service_tier, + "stop": stop, + "store": store, + "stream": True, + "stream_options": stream_options, + "system": system, + "temperature": temperature, + "thinking": thinking, + "tool_choice": tool_choice, + "tool_config": tool_config, + "tools": tools, + "top_k": top_k, + "top_logprobs": top_logprobs, + "top_p": top_p, + "user": user, + "verbosity": verbosity, + "web_search_options": web_search_options, + }, + completion_create_params.CompletionCreateParamsStreaming, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ChatCompletion, stream=True, - stream_options=stream_options, - system=system, - temperature=temperature, - thinking=thinking, - tool_choice=tool_choice, - tool_config=tool_config, - tools=tools, - top_k=top_k, - top_logprobs=top_logprobs, - top_p=top_p, - user=user, - verbosity=verbosity, - web_search_options=web_search_options, - response_format=_type_to_response_format(response_format), - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - idempotency_key=idempotency_key, + stream_cls=AsyncStream[ChatCompletionChunk], ) return AsyncChatCompletionStreamManager( diff --git a/src/dedalus_labs/resources/health.py b/src/dedalus_labs/resources/health.py deleted file mode 100644 index 6f751d2..0000000 --- a/src/dedalus_labs/resources/health.py +++ /dev/null @@ -1,135 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import httpx - -from .._types import Body, Query, Headers, NotGiven, not_given -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from .._base_client import make_request_options -from ..types.health_check_response import HealthCheckResponse - -__all__ = ["HealthResource", "AsyncHealthResource"] - - -class HealthResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> HealthResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/dedalus-labs/dedalus-sdk-python#accessing-raw-response-data-eg-headers - """ - return HealthResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> HealthResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/dedalus-labs/dedalus-sdk-python#with_streaming_response - """ - return HealthResourceWithStreamingResponse(self) - - def check( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> HealthCheckResponse: - """Simple health check.""" - return self._get( - "/health", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=HealthCheckResponse, - ) - - -class AsyncHealthResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncHealthResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/dedalus-labs/dedalus-sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncHealthResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncHealthResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/dedalus-labs/dedalus-sdk-python#with_streaming_response - """ - return AsyncHealthResourceWithStreamingResponse(self) - - async def check( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> HealthCheckResponse: - """Simple health check.""" - return await self._get( - "/health", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=HealthCheckResponse, - ) - - -class HealthResourceWithRawResponse: - def __init__(self, health: HealthResource) -> None: - self._health = health - - self.check = to_raw_response_wrapper( - health.check, - ) - - -class AsyncHealthResourceWithRawResponse: - def __init__(self, health: AsyncHealthResource) -> None: - self._health = health - - self.check = async_to_raw_response_wrapper( - health.check, - ) - - -class HealthResourceWithStreamingResponse: - def __init__(self, health: HealthResource) -> None: - self._health = health - - self.check = to_streamed_response_wrapper( - health.check, - ) - - -class AsyncHealthResourceWithStreamingResponse: - def __init__(self, health: AsyncHealthResource) -> None: - self._health = health - - self.check = async_to_streamed_response_wrapper( - health.check, - ) diff --git a/src/dedalus_labs/resources/root.py b/src/dedalus_labs/resources/root.py deleted file mode 100644 index 714ec6a..0000000 --- a/src/dedalus_labs/resources/root.py +++ /dev/null @@ -1,135 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import httpx - -from .._types import Body, Query, Headers, NotGiven, not_given -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from .._base_client import make_request_options -from ..types.root_get_response import RootGetResponse - -__all__ = ["RootResource", "AsyncRootResource"] - - -class RootResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> RootResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/dedalus-labs/dedalus-sdk-python#accessing-raw-response-data-eg-headers - """ - return RootResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> RootResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/dedalus-labs/dedalus-sdk-python#with_streaming_response - """ - return RootResourceWithStreamingResponse(self) - - def get( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> RootGetResponse: - """Root""" - return self._get( - "/", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=RootGetResponse, - ) - - -class AsyncRootResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncRootResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/dedalus-labs/dedalus-sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncRootResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncRootResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/dedalus-labs/dedalus-sdk-python#with_streaming_response - """ - return AsyncRootResourceWithStreamingResponse(self) - - async def get( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> RootGetResponse: - """Root""" - return await self._get( - "/", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=RootGetResponse, - ) - - -class RootResourceWithRawResponse: - def __init__(self, root: RootResource) -> None: - self._root = root - - self.get = to_raw_response_wrapper( - root.get, - ) - - -class AsyncRootResourceWithRawResponse: - def __init__(self, root: AsyncRootResource) -> None: - self._root = root - - self.get = async_to_raw_response_wrapper( - root.get, - ) - - -class RootResourceWithStreamingResponse: - def __init__(self, root: RootResource) -> None: - self._root = root - - self.get = to_streamed_response_wrapper( - root.get, - ) - - -class AsyncRootResourceWithStreamingResponse: - def __init__(self, root: AsyncRootResource) -> None: - self._root = root - - self.get = async_to_streamed_response_wrapper( - root.get, - ) diff --git a/src/dedalus_labs/types/__init__.py b/src/dedalus_labs/types/__init__.py index 7a7776d..40934bf 100644 --- a/src/dedalus_labs/types/__init__.py +++ b/src/dedalus_labs/types/__init__.py @@ -2,21 +2,51 @@ from __future__ import annotations +from . import chat, shared +from .. import _compat from .image import Image as Image from .model import Model as Model from .shared import ( + Reasoning as Reasoning, + Credential as Credential, + MCPServers as MCPServers, + ToolChoice as ToolChoice, DedalusModel as DedalusModel, + MCPServerSpec as MCPServerSpec, + MCPToolResult as MCPToolResult, + ModelSettings as ModelSettings, + JSONValueInput as JSONValueInput, + MCPCredentials as MCPCredentials, + JSONObjectInput as JSONObjectInput, DedalusModelChoice as DedalusModelChoice, + FunctionDefinition as FunctionDefinition, ResponseFormatText as ResponseFormatText, ResponseFormatJSONObject as ResponseFormatJSONObject, ResponseFormatJSONSchema as ResponseFormatJSONSchema, ) from .images_response import ImagesResponse as ImagesResponse from .image_edit_params import ImageEditParams as ImageEditParams -from .root_get_response import RootGetResponse as RootGetResponse from .list_models_response import ListModelsResponse as ListModelsResponse -from .health_check_response import HealthCheckResponse as HealthCheckResponse from .image_generate_params import ImageGenerateParams as ImageGenerateParams from .embedding_create_params import EmbeddingCreateParams as EmbeddingCreateParams from .create_embedding_response import CreateEmbeddingResponse as CreateEmbeddingResponse from .image_create_variation_params import ImageCreateVariationParams as ImageCreateVariationParams + +# Rebuild cyclical models only after all modules are imported. +# This ensures that, when building the deferred (due to cyclical references) model schema, +# Pydantic can resolve the necessary references. +# See: https://github.com/pydantic/pydantic/issues/11250 for more context. +if _compat.PYDANTIC_V1: + chat.chat_completion.ChatCompletion.update_forward_refs() # type: ignore + shared.dedalus_model.DedalusModel.update_forward_refs() # type: ignore + shared.function_definition.FunctionDefinition.update_forward_refs() # type: ignore + shared.mcp_tool_result.MCPToolResult.update_forward_refs() # type: ignore + shared.model_settings.ModelSettings.update_forward_refs() # type: ignore + shared.response_format_json_schema.ResponseFormatJSONSchema.update_forward_refs() # type: ignore +else: + chat.chat_completion.ChatCompletion.model_rebuild(_parent_namespace_depth=0) + shared.dedalus_model.DedalusModel.model_rebuild(_parent_namespace_depth=0) + shared.function_definition.FunctionDefinition.model_rebuild(_parent_namespace_depth=0) + shared.mcp_tool_result.MCPToolResult.model_rebuild(_parent_namespace_depth=0) + shared.model_settings.ModelSettings.model_rebuild(_parent_namespace_depth=0) + shared.response_format_json_schema.ResponseFormatJSONSchema.model_rebuild(_parent_namespace_depth=0) diff --git a/src/dedalus_labs/types/audio/speech_create_params.py b/src/dedalus_labs/types/audio/speech_create_params.py index 355893f..888f49e 100644 --- a/src/dedalus_labs/types/audio/speech_create_params.py +++ b/src/dedalus_labs/types/audio/speech_create_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional +from typing import Union from typing_extensions import Literal, Required, TypedDict __all__ = ["SpeechCreateParams"] @@ -12,14 +12,14 @@ class SpeechCreateParams(TypedDict, total=False): input: Required[str] """The text to generate audio for. The maximum length is 4096 characters.""" - model: Required[str] + model: Required[Union[str, Literal["tts-1", "tts-1-hd", "gpt-4o-mini-tts"]]] """ One of the available [TTS models](https://platform.openai.com/docs/models#tts): - `openai/tts-1`, `openai/tts-1-hd` or `openai/gpt-4o-mini-tts`. + `tts-1`, `tts-1-hd` or `gpt-4o-mini-tts`. """ voice: Required[ - Literal["alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"] + Union[str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"]] ] """The voice to use when generating the audio. @@ -29,25 +29,25 @@ class SpeechCreateParams(TypedDict, total=False): [Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech#voice-options). """ - instructions: Optional[str] + instructions: str """Control the voice of your generated audio with additional instructions. Does not work with `tts-1` or `tts-1-hd`. """ - response_format: Optional[Literal["mp3", "opus", "aac", "flac", "wav", "pcm"]] + response_format: Literal["mp3", "opus", "aac", "flac", "wav", "pcm"] """The format to audio in. Supported formats are `mp3`, `opus`, `aac`, `flac`, `wav`, and `pcm`. """ - speed: Optional[float] + speed: float """The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is the default. """ - stream_format: Optional[Literal["sse", "audio"]] + stream_format: Literal["sse", "audio"] """The format to stream the audio in. Supported formats are `sse` and `audio`. `sse` is not supported for `tts-1` or diff --git a/src/dedalus_labs/types/audio/transcription_create_response.py b/src/dedalus_labs/types/audio/transcription_create_response.py index d3b8a72..6f2f1d4 100644 --- a/src/dedalus_labs/types/audio/transcription_create_response.py +++ b/src/dedalus_labs/types/audio/transcription_create_response.py @@ -5,6 +5,7 @@ from ..._utils import PropertyInfo from ..._models import BaseModel +from ..chat.input_token_details import InputTokenDetails __all__ = [ "TranscriptionCreateResponse", @@ -16,12 +17,25 @@ "CreateTranscriptionResponseJSONLogprob", "CreateTranscriptionResponseJSONUsage", "CreateTranscriptionResponseJSONUsageTranscriptTextUsageTokens", - "CreateTranscriptionResponseJSONUsageTranscriptTextUsageTokensInputTokenDetails", "CreateTranscriptionResponseJSONUsageTranscriptTextUsageDuration", ] class CreateTranscriptionResponseVerboseJSONSegment(BaseModel): + """ + Fields: + - id (required): int + - seek (required): int + - start (required): float + - end (required): float + - text (required): str + - tokens (required): list[int] + - temperature (required): float + - avg_logprob (required): float + - compression_ratio (required): float + - no_speech_prob (required): float + """ + id: int """Unique identifier of the segment.""" @@ -64,6 +78,8 @@ class CreateTranscriptionResponseVerboseJSONSegment(BaseModel): class CreateTranscriptionResponseVerboseJSONUsage(BaseModel): + """Usage statistics for models billed by audio input duration.""" + seconds: float """Duration of the input audio in seconds.""" @@ -72,6 +88,13 @@ class CreateTranscriptionResponseVerboseJSONUsage(BaseModel): class CreateTranscriptionResponseVerboseJSONWord(BaseModel): + """ + Fields: + - word (required): str + - start (required): float + - end (required): float + """ + end: float """End time of the word in seconds.""" @@ -83,6 +106,18 @@ class CreateTranscriptionResponseVerboseJSONWord(BaseModel): class CreateTranscriptionResponseVerboseJSON(BaseModel): + """ + Represents a verbose json transcription response returned by model, based on the provided input. + + Fields: + - language (required): str + - duration (required): float + - text (required): str + - words (optional): list[TranscriptionWord] + - segments (optional): list[TranscriptionSegment] + - usage (optional): TranscriptTextUsageDuration + """ + duration: float """The duration of the input audio.""" @@ -103,6 +138,13 @@ class CreateTranscriptionResponseVerboseJSON(BaseModel): class CreateTranscriptionResponseJSONLogprob(BaseModel): + """ + Fields: + - token (optional): str + - logprob (optional): float + - bytes (optional): list[float] + """ + token: Optional[str] = None """The token in the transcription.""" @@ -113,15 +155,17 @@ class CreateTranscriptionResponseJSONLogprob(BaseModel): """The log probability of the token.""" -class CreateTranscriptionResponseJSONUsageTranscriptTextUsageTokensInputTokenDetails(BaseModel): - audio_tokens: Optional[int] = None - """Number of audio tokens billed for this request.""" - - text_tokens: Optional[int] = None - """Number of text tokens billed for this request.""" - - class CreateTranscriptionResponseJSONUsageTranscriptTextUsageTokens(BaseModel): + """Usage statistics for models billed by token usage. + + Fields: + - type (required): Literal['tokens'] + - input_tokens (required): int + - input_token_details (optional): InputTokenDetails + - output_tokens (required): int + - total_tokens (required): int + """ + input_tokens: int """Number of input tokens billed for this request.""" @@ -134,11 +178,18 @@ class CreateTranscriptionResponseJSONUsageTranscriptTextUsageTokens(BaseModel): type: Literal["tokens"] """The type of the usage object. Always `tokens` for this variant.""" - input_token_details: Optional[CreateTranscriptionResponseJSONUsageTranscriptTextUsageTokensInputTokenDetails] = None + input_token_details: Optional[InputTokenDetails] = None """Details about the input tokens billed for this request.""" class CreateTranscriptionResponseJSONUsageTranscriptTextUsageDuration(BaseModel): + """Usage statistics for models billed by audio input duration. + + Fields: + - type (required): Literal['duration'] + - seconds (required): float + """ + seconds: float """Duration of the input audio in seconds.""" @@ -156,6 +207,15 @@ class CreateTranscriptionResponseJSONUsageTranscriptTextUsageDuration(BaseModel) class CreateTranscriptionResponseJSON(BaseModel): + """ + Represents a transcription response returned by model, based on the provided input. + + Fields: + - text (required): str + - logprobs (optional): list[LogprobsItem] + - usage (optional): Usage + """ + text: str """The transcribed text.""" diff --git a/src/dedalus_labs/types/audio/translation_create_response.py b/src/dedalus_labs/types/audio/translation_create_response.py index b88e9a0..055d8b1 100644 --- a/src/dedalus_labs/types/audio/translation_create_response.py +++ b/src/dedalus_labs/types/audio/translation_create_response.py @@ -14,6 +14,20 @@ class CreateTranslationResponseVerboseJSONSegment(BaseModel): + """ + Fields: + - id (required): int + - seek (required): int + - start (required): float + - end (required): float + - text (required): str + - tokens (required): list[int] + - temperature (required): float + - avg_logprob (required): float + - compression_ratio (required): float + - no_speech_prob (required): float + """ + id: int """Unique identifier of the segment.""" @@ -56,6 +70,14 @@ class CreateTranslationResponseVerboseJSONSegment(BaseModel): class CreateTranslationResponseVerboseJSON(BaseModel): + """ + Fields: + - language (required): str + - duration (required): float + - text (required): str + - segments (optional): list[TranscriptionSegment] + """ + duration: float """The duration of the input audio.""" @@ -70,6 +92,11 @@ class CreateTranslationResponseVerboseJSON(BaseModel): class CreateTranslationResponseJSON(BaseModel): + """ + Fields: + - text (required): str + """ + text: str diff --git a/src/dedalus_labs/types/chat/__init__.py b/src/dedalus_labs/types/chat/__init__.py index 8cfcbfd..875ffcb 100644 --- a/src/dedalus_labs/types/chat/__init__.py +++ b/src/dedalus_labs/types/chat/__init__.py @@ -2,10 +2,66 @@ from __future__ import annotations -from .model_id import ModelID as ModelID -from .completion import Completion as Completion -from .top_logprob import TopLogprob as TopLogprob -from .models_param import ModelsParam as ModelsParam -from .stream_chunk import StreamChunk as StreamChunk +from .choice import Choice as Choice +from .audio_param import AudioParam as AudioParam +from .choice_delta import ChoiceDelta as ChoiceDelta +from .stream_choice import StreamChoice as StreamChoice +from .chat_completion import ChatCompletion as ChatCompletion +from .choice_logprobs import ChoiceLogprobs as ChoiceLogprobs +from .completion_usage import CompletionUsage as CompletionUsage +from .input_token_details import InputTokenDetails as InputTokenDetails +from .chat_completion_chunk import ChatCompletionChunk as ChatCompletionChunk +from .prompt_tokens_details import PromptTokensDetails as PromptTokensDetails +from .tool_choice_any_param import ToolChoiceAnyParam as ToolChoiceAnyParam +from .choice_delta_tool_call import ChoiceDeltaToolCall as ChoiceDeltaToolCall +from .stream_choice_logprobs import StreamChoiceLogprobs as StreamChoiceLogprobs +from .tool_choice_auto_param import ToolChoiceAutoParam as ToolChoiceAutoParam +from .tool_choice_none_param import ToolChoiceNoneParam as ToolChoiceNoneParam +from .tool_choice_tool_param import ToolChoiceToolParam as ToolChoiceToolParam +from .chat_completion_message import ChatCompletionMessage as ChatCompletionMessage from .completion_create_params import CompletionCreateParams as CompletionCreateParams +from .prediction_content_param import PredictionContentParam as PredictionContentParam +from .completion_tokens_details import CompletionTokensDetails as CompletionTokensDetails +from .chat_completion_tool_param import ChatCompletionToolParam as ChatCompletionToolParam +from .chat_completion_audio_param import ChatCompletionAudioParam as ChatCompletionAudioParam from .chat_completion_token_logprob import ChatCompletionTokenLogprob as ChatCompletionTokenLogprob +from .thinking_config_enabled_param import ThinkingConfigEnabledParam as ThinkingConfigEnabledParam +from .thinking_config_disabled_param import ThinkingConfigDisabledParam as ThinkingConfigDisabledParam +from .chat_completion_functions_param import ChatCompletionFunctionsParam as ChatCompletionFunctionsParam +from .chat_completion_message_tool_call import ChatCompletionMessageToolCall as ChatCompletionMessageToolCall +from .chat_completion_tool_message_param import ChatCompletionToolMessageParam as ChatCompletionToolMessageParam +from .chat_completion_user_message_param import ChatCompletionUserMessageParam as ChatCompletionUserMessageParam +from .chat_completion_system_message_param import ChatCompletionSystemMessageParam as ChatCompletionSystemMessageParam +from .chat_completion_function_message_param import ( + ChatCompletionFunctionMessageParam as ChatCompletionFunctionMessageParam, +) +from .chat_completion_assistant_message_param import ( + ChatCompletionAssistantMessageParam as ChatCompletionAssistantMessageParam, +) +from .chat_completion_content_part_file_param import ( + ChatCompletionContentPartFileParam as ChatCompletionContentPartFileParam, +) +from .chat_completion_content_part_text_param import ( + ChatCompletionContentPartTextParam as ChatCompletionContentPartTextParam, +) +from .chat_completion_developer_message_param import ( + ChatCompletionDeveloperMessageParam as ChatCompletionDeveloperMessageParam, +) +from .chat_completion_message_tool_call_param import ( + ChatCompletionMessageToolCallParam as ChatCompletionMessageToolCallParam, +) +from .chat_completion_content_part_image_param import ( + ChatCompletionContentPartImageParam as ChatCompletionContentPartImageParam, +) +from .chat_completion_message_custom_tool_call import ( + ChatCompletionMessageCustomToolCall as ChatCompletionMessageCustomToolCall, +) +from .chat_completion_content_part_refusal_param import ( + ChatCompletionContentPartRefusalParam as ChatCompletionContentPartRefusalParam, +) +from .chat_completion_content_part_input_audio_param import ( + ChatCompletionContentPartInputAudioParam as ChatCompletionContentPartInputAudioParam, +) +from .chat_completion_message_custom_tool_call_param import ( + ChatCompletionMessageCustomToolCallParam as ChatCompletionMessageCustomToolCallParam, +) diff --git a/src/dedalus_labs/types/chat/audio_param.py b/src/dedalus_labs/types/chat/audio_param.py new file mode 100644 index 0000000..a786fb1 --- /dev/null +++ b/src/dedalus_labs/types/chat/audio_param.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["AudioParam"] + + +class AudioParam(TypedDict, total=False): + """ + Data about a previous audio response from the model. + [Learn more](https://platform.openai.com/docs/guides/audio). + + Fields: + - id (required): str + """ + + id: Required[str] + """Unique identifier for a previous audio response from the model.""" diff --git a/src/dedalus_labs/types/chat/chat_completion.py b/src/dedalus_labs/types/chat/chat_completion.py new file mode 100644 index 0000000..5c6f850 --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion.py @@ -0,0 +1,93 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import builtins +from typing import Dict, List, Optional +from typing_extensions import Literal + +from .choice import Choice +from ..._models import BaseModel +from .completion_usage import CompletionUsage + +__all__ = ["ChatCompletion"] + + +class ChatCompletion(BaseModel): + """Chat completion response for Dedalus API. + + OpenAI-compatible chat completion response with Dedalus extensions. + Maintains full compatibility with OpenAI API while providing additional + features like server-side tool execution tracking and MCP error reporting. + """ + + id: str + """A unique identifier for the chat completion.""" + + choices: List[Choice] + """A list of chat completion choices. + + Can be more than one if `n` is greater than 1. + """ + + created: int + """The Unix timestamp (in seconds) of when the chat completion was created.""" + + model: str + """The model used for the chat completion.""" + + object: Literal["chat.completion"] + """The object type, which is always `chat.completion`.""" + + mcp_server_errors: Optional[Dict[str, builtins.object]] = None + """Information about MCP server failures, if any occurred during the request. + + Contains details about which servers failed and why, along with recommendations + for the user. Only present when MCP server failures occurred. + """ + + mcp_tool_results: Optional[List["MCPToolResult"]] = None + """Detailed results of MCP tool executions including inputs, outputs, and timing. + + Provides full visibility into server-side tool execution for debugging and audit + purposes. + """ + + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] = None + """Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + """ + + system_fingerprint: Optional[str] = None + """This fingerprint represents the backend configuration that the model runs with. + + Can be used in conjunction with the `seed` request parameter to understand when + backend changes have been made that might impact determinism. + """ + + tools_executed: Optional[List[str]] = None + """List of tool names that were executed server-side (e.g., MCP tools). + + Only present when tools were executed on the server rather than returned for + client-side execution. + """ + + usage: Optional[CompletionUsage] = None + """Usage statistics for the completion request.""" + + +from ..shared.mcp_tool_result import MCPToolResult diff --git a/src/dedalus_labs/types/chat/chat_completion_assistant_message_param.py b/src/dedalus_labs/types/chat/chat_completion_assistant_message_param.py new file mode 100644 index 0000000..70faf6b --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_assistant_message_param.py @@ -0,0 +1,106 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable, Optional +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .audio_param import AudioParam +from .chat_completion_content_part_text_param import ChatCompletionContentPartTextParam +from .chat_completion_message_tool_call_param import ChatCompletionMessageToolCallParam +from .chat_completion_content_part_refusal_param import ChatCompletionContentPartRefusalParam +from .chat_completion_message_custom_tool_call_param import ChatCompletionMessageCustomToolCallParam + +__all__ = [ + "ChatCompletionAssistantMessageParam", + "ContentChatCompletionRequestAssistantMessageContentArray", + "FunctionCall", + "ToolCall", +] + +ContentChatCompletionRequestAssistantMessageContentArray: TypeAlias = Union[ + ChatCompletionContentPartTextParam, ChatCompletionContentPartRefusalParam +] + + +class FunctionCall(TypedDict, total=False): + """Deprecated and replaced by `tool_calls`. + + The name and arguments of a function that should be called, as generated by the model. + + Fields: + - arguments (required): str + - name (required): str + """ + + arguments: Required[str] + """ + The arguments to call the function with, as generated by the model in JSON + format. Note that the model does not always generate valid JSON, and may + hallucinate parameters not defined by your function schema. Validate the + arguments in your code before calling your function. + """ + + name: Required[str] + """The name of the function to call.""" + + +ToolCall: TypeAlias = Union[ChatCompletionMessageToolCallParam, ChatCompletionMessageCustomToolCallParam] + + +class ChatCompletionAssistantMessageParam(TypedDict, total=False): + """Messages sent by the model in response to user messages. + + Fields: + - content (optional): str | Annotated[list[ChatCompletionRequestAssistantMessageContentPart], MinLen(1), ArrayTitle("ChatCompletionRequestAssistantMessageContentArray")] | None + - refusal (optional): str | None + - role (required): Literal["assistant"] + - name (optional): str + - audio (optional): Audio | None + - tool_calls (optional): ChatCompletionMessageToolCalls + - function_call (optional): FunctionCall | None + """ + + role: Required[Literal["assistant"]] + """The role of the messages author, in this case `assistant`.""" + + audio: Optional[AudioParam] + """ + Data about a previous audio response from the model. + [Learn more](https://platform.openai.com/docs/guides/audio). + + Fields: + + - id (required): str + """ + + content: Union[str, Iterable[ContentChatCompletionRequestAssistantMessageContentArray], None] + """The contents of the assistant message. + + Required unless `tool_calls` or `function_call` is specified. + """ + + function_call: Optional[FunctionCall] + """Deprecated and replaced by `tool_calls`. + + The name and arguments of a function that should be called, as generated by the + model. + + Fields: + + - arguments (required): str + - name (required): str + """ + + name: str + """An optional name for the participant. + + Provides the model information to differentiate between participants of the same + role. + """ + + refusal: Optional[str] + """The refusal message by the assistant.""" + + tool_calls: Iterable[ToolCall] + """The tool calls generated by the model, such as function calls.""" diff --git a/src/dedalus_labs/types/chat/chat_completion_audio_param.py b/src/dedalus_labs/types/chat/chat_completion_audio_param.py new file mode 100644 index 0000000..0ea75cd --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_audio_param.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionAudioParam"] + + +class ChatCompletionAudioParam(TypedDict, total=False): + """Parameters for audio output. + + Required when audio output is requested with + `modalities: ["audio"]`. [Learn more](https://platform.openai.com/docs/guides/audio). + + Fields: + - voice (required): VoiceIdsShared + - format (required): Literal["wav", "aac", "mp3", "flac", "opus", "pcm16"] + """ + + format: Required[Literal["wav", "aac", "mp3", "flac", "opus", "pcm16"]] + """Specifies the output audio format. + + Must be one of `wav`, `mp3`, `flac`, `opus`, or `pcm16`. + """ + + voice: Required[ + Union[str, Literal["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse", "marin", "cedar"]] + ] + """The voice the model uses to respond. + + Supported voices are `alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `nova`, + `onyx`, `sage`, and `shimmer`. + """ diff --git a/src/dedalus_labs/types/chat/chat_completion_chunk.py b/src/dedalus_labs/types/chat/chat_completion_chunk.py new file mode 100644 index 0000000..4248a61 --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_chunk.py @@ -0,0 +1,88 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .stream_choice import StreamChoice +from .completion_usage import CompletionUsage + +__all__ = ["ChatCompletionChunk"] + + +class ChatCompletionChunk(BaseModel): + """ + Represents a streamed chunk of a chat completion response returned + by the model, based on the provided input. + [Learn more](https://platform.openai.com/docs/guides/streaming-responses). + + Fields: + - id (required): str + - choices (required): list[ChatCompletionStreamResponseChoicesItem] + - created (required): int + - model (required): str + - service_tier (optional): ServiceTier + - system_fingerprint (optional): str + - object (required): Literal["chat.completion.chunk"] + - usage (optional): CompletionUsage + """ + + id: str + """A unique identifier for the chat completion. Each chunk has the same ID.""" + + choices: List[StreamChoice] + """A list of chat completion choices. + + Can contain more than one elements if `n` is greater than 1. Can also be empty + for the last chunk if you set `stream_options: {"include_usage": true}`. + """ + + created: int + """The Unix timestamp (in seconds) of when the chat completion was created. + + Each chunk has the same timestamp. + """ + + model: str + """The model to generate the completion.""" + + object: Literal["chat.completion.chunk"] + """The object type, which is always `chat.completion.chunk`.""" + + service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] = None + """Specifies the processing type used for serving the request. + + - If set to 'auto', then the request will be processed with the service tier + configured in the Project settings. Unless otherwise configured, the Project + will use 'default'. + - If set to 'default', then the request will be processed with the standard + pricing and performance for the selected model. + - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or + '[priority](https://openai.com/api-priority-processing/)', then the request + will be processed with the corresponding service tier. + - When not set, the default behavior is 'auto'. + + When the `service_tier` parameter is set, the response body will include the + `service_tier` value based on the processing mode actually used to serve the + request. This response value may be different from the value set in the + parameter. + """ + + system_fingerprint: Optional[str] = None + """ + This fingerprint represents the backend configuration that the model runs with. + Can be used in conjunction with the `seed` request parameter to understand when + backend changes have been made that might impact determinism. + """ + + usage: Optional[CompletionUsage] = None + """Usage statistics for the completion request. + + Fields: + + - completion_tokens (required): int + - prompt_tokens (required): int + - total_tokens (required): int + - completion_tokens_details (optional): CompletionTokensDetails + - prompt_tokens_details (optional): PromptTokensDetails + """ diff --git a/src/dedalus_labs/types/chat/chat_completion_content_part_file_param.py b/src/dedalus_labs/types/chat/chat_completion_content_part_file_param.py new file mode 100644 index 0000000..df4cf6e --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_content_part_file_param.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionContentPartFileParam", "File"] + + +class File(TypedDict, total=False): + """Schema for File. + + Fields: + - filename (optional): str + - file_data (optional): str + - file_id (optional): str + """ + + file_data: str + """ + The base64 encoded file data, used when passing the file to the model as a + string. + """ + + file_id: str + """The ID of an uploaded file to use as input.""" + + filename: str + """The name of the file, used when passing the file to the model as a string.""" + + +class ChatCompletionContentPartFileParam(TypedDict, total=False): + """ + Learn about [file inputs](https://platform.openai.com/docs/guides/text) for text generation. + + Fields: + - type (required): Literal["file"] + - file (required): File + """ + + file: Required[File] + """Schema for File. + + Fields: + + - filename (optional): str + - file_data (optional): str + - file_id (optional): str + """ + + type: Required[Literal["file"]] + """The type of the content part. Always `file`.""" diff --git a/src/dedalus_labs/types/chat/chat_completion_content_part_image_param.py b/src/dedalus_labs/types/chat/chat_completion_content_part_image_param.py new file mode 100644 index 0000000..5b7c529 --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_content_part_image_param.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionContentPartImageParam", "ImageURL"] + + +class ImageURL(TypedDict, total=False): + """Schema for ImageUrl. + + Fields: + - url (required): AnyUrl + - detail (optional): Literal["auto", "low", "high"] + """ + + url: Required[str] + """Either a URL of the image or the base64 encoded image data.""" + + detail: Literal["auto", "low", "high"] + """Specifies the detail level of the image. + + Learn more in the + [Vision guide](https://platform.openai.com/docs/guides/vision#low-or-high-fidelity-image-understanding). + """ + + +class ChatCompletionContentPartImageParam(TypedDict, total=False): + """Learn about [image inputs](https://platform.openai.com/docs/guides/vision). + + Fields: + - type (required): Literal["image_url"] + - image_url (required): ImageUrl + """ + + image_url: Required[ImageURL] + """Schema for ImageUrl. + + Fields: + + - url (required): AnyUrl + - detail (optional): Literal["auto", "low", "high"] + """ + + type: Required[Literal["image_url"]] + """The type of the content part.""" diff --git a/src/dedalus_labs/types/chat/chat_completion_content_part_input_audio_param.py b/src/dedalus_labs/types/chat/chat_completion_content_part_input_audio_param.py new file mode 100644 index 0000000..169e8ce --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_content_part_input_audio_param.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionContentPartInputAudioParam", "InputAudio"] + + +class InputAudio(TypedDict, total=False): + """Schema for InputAudio. + + Fields: + - data (required): str + - format (required): Literal["wav", "mp3"] + """ + + data: Required[str] + """Base64 encoded audio data.""" + + format: Required[Literal["wav", "mp3"]] + """The format of the encoded audio data. Currently supports "wav" and "mp3".""" + + +class ChatCompletionContentPartInputAudioParam(TypedDict, total=False): + """Learn about [audio inputs](https://platform.openai.com/docs/guides/audio). + + Fields: + - type (required): Literal["input_audio"] + - input_audio (required): InputAudio + """ + + input_audio: Required[InputAudio] + """Schema for InputAudio. + + Fields: + + - data (required): str + - format (required): Literal["wav", "mp3"] + """ + + type: Required[Literal["input_audio"]] + """The type of the content part. Always `input_audio`.""" diff --git a/src/dedalus_labs/types/chat/chat_completion_content_part_refusal_param.py b/src/dedalus_labs/types/chat/chat_completion_content_part_refusal_param.py new file mode 100644 index 0000000..f50ce3b --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_content_part_refusal_param.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionContentPartRefusalParam"] + + +class ChatCompletionContentPartRefusalParam(TypedDict, total=False): + """Schema for ChatCompletionRequestMessageContentPartRefusal. + + Fields: + - type (required): Literal["refusal"] + - refusal (required): str + """ + + refusal: Required[str] + """The refusal message generated by the model.""" + + type: Required[Literal["refusal"]] + """The type of the content part.""" diff --git a/src/dedalus_labs/types/chat/chat_completion_content_part_text_param.py b/src/dedalus_labs/types/chat/chat_completion_content_part_text_param.py new file mode 100644 index 0000000..13e844e --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_content_part_text_param.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionContentPartTextParam"] + + +class ChatCompletionContentPartTextParam(TypedDict, total=False): + """ + Learn about [text inputs](https://platform.openai.com/docs/guides/text-generation). + + Fields: + - type (required): Literal["text"] + - text (required): str + """ + + text: Required[str] + """The text content.""" + + type: Required[Literal["text"]] + """The type of the content part.""" diff --git a/src/dedalus_labs/types/chat/chat_completion_developer_message_param.py b/src/dedalus_labs/types/chat/chat_completion_developer_message_param.py new file mode 100644 index 0000000..28d09b2 --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_developer_message_param.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypedDict + +from .chat_completion_content_part_text_param import ChatCompletionContentPartTextParam + +__all__ = ["ChatCompletionDeveloperMessageParam"] + + +class ChatCompletionDeveloperMessageParam(TypedDict, total=False): + """ + Developer-provided instructions that the model should follow, regardless of + messages sent by the user. With o1 models and newer, `developer` messages + replace the previous `system` messages. + + Fields: + - content (required): str | Annotated[list[ChatCompletionRequestMessageContentPartText], MinLen(1), ArrayTitle("ChatCompletionRequestDeveloperMessageContentArray")] + - role (required): Literal["developer"] + - name (optional): str + """ + + content: Required[Union[str, Iterable[ChatCompletionContentPartTextParam]]] + """The contents of the developer message.""" + + role: Required[Literal["developer"]] + """The role of the messages author, in this case `developer`.""" + + name: str + """An optional name for the participant. + + Provides the model information to differentiate between participants of the same + role. + """ diff --git a/src/dedalus_labs/types/chat/chat_completion_function_message_param.py b/src/dedalus_labs/types/chat/chat_completion_function_message_param.py new file mode 100644 index 0000000..7343294 --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_function_message_param.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionFunctionMessageParam"] + + +class ChatCompletionFunctionMessageParam(TypedDict, total=False): + """Schema for ChatCompletionRequestFunctionMessage. + + Fields: + - role (required): Literal["function"] + - content (required): str | None + - name (required): str + """ + + content: Required[Optional[str]] + """The contents of the function message.""" + + name: Required[str] + """The name of the function to call.""" + + role: Required[Literal["function"]] + """The role of the messages author, in this case `function`.""" diff --git a/src/dedalus_labs/types/chat/chat_completion_functions_param.py b/src/dedalus_labs/types/chat/chat_completion_functions_param.py new file mode 100644 index 0000000..0ebde92 --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_functions_param.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["ChatCompletionFunctionsParam"] + + +class ChatCompletionFunctionsParam(TypedDict, total=False): + """Schema for ChatCompletionFunctions. + + Fields: + - description (optional): str + - name (required): str + - parameters (optional): FunctionParameters + """ + + name: Required[str] + """The name of the function to be called. + + Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length + of 64. + """ + + description: str + """ + A description of what the function does, used by the model to choose when and + how to call the function. + """ + + parameters: "JSONObjectInput" + """The parameters the functions accepts, described as a JSON Schema object. + + See the [guide](https://platform.openai.com/docs/guides/function-calling) for + examples, and the + [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for + documentation about the format. + + Omitting `parameters` defines a function with an empty parameter list. + """ + + +from ..shared_params.json_object_input import JSONObjectInput diff --git a/src/dedalus_labs/types/chat/chat_completion_message.py b/src/dedalus_labs/types/chat/chat_completion_message.py new file mode 100644 index 0000000..c240952 --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_message.py @@ -0,0 +1,173 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel +from .chat_completion_message_tool_call import ChatCompletionMessageToolCall +from .chat_completion_message_custom_tool_call import ChatCompletionMessageCustomToolCall + +__all__ = ["ChatCompletionMessage", "Annotation", "AnnotationURLCitation", "Audio", "FunctionCall", "ToolCall"] + + +class AnnotationURLCitation(BaseModel): + """A URL citation when using web search. + + Fields: + - end_index (required): int + - start_index (required): int + - url (required): str + - title (required): str + """ + + end_index: int + """The index of the last character of the URL citation in the message.""" + + start_index: int + """The index of the first character of the URL citation in the message.""" + + title: str + """The title of the web resource.""" + + url: str + """The URL of the web resource.""" + + +class Annotation(BaseModel): + """A URL citation when using web search. + + Fields: + - type (required): Literal["url_citation"] + - url_citation (required): UrlCitation + """ + + type: Literal["url_citation"] + """The type of the URL citation. Always `url_citation`.""" + + url_citation: AnnotationURLCitation + """A URL citation when using web search. + + Fields: + + - end_index (required): int + - start_index (required): int + - url (required): str + - title (required): str + """ + + +class Audio(BaseModel): + """ + If the audio output modality is requested, this object contains data + about the audio response from the model. [Learn more](https://platform.openai.com/docs/guides/audio). + + Fields: + - id (required): str + - expires_at (required): int + - data (required): str + - transcript (required): str + """ + + id: str + """Unique identifier for this audio response.""" + + data: str + """ + Base64 encoded audio bytes generated by the model, in the format specified in + the request. + """ + + expires_at: int + """ + The Unix timestamp (in seconds) for when this audio response will no longer be + accessible on the server for use in multi-turn conversations. + """ + + transcript: str + """Transcript of the audio generated by the model.""" + + +class FunctionCall(BaseModel): + """Deprecated and replaced by `tool_calls`. + + The name and arguments of a function that should be called, as generated by the model. + + Fields: + - arguments (required): str + - name (required): str + """ + + arguments: str + """ + The arguments to call the function with, as generated by the model in JSON + format. Note that the model does not always generate valid JSON, and may + hallucinate parameters not defined by your function schema. Validate the + arguments in your code before calling your function. + """ + + name: str + """The name of the function to call.""" + + +ToolCall: TypeAlias = Annotated[ + Union[ChatCompletionMessageToolCall, ChatCompletionMessageCustomToolCall], PropertyInfo(discriminator="type") +] + + +class ChatCompletionMessage(BaseModel): + """A chat completion message generated by the model. + + Fields: + - content (required): str | None + - refusal (required): str | None + - tool_calls (optional): ChatCompletionMessageToolCalls + - annotations (optional): list[AnnotationsItem] + - role (required): Literal["assistant"] + - function_call (optional): ChatCompletionResponseMessageFunctionCall + - audio (optional): ChatCompletionResponseMessageAudio | None + """ + + content: Optional[str] = None + """The contents of the message.""" + + refusal: Optional[str] = None + """The refusal message generated by the model.""" + + role: Literal["assistant"] + """The role of the author of this message.""" + + annotations: Optional[List[Annotation]] = None + """ + Annotations for the message, when applicable, as when using the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). + """ + + audio: Optional[Audio] = None + """ + If the audio output modality is requested, this object contains data about the + audio response from the model. + [Learn more](https://platform.openai.com/docs/guides/audio). + + Fields: + + - id (required): str + - expires_at (required): int + - data (required): str + - transcript (required): str + """ + + function_call: Optional[FunctionCall] = None + """Deprecated and replaced by `tool_calls`. + + The name and arguments of a function that should be called, as generated by the + model. + + Fields: + + - arguments (required): str + - name (required): str + """ + + tool_calls: Optional[List[ToolCall]] = None + """The tool calls generated by the model, such as function calls.""" diff --git a/src/dedalus_labs/types/chat/chat_completion_message_custom_tool_call.py b/src/dedalus_labs/types/chat/chat_completion_message_custom_tool_call.py new file mode 100644 index 0000000..5802bf5 --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_message_custom_tool_call.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ChatCompletionMessageCustomToolCall", "Custom"] + + +class Custom(BaseModel): + """The custom tool that the model called. + + Fields: + - name (required): str + - input (required): str + """ + + input: str + """The input for the custom tool call generated by the model.""" + + name: str + """The name of the custom tool to call.""" + + +class ChatCompletionMessageCustomToolCall(BaseModel): + """A call to a custom tool created by the model. + + Fields: + - id (required): str + - type (required): Literal["custom"] + - custom (required): ChatCompletionMessageCustomToolCallCustom + """ + + id: str + """The ID of the tool call.""" + + custom: Custom + """The custom tool that the model called. + + Fields: + + - name (required): str + - input (required): str + """ + + type: Literal["custom"] + """The type of the tool. Always `custom`.""" diff --git a/src/dedalus_labs/types/chat/chat_completion_message_custom_tool_call_param.py b/src/dedalus_labs/types/chat/chat_completion_message_custom_tool_call_param.py new file mode 100644 index 0000000..0004495 --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_message_custom_tool_call_param.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionMessageCustomToolCallParam", "Custom"] + + +class Custom(TypedDict, total=False): + """The custom tool that the model called. + + Fields: + - name (required): str + - input (required): str + """ + + input: Required[str] + """The input for the custom tool call generated by the model.""" + + name: Required[str] + """The name of the custom tool to call.""" + + +class ChatCompletionMessageCustomToolCallParam(TypedDict, total=False): + """A call to a custom tool created by the model. + + Fields: + - id (required): str + - type (required): Literal["custom"] + - custom (required): ChatCompletionMessageCustomToolCallCustom + """ + + id: Required[str] + """The ID of the tool call.""" + + custom: Required[Custom] + """The custom tool that the model called. + + Fields: + + - name (required): str + - input (required): str + """ + + type: Required[Literal["custom"]] + """The type of the tool. Always `custom`.""" diff --git a/src/dedalus_labs/types/chat/chat_completion_message_tool_call.py b/src/dedalus_labs/types/chat/chat_completion_message_tool_call.py new file mode 100644 index 0000000..d3f82f8 --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_message_tool_call.py @@ -0,0 +1,60 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ChatCompletionMessageToolCall", "Function"] + + +class Function(BaseModel): + """The function that the model called. + + Fields: + - name (required): str + - arguments (required): str + """ + + arguments: str + """ + The arguments to call the function with, as generated by the model in JSON + format. Note that the model does not always generate valid JSON, and may + hallucinate parameters not defined by your function schema. Validate the + arguments in your code before calling your function. + """ + + name: str + """The name of the function to call.""" + + +class ChatCompletionMessageToolCall(BaseModel): + """A call to a function tool created by the model. + + Fields: + - id (required): str + - type (required): Literal["function"] + - function (required): ChatCompletionMessageToolCallFunction + - thought_signature (optional): str + """ + + id: str + """The ID of the tool call.""" + + function: Function + """The function that the model called. + + Fields: + + - name (required): str + - arguments (required): str + """ + + type: Literal["function"] + """The type of the tool. Currently, only `function` is supported.""" + + thought_signature: Optional[str] = None + """ + Opaque signature for thought continuity in multi-turn tool use (Google-specific, + base64 encoded) + """ diff --git a/src/dedalus_labs/types/chat/chat_completion_message_tool_call_param.py b/src/dedalus_labs/types/chat/chat_completion_message_tool_call_param.py new file mode 100644 index 0000000..4d6935a --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_message_tool_call_param.py @@ -0,0 +1,60 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionMessageToolCallParam", "Function"] + + +class Function(TypedDict, total=False): + """The function that the model called. + + Fields: + - name (required): str + - arguments (required): str + """ + + arguments: Required[str] + """ + The arguments to call the function with, as generated by the model in JSON + format. Note that the model does not always generate valid JSON, and may + hallucinate parameters not defined by your function schema. Validate the + arguments in your code before calling your function. + """ + + name: Required[str] + """The name of the function to call.""" + + +class ChatCompletionMessageToolCallParam(TypedDict, total=False): + """A call to a function tool created by the model. + + Fields: + - id (required): str + - type (required): Literal["function"] + - function (required): ChatCompletionMessageToolCallFunction + - thought_signature (optional): str + """ + + id: Required[str] + """The ID of the tool call.""" + + function: Required[Function] + """The function that the model called. + + Fields: + + - name (required): str + - arguments (required): str + """ + + type: Required[Literal["function"]] + """The type of the tool. Currently, only `function` is supported.""" + + thought_signature: Optional[str] + """ + Opaque signature for thought continuity in multi-turn tool use (Google-specific, + base64 encoded) + """ diff --git a/src/dedalus_labs/types/chat/chat_completion_system_message_param.py b/src/dedalus_labs/types/chat/chat_completion_system_message_param.py new file mode 100644 index 0000000..547bd2c --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_system_message_param.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypedDict + +from .chat_completion_content_part_text_param import ChatCompletionContentPartTextParam + +__all__ = ["ChatCompletionSystemMessageParam"] + + +class ChatCompletionSystemMessageParam(TypedDict, total=False): + """ + Developer-provided instructions that the model should follow, regardless of + messages sent by the user. With o1 models and newer, use `developer` messages + for this purpose instead. + + Fields: + - content (required): str | Annotated[list[ChatCompletionRequestSystemMessageContentPart], MinLen(1), ArrayTitle("ChatCompletionRequestSystemMessageContentArray")] + - role (required): Literal["system"] + - name (optional): str + """ + + content: Required[Union[str, Iterable[ChatCompletionContentPartTextParam]]] + """The contents of the system message.""" + + role: Required[Literal["system"]] + """The role of the messages author, in this case `system`.""" + + name: str + """An optional name for the participant. + + Provides the model information to differentiate between participants of the same + role. + """ diff --git a/src/dedalus_labs/types/chat/chat_completion_token_logprob.py b/src/dedalus_labs/types/chat/chat_completion_token_logprob.py index 1ee0915..1e4db3d 100644 --- a/src/dedalus_labs/types/chat/chat_completion_token_logprob.py +++ b/src/dedalus_labs/types/chat/chat_completion_token_logprob.py @@ -3,12 +3,36 @@ from typing import List, Optional from ..._models import BaseModel -from .top_logprob import TopLogprob -__all__ = ["ChatCompletionTokenLogprob"] +__all__ = ["ChatCompletionTokenLogprob", "TopLogprob"] + + +class TopLogprob(BaseModel): + """Token and its log probability.""" + + token: str + """The token.""" + + bytes: Optional[List[int]] = None + """A list of integers representing the UTF-8 bytes representation of the token. + + Useful in instances where characters are represented by multiple tokens and + their byte representations must be combined to generate the correct text + representation. Can be `null` if there is no bytes representation for the token. + """ + + logprob: float + """The log probability of this token, if it is within the top 20 most likely + tokens. + + Otherwise, the value `-9999.0` is used to signify that the token is very + unlikely. + """ class ChatCompletionTokenLogprob(BaseModel): + """Token log probability information.""" + token: str """The token.""" diff --git a/src/dedalus_labs/types/chat/chat_completion_tool_message_param.py b/src/dedalus_labs/types/chat/chat_completion_tool_message_param.py new file mode 100644 index 0000000..8b7f717 --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_tool_message_param.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypedDict + +from .chat_completion_content_part_text_param import ChatCompletionContentPartTextParam + +__all__ = ["ChatCompletionToolMessageParam"] + + +class ChatCompletionToolMessageParam(TypedDict, total=False): + """Schema for ChatCompletionRequestToolMessage. + + Fields: + - role (required): Literal["tool"] + - content (required): str | Annotated[list[ChatCompletionRequestToolMessageContentPart], MinLen(1), ArrayTitle("ChatCompletionRequestToolMessageContentArray")] + - tool_call_id (required): str + """ + + content: Required[Union[str, Iterable[ChatCompletionContentPartTextParam]]] + """The contents of the tool message.""" + + role: Required[Literal["tool"]] + """The role of the messages author, in this case `tool`.""" + + tool_call_id: Required[str] + """Tool call that this message is responding to.""" diff --git a/src/dedalus_labs/types/chat/chat_completion_tool_param.py b/src/dedalus_labs/types/chat/chat_completion_tool_param.py new file mode 100644 index 0000000..da10c13 --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_tool_param.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ChatCompletionToolParam"] + + +class ChatCompletionToolParam(TypedDict, total=False): + """A function tool that can be used to generate a response. + + Fields: + - type (required): Literal["function"] + - function (required): FunctionObject + """ + + function: Required["FunctionDefinition"] + """Schema for FunctionObject. + + Fields: + + - description (optional): str + - name (required): str + - parameters (optional): FunctionParameters + - strict (optional): bool | None + """ + + type: Required[Literal["function"]] + """The type of the tool. Currently, only `function` is supported.""" + + +from ..shared_params.function_definition import FunctionDefinition diff --git a/src/dedalus_labs/types/chat/chat_completion_user_message_param.py b/src/dedalus_labs/types/chat/chat_completion_user_message_param.py new file mode 100644 index 0000000..d50dc21 --- /dev/null +++ b/src/dedalus_labs/types/chat/chat_completion_user_message_param.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +from .chat_completion_content_part_file_param import ChatCompletionContentPartFileParam +from .chat_completion_content_part_text_param import ChatCompletionContentPartTextParam +from .chat_completion_content_part_image_param import ChatCompletionContentPartImageParam +from .chat_completion_content_part_input_audio_param import ChatCompletionContentPartInputAudioParam + +__all__ = ["ChatCompletionUserMessageParam", "ContentChatCompletionRequestUserMessageContentArray"] + +ContentChatCompletionRequestUserMessageContentArray: TypeAlias = Union[ + ChatCompletionContentPartTextParam, + ChatCompletionContentPartImageParam, + ChatCompletionContentPartInputAudioParam, + ChatCompletionContentPartFileParam, +] + + +class ChatCompletionUserMessageParam(TypedDict, total=False): + """ + Messages sent by an end user, containing prompts or additional context + information. + + Fields: + - content (required): str | Annotated[list[ChatCompletionRequestUserMessageContentPart], MinLen(1), ArrayTitle("ChatCompletionRequestUserMessageContentArray")] + - role (required): Literal["user"] + - name (optional): str + """ + + content: Required[Union[str, Iterable[ContentChatCompletionRequestUserMessageContentArray]]] + """The contents of the user message.""" + + role: Required[Literal["user"]] + """The role of the messages author, in this case `user`.""" + + name: str + """An optional name for the participant. + + Provides the model information to differentiate between participants of the same + role. + """ diff --git a/src/dedalus_labs/types/chat/choice.py b/src/dedalus_labs/types/chat/choice.py new file mode 100644 index 0000000..f8d1efe --- /dev/null +++ b/src/dedalus_labs/types/chat/choice.py @@ -0,0 +1,37 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .choice_logprobs import ChoiceLogprobs +from .chat_completion_message import ChatCompletionMessage + +__all__ = ["Choice"] + + +class Choice(BaseModel): + """A chat completion choice. + + OpenAI-compatible choice object for non-streaming responses. + Part of the ChatCompletion response. + """ + + index: int + """The index of the choice in the list of choices.""" + + message: ChatCompletionMessage + """A chat completion message generated by the model.""" + + finish_reason: Optional[Literal["stop", "length", "tool_calls", "content_filter", "function_call"]] = None + """The reason the model stopped generating tokens. + + This will be `stop` if the model hit a natural stop point or a provided stop + sequence, `length` if the maximum number of tokens specified in the request was + reached, `content_filter` if content was omitted due to a flag from our content + filters, `tool_calls` if the model called a tool, or `function_call` + (deprecated) if the model called a function. + """ + + logprobs: Optional[ChoiceLogprobs] = None + """Log probability information for the choice.""" diff --git a/src/dedalus_labs/types/chat/choice_delta.py b/src/dedalus_labs/types/chat/choice_delta.py new file mode 100644 index 0000000..fe6a2f4 --- /dev/null +++ b/src/dedalus_labs/types/chat/choice_delta.py @@ -0,0 +1,66 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .choice_delta_tool_call import ChoiceDeltaToolCall + +__all__ = ["ChoiceDelta", "FunctionCall"] + + +class FunctionCall(BaseModel): + """Deprecated and replaced by `tool_calls`. + + The name and arguments of a function that should be called, as generated by the model. + + Fields: + - arguments (optional): str + - name (optional): str + """ + + arguments: Optional[str] = None + """ + The arguments to call the function with, as generated by the model in JSON + format. Note that the model does not always generate valid JSON, and may + hallucinate parameters not defined by your function schema. Validate the + arguments in your code before calling your function. + """ + + name: Optional[str] = None + """The name of the function to call.""" + + +class ChoiceDelta(BaseModel): + """A chat completion delta generated by streamed model responses. + + Fields: + - content (optional): str | None + - function_call (optional): ChatCompletionStreamResponseDeltaFunctionCall + - tool_calls (optional): list[ChatCompletionMessageToolCallChunk] + - role (optional): Literal["developer", "system", "user", "assistant", "tool"] + - refusal (optional): str | None + """ + + content: Optional[str] = None + """The contents of the chunk message.""" + + function_call: Optional[FunctionCall] = None + """Deprecated and replaced by `tool_calls`. + + The name and arguments of a function that should be called, as generated by the + model. + + Fields: + + - arguments (optional): str + - name (optional): str + """ + + refusal: Optional[str] = None + """The refusal message generated by the model.""" + + role: Optional[Literal["developer", "system", "user", "assistant", "tool"]] = None + """The role of the author of this message.""" + + tool_calls: Optional[List[ChoiceDeltaToolCall]] = None diff --git a/src/dedalus_labs/types/chat/choice_delta_tool_call.py b/src/dedalus_labs/types/chat/choice_delta_tool_call.py new file mode 100644 index 0000000..4df8a5f --- /dev/null +++ b/src/dedalus_labs/types/chat/choice_delta_tool_call.py @@ -0,0 +1,56 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ChoiceDeltaToolCall", "Function"] + + +class Function(BaseModel): + """Schema for ChatCompletionMessageToolCallChunkFunction. + + Fields: + - name (optional): str + - arguments (optional): str + """ + + arguments: Optional[str] = None + """ + The arguments to call the function with, as generated by the model in JSON + format. Note that the model does not always generate valid JSON, and may + hallucinate parameters not defined by your function schema. Validate the + arguments in your code before calling your function. + """ + + name: Optional[str] = None + """The name of the function to call.""" + + +class ChoiceDeltaToolCall(BaseModel): + """Schema for ChatCompletionMessageToolCallChunk. + + Fields: + - index (required): int + - id (optional): str + - type (optional): Literal["function"] + - function (optional): ChatCompletionMessageToolCallChunkFunction + """ + + index: int + + id: Optional[str] = None + """The ID of the tool call.""" + + function: Optional[Function] = None + """Schema for ChatCompletionMessageToolCallChunkFunction. + + Fields: + + - name (optional): str + - arguments (optional): str + """ + + type: Optional[Literal["function"]] = None + """The type of the tool. Currently, only `function` is supported.""" diff --git a/src/dedalus_labs/types/chat/choice_logprobs.py b/src/dedalus_labs/types/chat/choice_logprobs.py new file mode 100644 index 0000000..18f4520 --- /dev/null +++ b/src/dedalus_labs/types/chat/choice_logprobs.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from ..._models import BaseModel +from .chat_completion_token_logprob import ChatCompletionTokenLogprob + +__all__ = ["ChoiceLogprobs"] + + +class ChoiceLogprobs(BaseModel): + """Log probability information for the choice.""" + + content: Optional[List[ChatCompletionTokenLogprob]] = None + """A list of message content tokens with log probability information.""" + + refusal: Optional[List[ChatCompletionTokenLogprob]] = None + """A list of message refusal tokens with log probability information.""" diff --git a/src/dedalus_labs/types/chat/completion.py b/src/dedalus_labs/types/chat/completion.py deleted file mode 100644 index 32d756b..0000000 --- a/src/dedalus_labs/types/chat/completion.py +++ /dev/null @@ -1,312 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import builtins -from typing import Dict, List, Union, Optional -from typing_extensions import Literal, Annotated, TypeAlias - -from ..._utils import PropertyInfo -from ..._models import BaseModel -from .chat_completion_token_logprob import ChatCompletionTokenLogprob - -__all__ = [ - "Completion", - "Choice", - "ChoiceMessage", - "ChoiceMessageAnnotation", - "ChoiceMessageAnnotationURLCitation", - "ChoiceMessageAudio", - "ChoiceMessageFunctionCall", - "ChoiceMessageToolCall", - "ChoiceMessageToolCallChatCompletionMessageToolCall", - "ChoiceMessageToolCallChatCompletionMessageToolCallFunction", - "ChoiceMessageToolCallChatCompletionMessageCustomToolCall", - "ChoiceMessageToolCallChatCompletionMessageCustomToolCallCustom", - "ChoiceLogprobs", - "Usage", - "UsageCompletionTokensDetails", - "UsagePromptTokensDetails", -] - - -class ChoiceMessageAnnotationURLCitation(BaseModel): - end_index: int - """The index of the last character of the URL citation in the message.""" - - start_index: int - """The index of the first character of the URL citation in the message.""" - - title: str - """The title of the web resource.""" - - url: str - """The URL of the web resource.""" - - -class ChoiceMessageAnnotation(BaseModel): - type: Literal["url_citation"] - """The type of the URL citation. Always `url_citation`.""" - - url_citation: ChoiceMessageAnnotationURLCitation - """A URL citation when using web search.""" - - -class ChoiceMessageAudio(BaseModel): - id: str - """Unique identifier for this audio response.""" - - data: str - """ - Base64 encoded audio bytes generated by the model, in the format specified in - the request. - """ - - expires_at: int - """ - The Unix timestamp (in seconds) for when this audio response will no longer be - accessible on the server for use in multi-turn conversations. - """ - - transcript: str - """Transcript of the audio generated by the model.""" - - -class ChoiceMessageFunctionCall(BaseModel): - arguments: str - """ - The arguments to call the function with, as generated by the model in JSON - format. Note that the model does not always generate valid JSON, and may - hallucinate parameters not defined by your function schema. Validate the - arguments in your code before calling your function. - """ - - name: str - """The name of the function to call.""" - - -class ChoiceMessageToolCallChatCompletionMessageToolCallFunction(BaseModel): - arguments: str - """ - The arguments to call the function with, as generated by the model in JSON - format. Note that the model does not always generate valid JSON, and may - hallucinate parameters not defined by your function schema. Validate the - arguments in your code before calling your function. - """ - - name: str - """The name of the function to call.""" - - -class ChoiceMessageToolCallChatCompletionMessageToolCall(BaseModel): - id: str - """The ID of the tool call.""" - - function: ChoiceMessageToolCallChatCompletionMessageToolCallFunction - """The function that the model called.""" - - type: Literal["function"] - """The type of the tool. Currently, only `function` is supported.""" - - -class ChoiceMessageToolCallChatCompletionMessageCustomToolCallCustom(BaseModel): - input: str - """The input for the custom tool call generated by the model.""" - - name: str - """The name of the custom tool to call.""" - - -class ChoiceMessageToolCallChatCompletionMessageCustomToolCall(BaseModel): - id: str - """The ID of the tool call.""" - - custom: ChoiceMessageToolCallChatCompletionMessageCustomToolCallCustom - """The custom tool that the model called.""" - - type: Literal["custom"] - """The type of the tool. Always `custom`.""" - - -ChoiceMessageToolCall: TypeAlias = Annotated[ - Union[ChoiceMessageToolCallChatCompletionMessageToolCall, ChoiceMessageToolCallChatCompletionMessageCustomToolCall], - PropertyInfo(discriminator="type"), -] - - -class ChoiceMessage(BaseModel): - content: Union[str, List[Dict[str, object]], None] = None - """The contents of the message.""" - - refusal: Optional[str] = None - """The refusal message generated by the model.""" - - role: Literal["assistant"] - """The role of the author of this message.""" - - annotations: Optional[List[ChoiceMessageAnnotation]] = None - """ - Annotations for the message, when applicable, as when using the - [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). - """ - - audio: Optional[ChoiceMessageAudio] = None - """If the audio output modality is requested, this object contains data - - about the audio response from the model. - [Learn more](https://platform.openai.com/docs/guides/audio). - - Fields: - - - id (required): str - - expires_at (required): int - - data (required): str - - transcript (required): str - """ - - function_call: Optional[ChoiceMessageFunctionCall] = None - """Deprecated and replaced by `tool_calls`. - - The name and arguments of a function that should be called, as generated by the - model. - """ - - tool_calls: Optional[List[ChoiceMessageToolCall]] = None - """The tool calls generated by the model, such as function calls.""" - - -class ChoiceLogprobs(BaseModel): - content: Optional[List[ChatCompletionTokenLogprob]] = None - """A list of message content tokens with log probability information.""" - - refusal: Optional[List[ChatCompletionTokenLogprob]] = None - """A list of message refusal tokens with log probability information.""" - - -class Choice(BaseModel): - index: int - """The index of the choice in the list of choices.""" - - message: ChoiceMessage - """A chat completion message generated by the model.""" - - finish_reason: Optional[Literal["stop", "length", "tool_calls", "content_filter", "function_call"]] = None - """The reason the model stopped generating tokens. - - This will be `stop` if the model hit a natural stop point or a provided stop - sequence, `length` if the maximum number of tokens specified in the request was - reached, `content_filter` if content was omitted due to a flag from our content - filters, `tool_calls` if the model called a tool, or `function_call` - (deprecated) if the model called a function. - """ - - logprobs: Optional[ChoiceLogprobs] = None - """Log probability information for the choice.""" - - -class UsageCompletionTokensDetails(BaseModel): - accepted_prediction_tokens: Optional[int] = None - """ - When using Predicted Outputs, the number of tokens in the prediction that - appeared in the completion. - """ - - audio_tokens: Optional[int] = None - """Audio input tokens generated by the model.""" - - reasoning_tokens: Optional[int] = None - """Tokens generated by the model for reasoning.""" - - rejected_prediction_tokens: Optional[int] = None - """ - When using Predicted Outputs, the number of tokens in the prediction that did - not appear in the completion. However, like reasoning tokens, these tokens are - still counted in the total completion tokens for purposes of billing, output, - and context window limits. - """ - - -class UsagePromptTokensDetails(BaseModel): - audio_tokens: Optional[int] = None - """Audio input tokens present in the prompt.""" - - cached_tokens: Optional[int] = None - """Cached tokens present in the prompt.""" - - -class Usage(BaseModel): - completion_tokens: int - """Number of tokens in the generated completion.""" - - prompt_tokens: int - """Number of tokens in the prompt.""" - - total_tokens: int - """Total number of tokens used in the request (prompt + completion).""" - - completion_tokens_details: Optional[UsageCompletionTokensDetails] = None - """Breakdown of tokens used in a completion.""" - - prompt_tokens_details: Optional[UsagePromptTokensDetails] = None - """Breakdown of tokens used in the prompt.""" - - -class Completion(BaseModel): - id: str - """A unique identifier for the chat completion.""" - - choices: List[Choice] - """A list of chat completion choices. - - Can be more than one if `n` is greater than 1. - """ - - created: int - """The Unix timestamp (in seconds) of when the chat completion was created.""" - - model: str - """The model used for the chat completion.""" - - object: Literal["chat.completion"] - """The object type, which is always `chat.completion`.""" - - mcp_server_errors: Optional[Dict[str, builtins.object]] = None - """Information about MCP server failures, if any occurred during the request. - - Contains details about which servers failed and why, along with recommendations - for the user. Only present when MCP server failures occurred. - """ - - service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] = None - """Specifies the processing type used for serving the request. - - - If set to 'auto', then the request will be processed with the service tier - configured in the Project settings. Unless otherwise configured, the Project - will use 'default'. - - If set to 'default', then the request will be processed with the standard - pricing and performance for the selected model. - - If set to '[flex](https://platform.openai.com/docs/guides/flex-processing)' or - '[priority](https://openai.com/api-priority-processing/)', then the request - will be processed with the corresponding service tier. - - When not set, the default behavior is 'auto'. - - When the `service_tier` parameter is set, the response body will include the - `service_tier` value based on the processing mode actually used to serve the - request. This response value may be different from the value set in the - parameter. - """ - - system_fingerprint: Optional[str] = None - """This fingerprint represents the backend configuration that the model runs with. - - Can be used in conjunction with the `seed` request parameter to understand when - backend changes have been made that might impact determinism. - """ - - tools_executed: Optional[List[str]] = None - """List of tool names that were executed server-side (e.g., MCP tools). - - Only present when tools were executed on the server rather than returned for - client-side execution. - """ - - usage: Optional[Usage] = None - """Usage statistics for the completion request.""" diff --git a/src/dedalus_labs/types/chat/completion_create_params.py b/src/dedalus_labs/types/chat/completion_create_params.py index 02aac8d..91eaec2 100644 --- a/src/dedalus_labs/types/chat/completion_create_params.py +++ b/src/dedalus_labs/types/chat/completion_create_params.py @@ -6,20 +6,44 @@ from typing_extensions import Literal, Required, TypeAlias, TypedDict from ..._types import SequenceNotStr -from .model_id import ModelID -from .models_param import ModelsParam -from ..shared_params.dedalus_model import DedalusModel +from ..shared_params import mcp_servers as _mcp_servers +from .tool_choice_any_param import ToolChoiceAnyParam +from .tool_choice_auto_param import ToolChoiceAutoParam +from .tool_choice_none_param import ToolChoiceNoneParam +from .tool_choice_tool_param import ToolChoiceToolParam +from .prediction_content_param import PredictionContentParam +from ..shared_params.credential import Credential +from .chat_completion_audio_param import ChatCompletionAudioParam +from .thinking_config_enabled_param import ThinkingConfigEnabledParam +from ..shared_params.mcp_credentials import MCPCredentials +from ..shared_params.mcp_server_spec import MCPServerSpec +from .thinking_config_disabled_param import ThinkingConfigDisabledParam +from .chat_completion_tool_message_param import ChatCompletionToolMessageParam +from .chat_completion_user_message_param import ChatCompletionUserMessageParam from ..shared_params.response_format_text import ResponseFormatText +from .chat_completion_system_message_param import ChatCompletionSystemMessageParam +from .chat_completion_function_message_param import ChatCompletionFunctionMessageParam +from .chat_completion_assistant_message_param import ChatCompletionAssistantMessageParam +from .chat_completion_developer_message_param import ChatCompletionDeveloperMessageParam from ..shared_params.response_format_json_object import ResponseFormatJSONObject -from ..shared_params.response_format_json_schema import ResponseFormatJSONSchema __all__ = [ "CompletionCreateParamsBase", "Model", + "Credentials", + "MCPServers", + "Message", "ResponseFormat", + "SafetySetting", "Thinking", - "ThinkingThinkingConfigDisabled", - "ThinkingThinkingConfigEnabled", + "ToolChoice", + "Tool", + "ToolCustomToolChatCompletions", + "ToolCustomToolChatCompletionsCustom", + "ToolCustomToolChatCompletionsCustomFormat", + "ToolCustomToolChatCompletionsCustomFormatTextFormat", + "ToolCustomToolChatCompletionsCustomFormatGrammarFormat", + "ToolCustomToolChatCompletionsCustomFormatGrammarFormatGrammar", "CompletionCreateParamsNonStreaming", "CompletionCreateParamsStreaming", ] @@ -27,47 +51,52 @@ class CompletionCreateParamsBase(TypedDict, total=False): model: Required[Model] - """Model(s) to use for completion. + """Model identifier. - Can be a single model ID, a DedalusModel object, or a list for multi-model - routing. Single model: 'openai/gpt-4', 'anthropic/claude-3-5-sonnet-20241022', - 'openai/gpt-4o-mini', or a DedalusModel instance. Multi-model routing: - ['openai/gpt-4o-mini', 'openai/gpt-4', 'anthropic/claude-3-5-sonnet'] or list of - DedalusModel objects - agent will choose optimal model based on task complexity. + Accepts model ID strings, lists for routing, or DedalusModel objects with + per-model settings. """ agent_attributes: Optional[Dict[str, float]] - """Attributes for the agent itself, influencing behavior and model selection. - - Format: {'attribute': value}, where values are 0.0-1.0. Common attributes: - 'complexity', 'accuracy', 'efficiency', 'creativity', 'friendliness'. Higher - values indicate stronger preference for that characteristic. - """ + """Agent attributes. Values in [0.0, 1.0].""" - audio: Optional[Dict[str, object]] + audio: Optional[ChatCompletionAudioParam] """Parameters for audio output. - Required when requesting audio responses (for example, modalities including - 'audio'). + Required when audio output is requested with `modalities: ["audio"]`. + [Learn more](https://platform.openai.com/docs/guides/audio). + + Fields: + + - voice (required): VoiceIdsShared + - format (required): Literal["wav", "aac", "mp3", "flac", "opus", "pcm16"] """ - auto_execute_tools: bool + automatic_tool_execution: bool + """Execute tools server-side. + + If false, returns raw tool calls for manual handling. """ - When False, skip server-side tool execution and return raw OpenAI-style - tool_calls in the response. + + cached_content: Optional[str] + """Optional. + + The name of the content [cached](https://ai.google.dev/gemini-api/docs/caching) + to use as context to serve the prediction. Format: + `cachedContents/{cachedContent}` """ - deferred: Optional[bool] - """xAI-specific parameter. + credentials: Optional[Credentials] + """Credentials for MCP server authentication. - If set to true, the request returns a request_id for async completion retrieval - via GET /v1/chat/deferred-completion/{request_id}. + Each credential is matched to servers by connection name. """ - disable_automatic_function_calling: Optional[bool] - """Google-only flag to disable the SDK's automatic function execution. + deferred: Optional[bool] + """If set to `true`, the request returns a `request_id`. - When true, the model returns function calls for the client to execute manually. + You can then get the deferred response by GET + `/v1/chat/deferred-completion/{request_id}`. """ frequency_penalty: Optional[float] @@ -77,134 +106,107 @@ class CompletionCreateParamsBase(TypedDict, total=False): text so far, decreasing the model's likelihood to repeat the same line verbatim. """ - function_call: Union[str, Dict[str, object], None] - """Deprecated in favor of 'tool_choice'. - - Controls which function is called by the model (none, auto, or specific name). - """ + function_call: Optional[str] + """Wrapper for union variant: function call mode.""" - functions: Optional[Iterable[Dict[str, object]]] - """Deprecated in favor of 'tools'. + functions: Optional[Iterable["ChatCompletionFunctionsParam"]] + """Deprecated in favor of `tools`. - Legacy list of function definitions the model may generate JSON inputs for. + A list of functions the model may generate JSON inputs for. """ - generation_config: Optional[Dict[str, object]] - """Google generationConfig object. - - Merged with auto-generated config. Use for Google-specific params - (candidateCount, responseMimeType, etc.). - """ + generation_config: Optional["JSONObjectInput"] + """Generation parameters wrapper (Google-specific)""" guardrails: Optional[Iterable[Dict[str, object]]] - """Guardrails to apply to the agent for input/output validation and safety checks. - - Reserved for future use - guardrails configuration format not yet finalized. - """ + """Content filtering and safety policy configuration.""" handoff_config: Optional[Dict[str, object]] - """Configuration for multi-model handoffs and agent orchestration. - - Reserved for future use - handoff configuration format not yet finalized. - """ - - input: Union[Iterable[Dict[str, object]], str, None] - """Convenience alias for Responses-style `input`. - - Used when `messages` is omitted to provide the user prompt directly. - """ - - instructions: Union[str, Iterable[Dict[str, object]], None] - """Convenience alias for Responses-style `instructions`. - - Takes precedence over `system` and over system-role messages when provided. - """ + """Configuration for multi-model handoffs.""" logit_bias: Optional[Dict[str, int]] """Modify the likelihood of specified tokens appearing in the completion. - Accepts a JSON object mapping token IDs (as strings) to bias values from -100 - to 100. The bias is added to the logits before sampling; values between -1 and 1 - nudge selection probability, while values like -100 or 100 effectively ban or - require a token. + Accepts a JSON object that maps tokens (specified by their token ID in the + tokenizer) to an associated bias value from -100 to 100. Mathematically, the + bias is added to the logits generated by the model prior to sampling. The exact + effect will vary per model, but values between -1 and 1 should decrease or + increase likelihood of selection; values like -100 or 100 should result in a ban + or exclusive selection of the relevant token. """ logprobs: Optional[bool] - """Whether to return log probabilities of the output tokens. + """Whether to return log probabilities of the output tokens or not. - If true, returns the log probabilities for each token in the response content. + If true, returns the log probabilities of each output token returned in the + `content` of `message`. """ max_completion_tokens: Optional[int] - """ - An upper bound for the number of tokens that can be generated for a completion, - including visible output and reasoning tokens. - """ + """Maximum tokens in completion (newer parameter name)""" max_tokens: Optional[int] - """The maximum number of tokens that can be generated in the chat completion. - - This value can be used to control costs for text generated via API. This value - is now deprecated in favor of 'max_completion_tokens' and is not compatible with - o-series models. - """ + """Maximum tokens in completion""" max_turns: Optional[int] - """Maximum number of turns for agent execution before terminating (default: 10). + """Maximum conversation turns.""" - Each turn represents one model inference cycle. Higher values allow more complex - reasoning but increase cost and latency. - """ + mcp_servers: Optional[MCPServers] + """MCP server identifiers. - mcp_servers: Union[str, SequenceNotStr[str], None] - """ - MCP (Model Context Protocol) server addresses to make available for server-side - tool execution. Entries can be URLs (e.g., 'https://mcp.example.com'), slugs - (e.g., 'dedalus-labs/brave-search'), or structured objects specifying - slug/version/url. MCP tools are executed server-side and billed separately. + Accepts marketplace slugs, URLs, or MCPServerSpec objects. MCP tools are + executed server-side and billed separately. """ - messages: Union[Iterable[Dict[str, object]], str, None] - """Conversation history. + messages: Optional[Iterable[Message]] + """Conversation history (OpenAI: messages, Google: contents, Responses: input)""" - Accepts either a list of message objects or a string, which is treated as a - single user message. Optional if `input` or `instructions` is provided. - """ + metadata: Optional["JSONObjectInput"] + """Set of 16 key-value pairs that can be attached to an object. - metadata: Optional[Dict[str, str]] - """ - Set of up to 16 key-value string pairs that can be attached to the request for - structured metadata. + This can be useful for storing additional information about the object in a + structured format, and querying for objects via API or the dashboard. Keys are + strings with a maximum length of 64 characters. Values are strings with a + maximum length of 512 characters. """ modalities: Optional[SequenceNotStr[str]] - """Output types you would like the model to generate. + """Output types that you would like the model to generate. - Most models default to ['text']; some support ['text', 'audio']. + Most models are capable of generating text, which is the default: `["text"]` The + `gpt-4o-audio-preview` model can also be used to + [generate audio](https://platform.openai.com/docs/guides/audio). To request that + this model generate both text and audio responses, you can use: + `["text", "audio"]` """ model_attributes: Optional[Dict[str, Dict[str, float]]] - """ - Attributes for individual models used in routing decisions during multi-model - execution. Format: {'model_name': {'attribute': value}}, where values are - 0.0-1.0. Common attributes: 'intelligence', 'speed', 'cost', 'creativity', - 'accuracy'. Used by agent to select optimal model based on task requirements. + """Model attributes for routing. + + Maps model IDs to attribute dictionaries with values in [0.0, 1.0]. """ n: Optional[int] """How many chat completion choices to generate for each input message. - Keep 'n' as 1 to minimize costs. + Note that you will be charged based on the number of generated tokens across all + of the choices. Keep `n` as `1` to minimize costs. """ parallel_tool_calls: Optional[bool] - """Whether to enable parallel function calling during tool use.""" + """Whether to enable parallel tool calls (Anthropic uses inverted polarity)""" + + prediction: Optional[PredictionContentParam] + """ + Static predicted output content, such as the content of a text file that is + being regenerated. - prediction: Optional[Dict[str, object]] - """Configuration for predicted outputs. + Fields: - Improves response times when you already know large portions of the response - content. + - type (required): Literal["content"] + - content (required): str | + Annotated[list[ChatCompletionRequestMessageContentPartText], MinLen(1), + ArrayTitle("PredictionContentArray")] """ presence_penalty: Optional[float] @@ -216,198 +218,319 @@ class CompletionCreateParamsBase(TypedDict, total=False): prompt_cache_key: Optional[str] """ - Used by OpenAI to cache responses for similar requests and optimize cache hit - rates. Replaces the legacy 'user' field for caching. + Used by OpenAI to cache responses for similar requests to optimize your cache + hit rates. Replaces the `user` field. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching). """ - reasoning_effort: Optional[Literal["low", "medium", "high"]] - """Constrains effort on reasoning for supported reasoning models. + prompt_cache_retention: Optional[str] + """The retention policy for the prompt cache. - Higher values use more compute, potentially improving reasoning quality at the - cost of latency and tokens. + Set to `24h` to enable extended prompt caching, which keeps cached prefixes + active for longer, up to a maximum of 24 hours. + [Learn more](https://platform.openai.com/docs/guides/prompt-caching#prompt-cache-retention). + """ + + prompt_mode: Optional[Literal["reasoning"]] + """Allows toggling between the reasoning mode and no system prompt. + + When set to `reasoning` the system prompt for reasoning models will be used. + """ + + reasoning_effort: Optional[str] + """ + Constrains effort on reasoning for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). Currently + supported values are `none`, `minimal`, `low`, `medium`, and `high`. Reducing + reasoning effort can result in faster responses and fewer tokens used on + reasoning in a response. - `gpt-5.1` defaults to `none`, which does not perform + reasoning. The supported reasoning values for `gpt-5.1` are `none`, `low`, + `medium`, and `high`. Tool calls are supported for all reasoning values in + gpt-5.1. - All models before `gpt-5.1` default to `medium` reasoning effort, and + do not support `none`. - The `gpt-5-pro` model defaults to (and only supports) + `high` reasoning effort. """ response_format: Optional[ResponseFormat] """An object specifying the format that the model must output. - Use {'type': 'json_schema', 'json_schema': {...}} for structured outputs or - {'type': 'json_object'} for the legacy JSON mode. Currently only OpenAI-prefixed - models honour this field; Anthropic and Google requests will return an - invalid_request_error if it is supplied. + Setting to `{ "type": "json_schema", "json_schema": {...} }` enables Structured + Outputs which ensures the model will match your supplied JSON schema. Learn more + in the + [Structured Outputs guide](https://platform.openai.com/docs/guides/structured-outputs). + Setting to `{ "type": "json_object" }` enables the older JSON mode, which + ensures the message the model generates is valid JSON. Using `json_schema` is + preferred for models that support it. """ + safe_prompt: Optional[bool] + """Whether to inject a safety prompt before all conversations.""" + safety_identifier: Optional[str] """ - Stable identifier used to help detect users who might violate OpenAI usage - policies. Consider hashing end-user identifiers before sending. + A stable identifier used to help detect users of your application that may be + violating OpenAI's usage policies. The IDs should be a string that uniquely + identifies each user. We recommend hashing their username or email address, in + order to avoid sending us any identifying information. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). """ - safety_settings: Optional[Iterable[Dict[str, object]]] - """Google safety settings (harm categories and thresholds).""" + safety_settings: Optional[Iterable[SafetySetting]] + """Safety/content filtering settings (Google-specific)""" - search_parameters: Optional[Dict[str, object]] - """xAI-specific parameter for configuring web search data acquisition. + search_parameters: Optional["JSONObjectInput"] + """Set the parameters to be used for searched data. If not set, no data will be acquired by the model. """ seed: Optional[int] - """If specified, system will make a best effort to sample deterministically. + """Random seed for deterministic output""" - Determinism is not guaranteed for the same seed across different models or API - versions. - """ + service_tier: Optional[str] + """Service tier for request processing""" - service_tier: Optional[Literal["auto", "default"]] - """Specifies the processing tier used for the request. + stop: Union[SequenceNotStr[str], str, None] + """Sequences that stop generation""" - 'auto' uses project defaults, while 'default' forces standard pricing and - performance. + store: Optional[bool] + """ + Whether or not to store the output of this chat completion request for use in + our [model distillation](https://platform.openai.com/docs/guides/distillation) + or [evals](https://platform.openai.com/docs/guides/evals) products. Supports + text and image inputs. Note: image inputs over 8MB will be dropped. """ - stop: Optional[SequenceNotStr[str]] - """Not supported with latest reasoning models 'o3' and 'o4-mini'. + stream_options: Optional["JSONObjectInput"] + """Options for streaming response. Only set this when you set `stream: true`.""" + + system_instruction: Union["JSONObjectInput", str, None] + """System instruction/prompt""" + + temperature: Optional[float] + """Sampling temperature (0-2 for most providers)""" + + thinking: Optional[Thinking] + """Extended thinking configuration (Anthropic-specific)""" + + tool_choice: Optional[ToolChoice] + """Controls which (if any) tool is called by the model. - Up to 4 sequences where the API will stop generating further tokens; the returned text will not contain the stop sequence. + `none` means the model will not call any tool and instead generates a message. + `auto` means the model can pick between generating a message or calling one or + more tools. `required` means the model must call one or more tools. Specifying a + particular tool via `{"type": "function", "function": {"name": "my_function"}}` + forces the model to call that tool. `none` is the default when no tools are + present. `auto` is the default if tools are present. """ - store: Optional[bool] + tool_config: Optional["JSONObjectInput"] + """Tool calling configuration (Google-specific)""" + + tools: Optional[Iterable[Tool]] + """Available tools/functions for the model""" + + top_k: Optional[int] + """Top-k sampling parameter""" + + top_logprobs: Optional[int] """ - Whether to store the output of this chat completion request for OpenAI model - distillation or eval products. Image inputs over 8MB are dropped if storage is - enabled. + An integer between 0 and 20 specifying the number of most likely tokens to + return at each token position, each with an associated log probability. + `logprobs` must be set to `true` if this parameter is used. """ - stream_options: Optional[Dict[str, object]] - """Options for streaming responses. + top_p: Optional[float] + """Nucleus sampling threshold""" + + user: Optional[str] + """This field is being replaced by `safety_identifier` and `prompt_cache_key`. - Only set when 'stream' is true (supports 'include_usage' and - 'include_obfuscation'). + Use `prompt_cache_key` instead to maintain caching optimizations. A stable + identifier for your end-users. Used to boost cache hit rates by better bucketing + similar requests and to help OpenAI detect and prevent abuse. + [Learn more](https://platform.openai.com/docs/guides/safety-best-practices#safety-identifiers). """ - system: Union[str, Iterable[Dict[str, object]], None] - """System prompt/instructions. + verbosity: Optional[str] + """Constrains the verbosity of the model's response. - Anthropic: pass-through. Google: converted to systemInstruction. OpenAI: - extracted from messages. + Lower values will result in more concise responses, while higher values will + result in more verbose responses. Currently supported values are `low`, + `medium`, and `high`. """ - temperature: Optional[float] - """What sampling temperature to use, between 0 and 2. + web_search_options: Optional["JSONObjectInput"] + """This tool searches the web for relevant results to use in a response. - Higher values like 0.8 make the output more random, while lower values like 0.2 - make it more focused and deterministic. We generally recommend altering this or - 'top_p' but not both. + Learn more about the + [web search tool](https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat). """ - thinking: Optional[Thinking] - """Extended thinking configuration (Anthropic only). - Enables thinking blocks showing reasoning process. Requires min 1,024 token - budget. - """ +Model: TypeAlias = Union[str, "DedalusModel", SequenceNotStr["DedalusModelChoice"]] - tool_choice: Union[str, Dict[str, object], None] - """Controls which (if any) tool is called by the model. +Credentials: TypeAlias = Union[Credential, MCPCredentials] - 'none' stops tool calling, 'auto' lets the model decide, and 'required' forces - at least one tool invocation. Specific tool payloads force that tool. - """ +MCPServers: TypeAlias = Union[str, MCPServerSpec, _mcp_servers.MCPServers] - tool_config: Optional[Dict[str, object]] - """Google tool configuration (function calling mode, etc.).""" +Message: TypeAlias = Union[ + ChatCompletionDeveloperMessageParam, + ChatCompletionSystemMessageParam, + ChatCompletionUserMessageParam, + ChatCompletionAssistantMessageParam, + ChatCompletionToolMessageParam, + ChatCompletionFunctionMessageParam, +] - tools: Optional[Iterable[Dict[str, object]]] - """A list of tools the model may call. +ResponseFormat: TypeAlias = Union[ResponseFormatText, "ResponseFormatJSONSchema", ResponseFormatJSONObject] - Supports OpenAI function tools and custom tools; use 'mcp_servers' for - Dedalus-managed server-side tools. - """ - top_k: Optional[int] - """Top-k sampling. +class SafetySetting(TypedDict, total=False): + """Safety setting, affecting the safety-blocking behavior. - Anthropic: pass-through. Google: injected into generationConfig.topK. - """ + Passing a safety setting for a category changes the allowed probability that + content is blocked. - top_logprobs: Optional[int] - """ - An integer between 0 and 20 specifying how many of the most likely tokens to - return at each position, with log probabilities. Requires 'logprobs' to be true. + Fields: + - category (required): HarmCategory + - threshold (required): Literal["HARM_BLOCK_THRESHOLD_UNSPECIFIED", "BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE", "BLOCK_ONLY_HIGH", "BLOCK_NONE", "OFF"] """ - top_p: Optional[float] - """ - An alternative to sampling with temperature, called nucleus sampling, where the - model considers the results of the tokens with top_p probability mass. So 0.1 - means only the tokens comprising the top 10% probability mass are considered. We - generally recommend altering this or 'temperature' but not both. + category: Required[ + Literal[ + "HARM_CATEGORY_UNSPECIFIED", + "HARM_CATEGORY_DEROGATORY", + "HARM_CATEGORY_TOXICITY", + "HARM_CATEGORY_VIOLENCE", + "HARM_CATEGORY_SEXUAL", + "HARM_CATEGORY_MEDICAL", + "HARM_CATEGORY_DANGEROUS", + "HARM_CATEGORY_HARASSMENT", + "HARM_CATEGORY_HATE_SPEECH", + "HARM_CATEGORY_SEXUALLY_EXPLICIT", + "HARM_CATEGORY_DANGEROUS_CONTENT", + "HARM_CATEGORY_CIVIC_INTEGRITY", + ] + ] + """Required. The category for this setting.""" + + threshold: Required[ + Literal[ + "HARM_BLOCK_THRESHOLD_UNSPECIFIED", + "BLOCK_LOW_AND_ABOVE", + "BLOCK_MEDIUM_AND_ABOVE", + "BLOCK_ONLY_HIGH", + "BLOCK_NONE", + "OFF", + ] + ] + """Required. Controls the probability threshold at which harm is blocked.""" + + +Thinking: TypeAlias = Union[ThinkingConfigEnabledParam, ThinkingConfigDisabledParam] + +ToolChoice: TypeAlias = Union[ToolChoiceAutoParam, ToolChoiceAnyParam, ToolChoiceToolParam, ToolChoiceNoneParam] + + +class ToolCustomToolChatCompletionsCustomFormatTextFormat(TypedDict, total=False): + """Unconstrained free-form text. + + Fields: + - type (required): Literal["text"] """ - user: Optional[str] - """Stable identifier for your end-users. + type: Required[Literal["text"]] + """Unconstrained text format. Always `text`.""" + + +class ToolCustomToolChatCompletionsCustomFormatGrammarFormatGrammar(TypedDict, total=False): + """Your chosen grammar. - Helps OpenAI detect and prevent abuse and may boost cache hit rates. This field - is being replaced by 'safety_identifier' and 'prompt_cache_key'. + Fields: + - definition (required): str + - syntax (required): Literal["lark", "regex"] """ - verbosity: Optional[Literal["low", "medium", "high"]] - """Constrains the verbosity of the model's response. + definition: Required[str] + """The grammar definition.""" + + syntax: Required[Literal["lark", "regex"]] + """The syntax of the grammar definition. One of `lark` or `regex`.""" + - Lower values produce concise answers, higher values allow more detail. +class ToolCustomToolChatCompletionsCustomFormatGrammarFormat(TypedDict, total=False): + """A grammar defined by the user. + + Fields: + - type (required): Literal["grammar"] + - grammar (required): GrammarFormatGrammarFormat """ - web_search_options: Optional[Dict[str, object]] - """Configuration for OpenAI's web search tool. + grammar: Required[ToolCustomToolChatCompletionsCustomFormatGrammarFormatGrammar] + """Your chosen grammar. + + Fields: - Learn more at - https://platform.openai.com/docs/guides/tools-web-search?api-mode=chat. + - definition (required): str + - syntax (required): Literal["lark", "regex"] """ + type: Required[Literal["grammar"]] + """Grammar format. Always `grammar`.""" + -Model: TypeAlias = Union[ModelID, DedalusModel, ModelsParam] +ToolCustomToolChatCompletionsCustomFormat: TypeAlias = Union[ + ToolCustomToolChatCompletionsCustomFormatTextFormat, ToolCustomToolChatCompletionsCustomFormatGrammarFormat +] -ResponseFormat: TypeAlias = Union[ResponseFormatText, ResponseFormatJSONObject, ResponseFormatJSONSchema] +class ToolCustomToolChatCompletionsCustom(TypedDict, total=False): + """Properties of the custom tool.""" -class ThinkingThinkingConfigDisabled(TypedDict, total=False): - type: Required[Literal["disabled"]] + name: Required[str] + """The name of the custom tool, used to identify it in tool calls.""" + description: str + """Optional description of the custom tool, used to provide more context.""" -class ThinkingThinkingConfigEnabled(TypedDict, total=False): - budget_tokens: Required[int] - """Determines how many tokens Claude can use for its internal reasoning process. + format: ToolCustomToolChatCompletionsCustomFormat + """The input format for the custom tool. Default is unconstrained text.""" - Larger budgets can enable more thorough analysis for complex problems, improving - response quality. - Must be ≥1024 and less than `max_tokens`. +class ToolCustomToolChatCompletions(TypedDict, total=False): + """A custom tool that processes input using a specified format. - See - [extended thinking](https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking) - for details. + Fields: + - type (required): Literal["custom"] + - custom (required): CustomToolProperties """ - type: Required[Literal["enabled"]] + custom: Required[ToolCustomToolChatCompletionsCustom] + """Properties of the custom tool.""" + + type: Required[Literal["custom"]] + """The type of the custom tool. Always `custom`.""" -Thinking: TypeAlias = Union[ThinkingThinkingConfigDisabled, ThinkingThinkingConfigEnabled] +Tool: TypeAlias = Union["ChatCompletionToolParam", ToolCustomToolChatCompletions] class CompletionCreateParamsNonStreaming(CompletionCreateParamsBase, total=False): - stream: Literal[False] - """ - If true, the model response data is streamed to the client as it is generated - using Server-Sent Events. - """ + stream: Optional[Literal[False]] + """Enable streaming response""" class CompletionCreateParamsStreaming(CompletionCreateParamsBase): stream: Required[Literal[True]] - """ - If true, the model response data is streamed to the client as it is generated - using Server-Sent Events. - """ + """Enable streaming response""" CompletionCreateParams = Union[CompletionCreateParamsNonStreaming, CompletionCreateParamsStreaming] + +from .chat_completion_tool_param import ChatCompletionToolParam +from ..shared_params.dedalus_model import DedalusModel +from .chat_completion_functions_param import ChatCompletionFunctionsParam +from ..shared_params.json_object_input import JSONObjectInput +from ..shared_params.dedalus_model_choice import DedalusModelChoice +from ..shared_params.response_format_json_schema import ResponseFormatJSONSchema diff --git a/src/dedalus_labs/types/chat/completion_tokens_details.py b/src/dedalus_labs/types/chat/completion_tokens_details.py new file mode 100644 index 0000000..918c9b1 --- /dev/null +++ b/src/dedalus_labs/types/chat/completion_tokens_details.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["CompletionTokensDetails"] + + +class CompletionTokensDetails(BaseModel): + """Breakdown of tokens used in a completion. + + Fields: + - accepted_prediction_tokens (optional): int + - audio_tokens (optional): int + - reasoning_tokens (optional): int + - rejected_prediction_tokens (optional): int + """ + + accepted_prediction_tokens: Optional[int] = None + """ + When using Predicted Outputs, the number of tokens in the prediction that + appeared in the completion. + """ + + audio_tokens: Optional[int] = None + """Audio input tokens generated by the model.""" + + reasoning_tokens: Optional[int] = None + """Tokens generated by the model for reasoning.""" + + rejected_prediction_tokens: Optional[int] = None + """ + When using Predicted Outputs, the number of tokens in the prediction that did + not appear in the completion. However, like reasoning tokens, these tokens are + still counted in the total completion tokens for purposes of billing, output, + and context window limits. + """ diff --git a/src/dedalus_labs/types/chat/completion_usage.py b/src/dedalus_labs/types/chat/completion_usage.py new file mode 100644 index 0000000..6239a58 --- /dev/null +++ b/src/dedalus_labs/types/chat/completion_usage.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel +from .prompt_tokens_details import PromptTokensDetails +from .completion_tokens_details import CompletionTokensDetails + +__all__ = ["CompletionUsage"] + + +class CompletionUsage(BaseModel): + """Usage statistics for the completion request. + + Fields: + - completion_tokens (required): int + - prompt_tokens (required): int + - total_tokens (required): int + - completion_tokens_details (optional): CompletionTokensDetails + - prompt_tokens_details (optional): PromptTokensDetails + """ + + completion_tokens: int + """Number of tokens in the generated completion.""" + + prompt_tokens: int + """Number of tokens in the prompt.""" + + total_tokens: int + """Total number of tokens used in the request (prompt + completion).""" + + completion_tokens_details: Optional[CompletionTokensDetails] = None + """Breakdown of tokens used in a completion.""" + + prompt_tokens_details: Optional[PromptTokensDetails] = None + """Breakdown of tokens used in the prompt.""" diff --git a/src/dedalus_labs/types/chat/input_token_details.py b/src/dedalus_labs/types/chat/input_token_details.py new file mode 100644 index 0000000..8b4c13e --- /dev/null +++ b/src/dedalus_labs/types/chat/input_token_details.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["InputTokenDetails"] + + +class InputTokenDetails(BaseModel): + """Details about the input tokens billed for this request. + + Fields: + - text_tokens (optional): int + - audio_tokens (optional): int + """ + + audio_tokens: Optional[int] = None + """Number of audio tokens billed for this request.""" + + text_tokens: Optional[int] = None + """Number of text tokens billed for this request.""" diff --git a/src/dedalus_labs/types/chat/model_id.py b/src/dedalus_labs/types/chat/model_id.py deleted file mode 100644 index 132b470..0000000 --- a/src/dedalus_labs/types/chat/model_id.py +++ /dev/null @@ -1,7 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing_extensions import TypeAlias - -__all__ = ["ModelID"] - -ModelID: TypeAlias = str diff --git a/src/dedalus_labs/types/chat/models_param.py b/src/dedalus_labs/types/chat/models_param.py deleted file mode 100644 index b331ffd..0000000 --- a/src/dedalus_labs/types/chat/models_param.py +++ /dev/null @@ -1,15 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List, Union -from typing_extensions import TypeAlias - -from .model_id import ModelID -from ..shared_params.dedalus_model import DedalusModel - -__all__ = ["ModelsParam", "DedalusModelChoice"] - -DedalusModelChoice: TypeAlias = Union[ModelID, DedalusModel] - -ModelsParam: TypeAlias = List[DedalusModelChoice] diff --git a/src/dedalus_labs/types/chat/parsed_chat_completion.py b/src/dedalus_labs/types/chat/parsed_chat_completion.py index 5215f94..38ae7eb 100644 --- a/src/dedalus_labs/types/chat/parsed_chat_completion.py +++ b/src/dedalus_labs/types/chat/parsed_chat_completion.py @@ -3,7 +3,8 @@ from typing import List, Generic, TypeVar, Optional from ..._models import GenericModel -from .completion import Completion, Choice, ChoiceMessage +from .chat_completion import ChatCompletion, Choice +from .chat_completion_message import ChatCompletionMessage from .parsed_function_tool_call import ParsedFunctionToolCall __all__ = ["ParsedChatCompletion", "ParsedChoice", "ParsedChatCompletionMessage"] @@ -15,7 +16,7 @@ # pyright: reportIncompatibleVariableOverride=false -class ParsedChatCompletionMessage(ChoiceMessage, GenericModel, Generic[ContentType]): +class ParsedChatCompletionMessage(ChatCompletionMessage, GenericModel, Generic[ContentType]): parsed: Optional[ContentType] = None """The auto-parsed message contents""" @@ -28,7 +29,7 @@ class ParsedChoice(Choice, GenericModel, Generic[ContentType]): """A chat completion message generated by the model.""" -class ParsedChatCompletion(Completion, GenericModel, Generic[ContentType]): +class ParsedChatCompletion(ChatCompletion, GenericModel, Generic[ContentType]): choices: List[ParsedChoice[ContentType]] # type: ignore[assignment] """A list of chat completion choices. diff --git a/src/dedalus_labs/types/chat/parsed_function_tool_call.py b/src/dedalus_labs/types/chat/parsed_function_tool_call.py index c3f4b6f..f2fef19 100644 --- a/src/dedalus_labs/types/chat/parsed_function_tool_call.py +++ b/src/dedalus_labs/types/chat/parsed_function_tool_call.py @@ -2,8 +2,7 @@ from typing import Optional -from .completion import ChoiceMessageToolCallChatCompletionMessageToolCallFunction as Function -from .completion import ChoiceMessageToolCallChatCompletionMessageToolCall as FunctionToolCall +from .chat_completion_message_tool_call import ChatCompletionMessageToolCall as FunctionToolCall, Function __all__ = ["ParsedFunctionToolCall", "ParsedFunction"] diff --git a/src/dedalus_labs/types/chat/prediction_content_param.py b/src/dedalus_labs/types/chat/prediction_content_param.py new file mode 100644 index 0000000..3487c88 --- /dev/null +++ b/src/dedalus_labs/types/chat/prediction_content_param.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Iterable +from typing_extensions import Literal, Required, TypedDict + +from .chat_completion_content_part_text_param import ChatCompletionContentPartTextParam + +__all__ = ["PredictionContentParam"] + + +class PredictionContentParam(TypedDict, total=False): + """ + Static predicted output content, such as the content of a text file that is + being regenerated. + + Fields: + - type (required): Literal["content"] + - content (required): str | Annotated[list[ChatCompletionRequestMessageContentPartText], MinLen(1), ArrayTitle("PredictionContentArray")] + """ + + content: Required[Union[str, Iterable[ChatCompletionContentPartTextParam]]] + """ + The content that should be matched when generating a model response. If + generated tokens would match this content, the entire model response can be + returned much more quickly. + """ + + type: Required[Literal["content"]] + """The type of the predicted content you want to provide. + + This type is currently always `content`. + """ diff --git a/src/dedalus_labs/types/chat/prompt_tokens_details.py b/src/dedalus_labs/types/chat/prompt_tokens_details.py new file mode 100644 index 0000000..475ad01 --- /dev/null +++ b/src/dedalus_labs/types/chat/prompt_tokens_details.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["PromptTokensDetails"] + + +class PromptTokensDetails(BaseModel): + """Breakdown of tokens used in the prompt. + + Fields: + - audio_tokens (optional): int + - cached_tokens (optional): int + """ + + audio_tokens: Optional[int] = None + """Audio input tokens present in the prompt.""" + + cached_tokens: Optional[int] = None + """Cached tokens present in the prompt.""" diff --git a/src/dedalus_labs/types/chat/stream_choice.py b/src/dedalus_labs/types/chat/stream_choice.py new file mode 100644 index 0000000..a9020e6 --- /dev/null +++ b/src/dedalus_labs/types/chat/stream_choice.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .choice_delta import ChoiceDelta +from .stream_choice_logprobs import StreamChoiceLogprobs + +__all__ = ["StreamChoice"] + + +class StreamChoice(BaseModel): + """Schema for ChatCompletionStreamResponseChoicesItem. + + Fields: + - delta (required): ChatCompletionStreamResponseDelta + - logprobs (optional): ChatCompletionStreamResponseChoicesItemLogprobs + - finish_reason (required): Literal["stop", "length", "tool_calls", "content_filter", "function_call"] + - index (required): int + """ + + delta: ChoiceDelta + """A chat completion delta generated by streamed model responses.""" + + finish_reason: Optional[Literal["stop", "length", "tool_calls", "content_filter", "function_call"]] = None + """The reason the model stopped generating tokens. + + This will be `stop` if the model hit a natural stop point or a provided stop + sequence, `length` if the maximum number of tokens specified in the request was + reached, `content_filter` if content was omitted due to a flag from our content + filters, `tool_calls` if the model called a tool, or `function_call` + (deprecated) if the model called a function. Will be `null` until the stream is + complete, then one of the above values. + """ + + index: int + """The index of the choice in the list of choices.""" + + logprobs: Optional[StreamChoiceLogprobs] = None + """Log probability information for the choice. + + Fields: + + - content (required): list[ChatCompletionTokenLogprob] + - refusal (required): list[ChatCompletionTokenLogprob] + """ diff --git a/src/dedalus_labs/types/chat/stream_choice_logprobs.py b/src/dedalus_labs/types/chat/stream_choice_logprobs.py new file mode 100644 index 0000000..1dab79f --- /dev/null +++ b/src/dedalus_labs/types/chat/stream_choice_logprobs.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from ..._models import BaseModel +from .chat_completion_token_logprob import ChatCompletionTokenLogprob + +__all__ = ["StreamChoiceLogprobs"] + + +class StreamChoiceLogprobs(BaseModel): + """Log probability information for the choice. + + Fields: + - content (required): list[ChatCompletionTokenLogprob] + - refusal (required): list[ChatCompletionTokenLogprob] + """ + + content: Optional[List[ChatCompletionTokenLogprob]] = None + """A list of message content tokens with log probability information.""" + + refusal: Optional[List[ChatCompletionTokenLogprob]] = None + """A list of message refusal tokens with log probability information.""" diff --git a/src/dedalus_labs/types/chat/stream_chunk.py b/src/dedalus_labs/types/chat/stream_chunk.py deleted file mode 100644 index 2bb0242..0000000 --- a/src/dedalus_labs/types/chat/stream_chunk.py +++ /dev/null @@ -1,208 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import TYPE_CHECKING, Dict, List, Optional -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from ..._models import BaseModel -from .chat_completion_token_logprob import ChatCompletionTokenLogprob - -__all__ = [ - "StreamChunk", - "Choice", - "ChoiceDelta", - "ChoiceDeltaFunctionCall", - "ChoiceDeltaToolCall", - "ChoiceDeltaToolCallFunction", - "ChoiceLogprobs", - "Usage", - "UsageCompletionTokensDetails", - "UsagePromptTokensDetails", -] - - -class ChoiceDeltaFunctionCall(BaseModel): - arguments: Optional[str] = None - - name: Optional[str] = None - - if TYPE_CHECKING: - # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a - # value to this field, so for compatibility we avoid doing it at runtime. - __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] - - # Stub to indicate that arbitrary properties are accepted. - # To access properties that are not valid identifiers you can use `getattr`, e.g. - # `getattr(obj, '$type')` - def __getattr__(self, attr: str) -> object: ... - else: - __pydantic_extra__: Dict[str, object] - - -class ChoiceDeltaToolCallFunction(BaseModel): - arguments: Optional[str] = None - - name: Optional[str] = None - - if TYPE_CHECKING: - # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a - # value to this field, so for compatibility we avoid doing it at runtime. - __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] - - # Stub to indicate that arbitrary properties are accepted. - # To access properties that are not valid identifiers you can use `getattr`, e.g. - # `getattr(obj, '$type')` - def __getattr__(self, attr: str) -> object: ... - else: - __pydantic_extra__: Dict[str, object] - - -class ChoiceDeltaToolCall(BaseModel): - index: int - - id: Optional[str] = None - - function: Optional[ChoiceDeltaToolCallFunction] = None - - type: Optional[Literal["function"]] = None - - if TYPE_CHECKING: - # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a - # value to this field, so for compatibility we avoid doing it at runtime. - __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] - - # Stub to indicate that arbitrary properties are accepted. - # To access properties that are not valid identifiers you can use `getattr`, e.g. - # `getattr(obj, '$type')` - def __getattr__(self, attr: str) -> object: ... - else: - __pydantic_extra__: Dict[str, object] - - -class ChoiceDelta(BaseModel): - content: Optional[str] = None - - function_call: Optional[ChoiceDeltaFunctionCall] = None - - refusal: Optional[str] = None - - role: Optional[Literal["developer", "system", "user", "assistant", "tool"]] = None - - tool_calls: Optional[List[ChoiceDeltaToolCall]] = None - - if TYPE_CHECKING: - # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a - # value to this field, so for compatibility we avoid doing it at runtime. - __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] - - # Stub to indicate that arbitrary properties are accepted. - # To access properties that are not valid identifiers you can use `getattr`, e.g. - # `getattr(obj, '$type')` - def __getattr__(self, attr: str) -> object: ... - else: - __pydantic_extra__: Dict[str, object] - - -class ChoiceLogprobs(BaseModel): - content: Optional[List[ChatCompletionTokenLogprob]] = None - """A list of message content tokens with log probability information.""" - - refusal: Optional[List[ChatCompletionTokenLogprob]] = None - """A list of message refusal tokens with log probability information.""" - - -class Choice(BaseModel): - delta: ChoiceDelta - """Delta content for streaming responses""" - - index: int - """The index of this choice in the list of choices""" - - finish_reason: Optional[Literal["stop", "length", "tool_calls", "content_filter", "function_call"]] = None - """The reason the model stopped (only in final chunk)""" - - logprobs: Optional[ChoiceLogprobs] = None - """Log probability information for the choice.""" - - -class UsageCompletionTokensDetails(BaseModel): - accepted_prediction_tokens: Optional[int] = None - """ - When using Predicted Outputs, the number of tokens in the prediction that - appeared in the completion. - """ - - audio_tokens: Optional[int] = None - """Audio input tokens generated by the model.""" - - reasoning_tokens: Optional[int] = None - """Tokens generated by the model for reasoning.""" - - rejected_prediction_tokens: Optional[int] = None - """ - When using Predicted Outputs, the number of tokens in the prediction that did - not appear in the completion. However, like reasoning tokens, these tokens are - still counted in the total completion tokens for purposes of billing, output, - and context window limits. - """ - - -class UsagePromptTokensDetails(BaseModel): - audio_tokens: Optional[int] = None - """Audio input tokens present in the prompt.""" - - cached_tokens: Optional[int] = None - """Cached tokens present in the prompt.""" - - -class Usage(BaseModel): - completion_tokens: int - """Number of tokens in the generated completion.""" - - prompt_tokens: int - """Number of tokens in the prompt.""" - - total_tokens: int - """Total number of tokens used in the request (prompt + completion).""" - - completion_tokens_details: Optional[UsageCompletionTokensDetails] = None - """Breakdown of tokens used in a completion.""" - - prompt_tokens_details: Optional[UsagePromptTokensDetails] = None - """Breakdown of tokens used in the prompt.""" - - -class StreamChunk(BaseModel): - id: str - """Unique identifier for the chat completion""" - - choices: List[Choice] - """List of completion choice chunks""" - - created: int - """Unix timestamp when the chunk was created""" - - model: str - """ID of the model used for the completion""" - - object: Optional[Literal["chat.completion.chunk"]] = None - """Object type, always 'chat.completion.chunk'""" - - service_tier: Optional[Literal["auto", "default", "flex", "scale", "priority"]] = None - """Service tier used for processing the request""" - - system_fingerprint: Optional[str] = None - """System fingerprint representing backend configuration""" - - usage: Optional[Usage] = None - """Usage statistics for the completion request. - - Fields: - - - completion_tokens (required): int - - prompt_tokens (required): int - - total_tokens (required): int - - completion_tokens_details (optional): CompletionTokensDetails - - prompt_tokens_details (optional): PromptTokensDetails - """ diff --git a/src/dedalus_labs/types/chat/thinking_config_disabled_param.py b/src/dedalus_labs/types/chat/thinking_config_disabled_param.py new file mode 100644 index 0000000..e985863 --- /dev/null +++ b/src/dedalus_labs/types/chat/thinking_config_disabled_param.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ThinkingConfigDisabledParam"] + + +class ThinkingConfigDisabledParam(TypedDict, total=False): + """Schema for ThinkingConfigDisabled. + + Fields: + - type (required): Literal["disabled"] + """ + + type: Required[Literal["disabled"]] diff --git a/src/dedalus_labs/types/chat/thinking_config_enabled_param.py b/src/dedalus_labs/types/chat/thinking_config_enabled_param.py new file mode 100644 index 0000000..bb9a1be --- /dev/null +++ b/src/dedalus_labs/types/chat/thinking_config_enabled_param.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ThinkingConfigEnabledParam"] + + +class ThinkingConfigEnabledParam(TypedDict, total=False): + """Schema for ThinkingConfigEnabled. + + Fields: + - budget_tokens (required): int + - type (required): Literal["enabled"] + """ + + budget_tokens: Required[int] + """Determines how many tokens Claude can use for its internal reasoning process. + + Larger budgets can enable more thorough analysis for complex problems, improving + response quality. + + Must be ≥1024 and less than `max_tokens`. + + See + [extended thinking](https://docs.claude.com/en/docs/build-with-claude/extended-thinking) + for details. + """ + + type: Required[Literal["enabled"]] diff --git a/src/dedalus_labs/types/chat/tool_choice_any_param.py b/src/dedalus_labs/types/chat/tool_choice_any_param.py new file mode 100644 index 0000000..5ecdb82 --- /dev/null +++ b/src/dedalus_labs/types/chat/tool_choice_any_param.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ToolChoiceAnyParam"] + + +class ToolChoiceAnyParam(TypedDict, total=False): + """The model will use any available tools. + + Fields: + - disable_parallel_tool_use (optional): bool + - type (required): Literal["any"] + """ + + type: Required[Literal["any"]] + + disable_parallel_tool_use: bool + """Whether to disable parallel tool use. + + Defaults to `false`. If set to `true`, the model will output exactly one tool + use. + """ diff --git a/src/dedalus_labs/types/chat/tool_choice_auto_param.py b/src/dedalus_labs/types/chat/tool_choice_auto_param.py new file mode 100644 index 0000000..8a3879d --- /dev/null +++ b/src/dedalus_labs/types/chat/tool_choice_auto_param.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ToolChoiceAutoParam"] + + +class ToolChoiceAutoParam(TypedDict, total=False): + """The model will automatically decide whether to use tools. + + Fields: + - disable_parallel_tool_use (optional): bool + - type (required): Literal["auto"] + """ + + type: Required[Literal["auto"]] + + disable_parallel_tool_use: bool + """Whether to disable parallel tool use. + + Defaults to `false`. If set to `true`, the model will output at most one tool + use. + """ diff --git a/src/dedalus_labs/types/chat/tool_choice_none_param.py b/src/dedalus_labs/types/chat/tool_choice_none_param.py new file mode 100644 index 0000000..fbe553e --- /dev/null +++ b/src/dedalus_labs/types/chat/tool_choice_none_param.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ToolChoiceNoneParam"] + + +class ToolChoiceNoneParam(TypedDict, total=False): + """The model will not be allowed to use tools. + + Fields: + - type (required): Literal["none"] + """ + + type: Required[Literal["none"]] diff --git a/src/dedalus_labs/types/chat/tool_choice_tool_param.py b/src/dedalus_labs/types/chat/tool_choice_tool_param.py new file mode 100644 index 0000000..3fc03a0 --- /dev/null +++ b/src/dedalus_labs/types/chat/tool_choice_tool_param.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["ToolChoiceToolParam"] + + +class ToolChoiceToolParam(TypedDict, total=False): + """The model will use the specified tool with `tool_choice.name`. + + Fields: + - disable_parallel_tool_use (optional): bool + - name (required): str + - type (required): Literal["tool"] + """ + + name: Required[str] + """The name of the tool to use.""" + + type: Required[Literal["tool"]] + + disable_parallel_tool_use: bool + """Whether to disable parallel tool use. + + Defaults to `false`. If set to `true`, the model will output exactly one tool + use. + """ diff --git a/src/dedalus_labs/types/chat/top_logprob.py b/src/dedalus_labs/types/chat/top_logprob.py deleted file mode 100644 index 813a4ee..0000000 --- a/src/dedalus_labs/types/chat/top_logprob.py +++ /dev/null @@ -1,28 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import List, Optional - -from ..._models import BaseModel - -__all__ = ["TopLogprob"] - - -class TopLogprob(BaseModel): - token: str - """The token.""" - - bytes: Optional[List[int]] = None - """A list of integers representing the UTF-8 bytes representation of the token. - - Useful in instances where characters are represented by multiple tokens and - their byte representations must be combined to generate the correct text - representation. Can be `null` if there is no bytes representation for the token. - """ - - logprob: float - """The log probability of this token, if it is within the top 20 most likely - tokens. - - Otherwise, the value `-9999.0` is used to signify that the token is very - unlikely. - """ diff --git a/src/dedalus_labs/types/create_embedding_response.py b/src/dedalus_labs/types/create_embedding_response.py index be87d00..cb26fdb 100644 --- a/src/dedalus_labs/types/create_embedding_response.py +++ b/src/dedalus_labs/types/create_embedding_response.py @@ -1,33 +1,64 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Union, Optional +from typing import List from typing_extensions import Literal from .._models import BaseModel -__all__ = ["CreateEmbeddingResponse", "Data"] +__all__ = ["CreateEmbeddingResponse", "Data", "Usage"] class Data(BaseModel): - embedding: Union[List[float], str] - """The embedding vector (float array or base64 string)""" + """Represents an embedding vector returned by embedding endpoint. + + Fields: + - index (required): int + - embedding (required): list[float] + - object (required): Literal["embedding"] + """ + + embedding: List[float] + """The embedding vector, which is a list of floats. + + The length of vector depends on the model as listed in the + [embedding guide](https://platform.openai.com/docs/guides/embeddings). + """ index: int - """Index of the embedding in the list""" + """The index of the embedding in the list of embeddings.""" + + object: Literal["embedding"] + """The object type, which is always "embedding".""" + - object: Optional[Literal["embedding"]] = None - """Object type, always 'embedding'""" +class Usage(BaseModel): + """The usage information for the request.""" + + prompt_tokens: int + """The number of tokens used by the prompt.""" + + total_tokens: int + """The total number of tokens used by the request.""" class CreateEmbeddingResponse(BaseModel): + """Schema for EmbeddingResponse. + + Fields: + - data (required): list[Embedding] + - model (required): str + - object (required): Literal["list"] + - usage (required): Usage + """ + data: List[Data] - """List of embedding objects""" + """The list of embeddings generated by the model.""" model: str - """The model used for embeddings""" + """The name of the model used to generate the embedding.""" - usage: Dict[str, int] - """Usage statistics (prompt_tokens, total_tokens)""" + object: Literal["list"] + """The object type, which is always "list".""" - object: Optional[Literal["list"]] = None - """Object type, always 'list'""" + usage: Usage + """The usage information for the request.""" diff --git a/src/dedalus_labs/types/health_check_response.py b/src/dedalus_labs/types/health_check_response.py deleted file mode 100644 index b3b7958..0000000 --- a/src/dedalus_labs/types/health_check_response.py +++ /dev/null @@ -1,9 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .._models import BaseModel - -__all__ = ["HealthCheckResponse"] - - -class HealthCheckResponse(BaseModel): - status: str diff --git a/src/dedalus_labs/types/image.py b/src/dedalus_labs/types/image.py index bc5ce8e..00061e3 100644 --- a/src/dedalus_labs/types/image.py +++ b/src/dedalus_labs/types/image.py @@ -8,6 +8,8 @@ class Image(BaseModel): + """Single image object.""" + b64_json: Optional[str] = None """Base64-encoded image data (if response_format=b64_json)""" diff --git a/src/dedalus_labs/types/images_response.py b/src/dedalus_labs/types/images_response.py index 8974a7e..20be59a 100644 --- a/src/dedalus_labs/types/images_response.py +++ b/src/dedalus_labs/types/images_response.py @@ -9,6 +9,8 @@ class ImagesResponse(BaseModel): + """Response from image generation.""" + created: int """Unix timestamp when images were created""" diff --git a/src/dedalus_labs/types/list_models_response.py b/src/dedalus_labs/types/list_models_response.py index c1b1278..cb7c641 100644 --- a/src/dedalus_labs/types/list_models_response.py +++ b/src/dedalus_labs/types/list_models_response.py @@ -10,6 +10,8 @@ class ListModelsResponse(BaseModel): + """Response for /v1/models endpoint.""" + data: List[Model] """List of available models""" diff --git a/src/dedalus_labs/types/model.py b/src/dedalus_labs/types/model.py index 549e78a..0ac28fc 100644 --- a/src/dedalus_labs/types/model.py +++ b/src/dedalus_labs/types/model.py @@ -10,6 +10,8 @@ class Capabilities(BaseModel): + """Normalized model capabilities across all providers.""" + audio: Optional[bool] = None """Supports audio processing""" @@ -42,6 +44,8 @@ class Capabilities(BaseModel): class Defaults(BaseModel): + """Provider-declared default parameters for model generation.""" + max_output_tokens: Optional[int] = None """Default maximum output tokens""" @@ -56,6 +60,12 @@ class Defaults(BaseModel): class Model(BaseModel): + """Unified model metadata across all providers. + + Combines provider-specific schemas into a single, consistent format. + Fields that aren't available from a provider are set to None. + """ + id: str """Unique model identifier with provider prefix (e.g., 'openai/gpt-4')""" diff --git a/src/dedalus_labs/types/root_get_response.py b/src/dedalus_labs/types/root_get_response.py deleted file mode 100644 index bce605b..0000000 --- a/src/dedalus_labs/types/root_get_response.py +++ /dev/null @@ -1,9 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .._models import BaseModel - -__all__ = ["RootGetResponse"] - - -class RootGetResponse(BaseModel): - message: str diff --git a/src/dedalus_labs/types/shared/__init__.py b/src/dedalus_labs/types/shared/__init__.py index 0ed7dcf..343f3ed 100644 --- a/src/dedalus_labs/types/shared/__init__.py +++ b/src/dedalus_labs/types/shared/__init__.py @@ -1,6 +1,17 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .reasoning import Reasoning as Reasoning +from .credential import Credential as Credential +from .mcp_servers import MCPServers as MCPServers +from .tool_choice import ToolChoice as ToolChoice from .dedalus_model import DedalusModel as DedalusModel +from .model_settings import ModelSettings as ModelSettings +from .mcp_credentials import MCPCredentials as MCPCredentials +from .mcp_server_spec import MCPServerSpec as MCPServerSpec +from .mcp_tool_result import MCPToolResult as MCPToolResult +from .json_value_input import JSONValueInput as JSONValueInput +from .json_object_input import JSONObjectInput as JSONObjectInput +from .function_definition import FunctionDefinition as FunctionDefinition from .dedalus_model_choice import DedalusModelChoice as DedalusModelChoice from .response_format_text import ResponseFormatText as ResponseFormatText from .response_format_json_object import ResponseFormatJSONObject as ResponseFormatJSONObject diff --git a/src/dedalus_labs/types/shared/credential.py b/src/dedalus_labs/types/shared/credential.py new file mode 100644 index 0000000..b7fb196 --- /dev/null +++ b/src/dedalus_labs/types/shared/credential.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Union + +from ..._models import BaseModel + +__all__ = ["Credential"] + + +class Credential(BaseModel): + """Credential for MCP server authentication. + + Passed at endpoint level (e.g., chat.completions.create) and matched + to MCP servers by connection name. Wire format matches dedalus_mcp.Credential.to_dict(). + """ + + connection_name: str + """Connection name. Must match a connection in MCPServer.connections.""" + + values: Dict[str, Union[str, int, bool]] + """Credential values. Keys are credential field names, values are the secrets.""" diff --git a/src/dedalus_labs/types/shared/dedalus_model.py b/src/dedalus_labs/types/shared/dedalus_model.py index b40dd1f..163e322 100644 --- a/src/dedalus_labs/types/shared/dedalus_model.py +++ b/src/dedalus_labs/types/shared/dedalus_model.py @@ -1,178 +1,32 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import TYPE_CHECKING, Dict, List, Union, Optional -from typing_extensions import Literal, TypeAlias +from __future__ import annotations -from pydantic import Field as FieldInfo +from typing import Optional from ..._models import BaseModel -__all__ = ["DedalusModel", "Settings", "SettingsReasoning", "SettingsToolChoice", "SettingsToolChoiceMCPToolChoice"] +__all__ = ["DedalusModel"] -class SettingsReasoning(BaseModel): - effort: Optional[Literal["minimal", "low", "medium", "high"]] = None - - generate_summary: Optional[Literal["auto", "concise", "detailed"]] = None - - summary: Optional[Literal["auto", "concise", "detailed"]] = None - - if TYPE_CHECKING: - # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a - # value to this field, so for compatibility we avoid doing it at runtime. - __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] - - # Stub to indicate that arbitrary properties are accepted. - # To access properties that are not valid identifiers you can use `getattr`, e.g. - # `getattr(obj, '$type')` - def __getattr__(self, attr: str) -> object: ... - else: - __pydantic_extra__: Dict[str, object] - - -class SettingsToolChoiceMCPToolChoice(BaseModel): - name: str - - server_label: str - - -SettingsToolChoice: TypeAlias = Union[ - Literal["auto", "required", "none"], str, Dict[str, object], SettingsToolChoiceMCPToolChoice, None -] - - -class Settings(BaseModel): - attributes: Optional[Dict[str, object]] = None - - audio: Optional[Dict[str, object]] = None - - deferred: Optional[bool] = None - - disable_automatic_function_calling: Optional[bool] = None - - extra_args: Optional[Dict[str, object]] = None - - extra_headers: Optional[Dict[str, str]] = None - - extra_query: Optional[Dict[str, object]] = None - - frequency_penalty: Optional[float] = None - - generation_config: Optional[Dict[str, object]] = None - - include_usage: Optional[bool] = None - - input_audio_format: Optional[str] = None - - input_audio_transcription: Optional[Dict[str, object]] = None - - logit_bias: Optional[Dict[str, int]] = None - - logprobs: Optional[bool] = None - - max_completion_tokens: Optional[int] = None - - max_tokens: Optional[int] = None - - metadata: Optional[Dict[str, str]] = None - - modalities: Optional[List[str]] = None - - n: Optional[int] = None - - output_audio_format: Optional[str] = None - - parallel_tool_calls: Optional[bool] = None - - prediction: Optional[Dict[str, object]] = None - - presence_penalty: Optional[float] = None - - prompt_cache_key: Optional[str] = None - - reasoning: Optional[SettingsReasoning] = None - - reasoning_effort: Optional[str] = None - - response_format: Optional[Dict[str, object]] = None - - response_include: Optional[ - List[ - Literal[ - "file_search_call.results", - "web_search_call.results", - "web_search_call.action.sources", - "message.input_image.image_url", - "computer_call_output.output.image_url", - "code_interpreter_call.outputs", - "reasoning.encrypted_content", - "message.output_text.logprobs", - ] - ] - ] = None - - safety_identifier: Optional[str] = None - - safety_settings: Optional[List[Dict[str, object]]] = None - - search_parameters: Optional[Dict[str, object]] = None - - seed: Optional[int] = None - - service_tier: Optional[str] = None - - stop: Union[str, List[str], None] = None - - store: Optional[bool] = None - - stream: Optional[bool] = None - - stream_options: Optional[Dict[str, object]] = None - - structured_output: Optional[object] = None - - system_instruction: Optional[Dict[str, object]] = None - - temperature: Optional[float] = None - - thinking: Optional[Dict[str, object]] = None - - timeout: Optional[float] = None - - tool_choice: Optional[SettingsToolChoice] = None - - tool_config: Optional[Dict[str, object]] = None - - top_k: Optional[int] = None - - top_logprobs: Optional[int] = None - - top_p: Optional[float] = None - - truncation: Optional[Literal["auto", "disabled"]] = None - - turn_detection: Optional[Dict[str, object]] = None - - use_responses: Optional[bool] = None - - user: Optional[str] = None - - verbosity: Optional[str] = None - - voice: Optional[str] = None - - web_search_options: Optional[Dict[str, object]] = None +class DedalusModel(BaseModel): + """Structured model selection entry used in request payloads. + Supports OpenAI-style semantics (string model id) while enabling + optional per-model default settings for Dedalus multi-model routing. + """ -class DedalusModel(BaseModel): model: str """ Model identifier with provider prefix (e.g., 'openai/gpt-5', 'anthropic/claude-3-5-sonnet'). """ - settings: Optional[Settings] = None + settings: Optional["ModelSettings"] = None """ Optional default generation settings (e.g., temperature, max_tokens) applied when this model is selected. """ + + +from .model_settings import ModelSettings diff --git a/src/dedalus_labs/types/shared/dedalus_model_choice.py b/src/dedalus_labs/types/shared/dedalus_model_choice.py index 54815f4..d9eb0f1 100644 --- a/src/dedalus_labs/types/shared/dedalus_model_choice.py +++ b/src/dedalus_labs/types/shared/dedalus_model_choice.py @@ -1,11 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from __future__ import annotations + from typing import Union from typing_extensions import TypeAlias -from .dedalus_model import DedalusModel -from ..chat.model_id import ModelID - __all__ = ["DedalusModelChoice"] -DedalusModelChoice: TypeAlias = Union[ModelID, DedalusModel] +DedalusModelChoice: TypeAlias = Union[str, "DedalusModel"] + +from .dedalus_model import DedalusModel diff --git a/src/dedalus_labs/types/shared/function_definition.py b/src/dedalus_labs/types/shared/function_definition.py new file mode 100644 index 0000000..5277262 --- /dev/null +++ b/src/dedalus_labs/types/shared/function_definition.py @@ -0,0 +1,56 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["FunctionDefinition"] + + +class FunctionDefinition(BaseModel): + """Schema for FunctionObject. + + Fields: + - description (optional): str + - name (required): str + - parameters (optional): FunctionParameters + - strict (optional): bool | None + """ + + name: str + """The name of the function to be called. + + Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length + of 64. + """ + + description: Optional[str] = None + """ + A description of what the function does, used by the model to choose when and + how to call the function. + """ + + parameters: Optional["JSONObjectInput"] = None + """The parameters the functions accepts, described as a JSON Schema object. + + See the [guide](https://platform.openai.com/docs/guides/function-calling) for + examples, and the + [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for + documentation about the format. + + Omitting `parameters` defines a function with an empty parameter list. + """ + + strict: Optional[bool] = None + """Whether to enable strict schema adherence when generating the function call. + + If set to true, the model will follow the exact schema defined in the + `parameters` field. Only a subset of JSON Schema is supported when `strict` is + `true`. Learn more about Structured Outputs in the + [function calling guide](https://platform.openai.com/docs/guides/function-calling). + """ + + +from .json_object_input import JSONObjectInput diff --git a/src/dedalus_labs/types/shared/json_object_input.py b/src/dedalus_labs/types/shared/json_object_input.py new file mode 100644 index 0000000..df1f9c3 --- /dev/null +++ b/src/dedalus_labs/types/shared/json_object_input.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Optional +from typing_extensions import TypeAliasType + +__all__ = ["JSONObjectInput"] + +from .json_value_input import JSONValueInput + +JSONObjectInput = TypeAliasType("JSONObjectInput", Dict[str, Optional[JSONValueInput]]) diff --git a/src/dedalus_labs/types/shared/json_value_input.py b/src/dedalus_labs/types/shared/json_value_input.py new file mode 100644 index 0000000..cfd22c6 --- /dev/null +++ b/src/dedalus_labs/types/shared/json_value_input.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Union, Optional +from typing_extensions import TypeAliasType + +__all__ = ["JSONValueInput"] + +JSONValueInput = TypeAliasType( + "JSONValueInput", + Union[str, float, bool, Dict[str, Optional["JSONValueInput"]], List[Optional["JSONValueInput"]], None], +) diff --git a/src/dedalus_labs/types/shared/mcp_credentials.py b/src/dedalus_labs/types/shared/mcp_credentials.py new file mode 100644 index 0000000..9508821 --- /dev/null +++ b/src/dedalus_labs/types/shared/mcp_credentials.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List +from typing_extensions import TypeAlias + +from .credential import Credential + +__all__ = ["MCPCredentials"] + +MCPCredentials: TypeAlias = List[Credential] diff --git a/src/dedalus_labs/types/shared/mcp_server_spec.py b/src/dedalus_labs/types/shared/mcp_server_spec.py new file mode 100644 index 0000000..dae65f7 --- /dev/null +++ b/src/dedalus_labs/types/shared/mcp_server_spec.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional + +from ..._models import BaseModel + +__all__ = ["MCPServerSpec"] + + +class MCPServerSpec(BaseModel): + """Structured MCP server specification. + + Slug-based: {"slug": "dedalus-labs/brave-search", "name": "github-integration", "version": "v1.0.0"} + URL-based: {"url": "https://mcp.dedaluslabs.ai/acme/my-server/mcp", "name": "custom-server"} + """ + + name: str + """Server instance name for credential matching.""" + + credentials: Optional[Dict[str, str]] = None + """Encrypted credential blobs keyed by connection name. + + Values are base64url ciphertext produced by the SDK (client-side encryption with + the AS public key). + """ + + slug: Optional[str] = None + """Marketplace identifier.""" + + url: Optional[str] = None + """Direct URL to MCP server endpoint (Pro users).""" + + version: Optional[str] = None + """Version constraint for slug-based servers.""" diff --git a/src/dedalus_labs/types/shared/mcp_servers.py b/src/dedalus_labs/types/shared/mcp_servers.py new file mode 100644 index 0000000..2d7b497 --- /dev/null +++ b/src/dedalus_labs/types/shared/mcp_servers.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union +from typing_extensions import TypeAlias + +from .mcp_server_spec import MCPServerSpec + +__all__ = ["MCPServers", "MCPServerItem"] + +MCPServerItem: TypeAlias = Union[str, MCPServerSpec] + +MCPServers: TypeAlias = List[MCPServerItem] diff --git a/src/dedalus_labs/types/shared/mcp_tool_result.py b/src/dedalus_labs/types/shared/mcp_tool_result.py new file mode 100644 index 0000000..3b6ebdf --- /dev/null +++ b/src/dedalus_labs/types/shared/mcp_tool_result.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["MCPToolResult"] + + +class MCPToolResult(BaseModel): + """Result of a single MCP tool execution. + + Provides visibility into MCP tool calls including the full input arguments + and structured output, enabling debugging and audit trails. + """ + + arguments: "JSONObjectInput" + """Input arguments passed to the tool""" + + is_error: bool + """Whether the tool execution resulted in an error""" + + server_name: str + """Name of the MCP server that handled the tool""" + + tool_name: str + """Name of the MCP tool that was executed""" + + duration_ms: Optional[int] = None + """Execution time in milliseconds""" + + result: Optional["JSONValueInput"] = None + """Structured result from the tool (parsed from structuredContent or content)""" + + +from .json_value_input import JSONValueInput +from .json_object_input import JSONObjectInput diff --git a/src/dedalus_labs/types/shared/model_settings.py b/src/dedalus_labs/types/shared/model_settings.py new file mode 100644 index 0000000..8ecbc7f --- /dev/null +++ b/src/dedalus_labs/types/shared/model_settings.py @@ -0,0 +1,124 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .reasoning import Reasoning +from .tool_choice import ToolChoice + +__all__ = ["ModelSettings"] + + +class ModelSettings(BaseModel): + attributes: Optional[Dict[str, object]] = None + + audio: Optional["JSONObjectInput"] = None + + deferred: Optional[bool] = None + + extra_args: Optional[Dict[str, object]] = None + + extra_headers: Optional[Dict[str, str]] = None + + extra_query: Optional[Dict[str, object]] = None + + frequency_penalty: Optional[float] = None + + generation_config: Optional["JSONObjectInput"] = None + + include_usage: Optional[bool] = None + + input_audio_format: Optional[str] = None + + input_audio_transcription: Optional["JSONObjectInput"] = None + + logit_bias: Optional[Dict[str, int]] = None + + logprobs: Optional[bool] = None + + max_completion_tokens: Optional[int] = None + + max_tokens: Optional[int] = None + + metadata: Optional[Dict[str, str]] = None + + modalities: Optional[List[str]] = None + + n: Optional[int] = None + + output_audio_format: Optional[str] = None + + parallel_tool_calls: Optional[bool] = None + + prediction: Optional["JSONObjectInput"] = None + + presence_penalty: Optional[float] = None + + prompt_cache_key: Optional[str] = None + + reasoning: Optional[Reasoning] = None + """**gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + """ + + reasoning_effort: Optional[str] = None + + response_format: Optional["JSONObjectInput"] = None + + safety_identifier: Optional[str] = None + + safety_settings: Optional[List["JSONObjectInput"]] = None + + search_parameters: Optional["JSONObjectInput"] = None + + seed: Optional[int] = None + + service_tier: Optional[str] = None + + stop: Union[str, List[str], None] = None + + store: Optional[bool] = None + + stream: Optional[bool] = None + + stream_options: Optional["JSONObjectInput"] = None + + structured_output: Optional[object] = None + + system_instruction: Optional["JSONObjectInput"] = None + + temperature: Optional[float] = None + + thinking: Optional["JSONObjectInput"] = None + + timeout: Optional[float] = None + + tool_choice: Optional[ToolChoice] = None + + tool_config: Optional["JSONObjectInput"] = None + + top_k: Optional[int] = None + + top_logprobs: Optional[int] = None + + top_p: Optional[float] = None + + truncation: Optional[Literal["auto", "disabled"]] = None + + turn_detection: Optional["JSONObjectInput"] = None + + user: Optional[str] = None + + verbosity: Optional[str] = None + + voice: Optional[str] = None + + web_search_options: Optional["JSONObjectInput"] = None + + +from .json_object_input import JSONObjectInput diff --git a/src/dedalus_labs/types/shared/reasoning.py b/src/dedalus_labs/types/shared/reasoning.py new file mode 100644 index 0000000..e6254fc --- /dev/null +++ b/src/dedalus_labs/types/shared/reasoning.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import TYPE_CHECKING, Dict, Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["Reasoning"] + + +class Reasoning(BaseModel): + """**gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + """ + + effort: Optional[Literal["none", "minimal", "low", "medium", "high", "xhigh"]] = None + + generate_summary: Optional[Literal["auto", "concise", "detailed"]] = None + + summary: Optional[Literal["auto", "concise", "detailed"]] = None + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] diff --git a/src/dedalus_labs/types/shared/response_format_json_object.py b/src/dedalus_labs/types/shared/response_format_json_object.py index 2aaa5db..95017c8 100644 --- a/src/dedalus_labs/types/shared/response_format_json_object.py +++ b/src/dedalus_labs/types/shared/response_format_json_object.py @@ -8,5 +8,16 @@ class ResponseFormatJSONObject(BaseModel): + """JSON object response format. + + An older method of generating JSON responses. + Using `json_schema` is recommended for models that support it. Note that the + model will not generate JSON without a system or user message instructing it + to do so. + + Fields: + - type (required): Literal["json_object"] + """ + type: Literal["json_object"] """The type of response format being defined. Always `json_object`.""" diff --git a/src/dedalus_labs/types/shared/response_format_json_schema.py b/src/dedalus_labs/types/shared/response_format_json_schema.py index c792444..68dc5a9 100644 --- a/src/dedalus_labs/types/shared/response_format_json_schema.py +++ b/src/dedalus_labs/types/shared/response_format_json_schema.py @@ -1,6 +1,8 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, Optional +from __future__ import annotations + +from typing import Optional from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -11,6 +13,8 @@ class JSONSchema(BaseModel): + """Structured Outputs configuration options, including a JSON Schema.""" + name: str """The name of the response format. @@ -24,7 +28,7 @@ class JSONSchema(BaseModel): how to respond in the format. """ - schema_: Optional[Dict[str, object]] = FieldInfo(alias="schema", default=None) + schema_: Optional["JSONObjectInput"] = FieldInfo(alias="schema", default=None) """ The schema for the response format, described as a JSON Schema object. Learn how to build JSON schemas [here](https://json-schema.org/). @@ -41,8 +45,21 @@ class JSONSchema(BaseModel): class ResponseFormatJSONSchema(BaseModel): + """JSON Schema response format. + + Used to generate structured JSON responses. + Learn more about [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs). + + Fields: + - type (required): Literal["json_schema"] + - json_schema (required): JSONSchema + """ + json_schema: JSONSchema """Structured Outputs configuration options, including a JSON Schema.""" type: Literal["json_schema"] """The type of response format being defined. Always `json_schema`.""" + + +from .json_object_input import JSONObjectInput diff --git a/src/dedalus_labs/types/shared/response_format_text.py b/src/dedalus_labs/types/shared/response_format_text.py index f0c8cfb..bcdd6e5 100644 --- a/src/dedalus_labs/types/shared/response_format_text.py +++ b/src/dedalus_labs/types/shared/response_format_text.py @@ -8,5 +8,11 @@ class ResponseFormatText(BaseModel): + """Default response format. Used to generate text responses. + + Fields: + - type (required): Literal["text"] + """ + type: Literal["text"] """The type of response format being defined. Always `text`.""" diff --git a/src/dedalus_labs/types/shared/tool_choice.py b/src/dedalus_labs/types/shared/tool_choice.py new file mode 100644 index 0000000..3a1c5fc --- /dev/null +++ b/src/dedalus_labs/types/shared/tool_choice.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Union +from typing_extensions import Literal, TypeAlias + +from ..._models import BaseModel + +__all__ = ["ToolChoice", "MCPToolChoice"] + + +class MCPToolChoice(BaseModel): + name: str + + server_label: str + + +ToolChoice: TypeAlias = Union[Literal["auto", "required", "none"], str, Dict[str, object], MCPToolChoice, None] diff --git a/src/dedalus_labs/types/shared_params/__init__.py b/src/dedalus_labs/types/shared_params/__init__.py index 0ed7dcf..ceda706 100644 --- a/src/dedalus_labs/types/shared_params/__init__.py +++ b/src/dedalus_labs/types/shared_params/__init__.py @@ -1,6 +1,16 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .reasoning import Reasoning as Reasoning +from .credential import Credential as Credential +from .mcp_servers import MCPServers as MCPServers +from .tool_choice import ToolChoice as ToolChoice from .dedalus_model import DedalusModel as DedalusModel +from .model_settings import ModelSettings as ModelSettings +from .mcp_credentials import MCPCredentials as MCPCredentials +from .mcp_server_spec import MCPServerSpec as MCPServerSpec +from .json_value_input import JSONValueInput as JSONValueInput +from .json_object_input import JSONObjectInput as JSONObjectInput +from .function_definition import FunctionDefinition as FunctionDefinition from .dedalus_model_choice import DedalusModelChoice as DedalusModelChoice from .response_format_text import ResponseFormatText as ResponseFormatText from .response_format_json_object import ResponseFormatJSONObject as ResponseFormatJSONObject diff --git a/src/dedalus_labs/types/shared_params/credential.py b/src/dedalus_labs/types/shared_params/credential.py new file mode 100644 index 0000000..74e9d05 --- /dev/null +++ b/src/dedalus_labs/types/shared_params/credential.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union +from typing_extensions import Required, TypedDict + +__all__ = ["Credential"] + + +class Credential(TypedDict, total=False): + """Credential for MCP server authentication. + + Passed at endpoint level (e.g., chat.completions.create) and matched + to MCP servers by connection name. Wire format matches dedalus_mcp.Credential.to_dict(). + """ + + connection_name: Required[str] + """Connection name. Must match a connection in MCPServer.connections.""" + + values: Required[Dict[str, Union[str, int, bool]]] + """Credential values. Keys are credential field names, values are the secrets.""" diff --git a/src/dedalus_labs/types/shared_params/dedalus_model.py b/src/dedalus_labs/types/shared_params/dedalus_model.py index e931b7a..ed2d556 100644 --- a/src/dedalus_labs/types/shared_params/dedalus_model.py +++ b/src/dedalus_labs/types/shared_params/dedalus_model.py @@ -2,168 +2,30 @@ from __future__ import annotations -from typing import Dict, List, Union, Iterable, Optional -from typing_extensions import Literal, Required, TypeAlias, TypedDict +from typing import Optional +from typing_extensions import Required, TypedDict -from ..._types import SequenceNotStr +__all__ = ["DedalusModel"] -__all__ = ["DedalusModel", "Settings", "SettingsReasoning", "SettingsToolChoice", "SettingsToolChoiceMCPToolChoice"] +class DedalusModel(TypedDict, total=False): + """Structured model selection entry used in request payloads. -class SettingsReasoningTyped(TypedDict, total=False): - effort: Optional[Literal["minimal", "low", "medium", "high"]] - - generate_summary: Optional[Literal["auto", "concise", "detailed"]] - - summary: Optional[Literal["auto", "concise", "detailed"]] - - -SettingsReasoning: TypeAlias = Union[SettingsReasoningTyped, Dict[str, object]] - - -class SettingsToolChoiceMCPToolChoice(TypedDict, total=False): - name: Required[str] - - server_label: Required[str] - - -SettingsToolChoice: TypeAlias = Union[ - Literal["auto", "required", "none"], str, Dict[str, object], SettingsToolChoiceMCPToolChoice -] - - -class Settings(TypedDict, total=False): - attributes: Dict[str, object] - - audio: Optional[Dict[str, object]] - - deferred: Optional[bool] - - disable_automatic_function_calling: bool - - extra_args: Optional[Dict[str, object]] - - extra_headers: Optional[Dict[str, str]] - - extra_query: Optional[Dict[str, object]] - - frequency_penalty: Optional[float] - - generation_config: Optional[Dict[str, object]] - - include_usage: Optional[bool] - - input_audio_format: Optional[str] - - input_audio_transcription: Optional[Dict[str, object]] - - logit_bias: Optional[Dict[str, int]] - - logprobs: Optional[bool] - - max_completion_tokens: Optional[int] - - max_tokens: Optional[int] - - metadata: Optional[Dict[str, str]] - - modalities: Optional[SequenceNotStr[str]] - - n: Optional[int] - - output_audio_format: Optional[str] - - parallel_tool_calls: Optional[bool] - - prediction: Optional[Dict[str, object]] - - presence_penalty: Optional[float] - - prompt_cache_key: Optional[str] - - reasoning: Optional[SettingsReasoning] - - reasoning_effort: Optional[str] - - response_format: Optional[Dict[str, object]] - - response_include: Optional[ - List[ - Literal[ - "file_search_call.results", - "web_search_call.results", - "web_search_call.action.sources", - "message.input_image.image_url", - "computer_call_output.output.image_url", - "code_interpreter_call.outputs", - "reasoning.encrypted_content", - "message.output_text.logprobs", - ] - ] - ] - - safety_identifier: Optional[str] - - safety_settings: Optional[Iterable[Dict[str, object]]] - - search_parameters: Optional[Dict[str, object]] - - seed: Optional[int] - - service_tier: Optional[str] - - stop: Union[str, SequenceNotStr[str], None] - - store: Optional[bool] - - stream: Optional[bool] - - stream_options: Optional[Dict[str, object]] - - structured_output: object - - system_instruction: Optional[Dict[str, object]] - - temperature: Optional[float] - - thinking: Optional[Dict[str, object]] - - timeout: Optional[float] - - tool_choice: Optional[SettingsToolChoice] - - tool_config: Optional[Dict[str, object]] - - top_k: Optional[int] - - top_logprobs: Optional[int] - - top_p: Optional[float] - - truncation: Optional[Literal["auto", "disabled"]] - - turn_detection: Optional[Dict[str, object]] - - use_responses: bool - - user: Optional[str] - - verbosity: Optional[str] - - voice: Optional[str] - - web_search_options: Optional[Dict[str, object]] - + Supports OpenAI-style semantics (string model id) while enabling + optional per-model default settings for Dedalus multi-model routing. + """ -class DedalusModel(TypedDict, total=False): model: Required[str] """ Model identifier with provider prefix (e.g., 'openai/gpt-5', 'anthropic/claude-3-5-sonnet'). """ - settings: Optional[Settings] + settings: Optional["ModelSettings"] """ Optional default generation settings (e.g., temperature, max_tokens) applied when this model is selected. """ + + +from .model_settings import ModelSettings diff --git a/src/dedalus_labs/types/shared_params/dedalus_model_choice.py b/src/dedalus_labs/types/shared_params/dedalus_model_choice.py index 9f7fdda..d9eb0f1 100644 --- a/src/dedalus_labs/types/shared_params/dedalus_model_choice.py +++ b/src/dedalus_labs/types/shared_params/dedalus_model_choice.py @@ -5,9 +5,8 @@ from typing import Union from typing_extensions import TypeAlias -from .dedalus_model import DedalusModel -from ..chat.model_id import ModelID - __all__ = ["DedalusModelChoice"] -DedalusModelChoice: TypeAlias = Union[ModelID, DedalusModel] +DedalusModelChoice: TypeAlias = Union[str, "DedalusModel"] + +from .dedalus_model import DedalusModel diff --git a/src/dedalus_labs/types/shared_params/function_definition.py b/src/dedalus_labs/types/shared_params/function_definition.py new file mode 100644 index 0000000..1511287 --- /dev/null +++ b/src/dedalus_labs/types/shared_params/function_definition.py @@ -0,0 +1,55 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +__all__ = ["FunctionDefinition"] + + +class FunctionDefinition(TypedDict, total=False): + """Schema for FunctionObject. + + Fields: + - description (optional): str + - name (required): str + - parameters (optional): FunctionParameters + - strict (optional): bool | None + """ + + name: Required[str] + """The name of the function to be called. + + Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length + of 64. + """ + + description: str + """ + A description of what the function does, used by the model to choose when and + how to call the function. + """ + + parameters: "JSONObjectInput" + """The parameters the functions accepts, described as a JSON Schema object. + + See the [guide](https://platform.openai.com/docs/guides/function-calling) for + examples, and the + [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for + documentation about the format. + + Omitting `parameters` defines a function with an empty parameter list. + """ + + strict: Optional[bool] + """Whether to enable strict schema adherence when generating the function call. + + If set to true, the model will follow the exact schema defined in the + `parameters` field. Only a subset of JSON Schema is supported when `strict` is + `true`. Learn more about Structured Outputs in the + [function calling guide](https://platform.openai.com/docs/guides/function-calling). + """ + + +from .json_object_input import JSONObjectInput diff --git a/src/dedalus_labs/types/shared_params/json_object_input.py b/src/dedalus_labs/types/shared_params/json_object_input.py new file mode 100644 index 0000000..df1f9c3 --- /dev/null +++ b/src/dedalus_labs/types/shared_params/json_object_input.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Optional +from typing_extensions import TypeAliasType + +__all__ = ["JSONObjectInput"] + +from .json_value_input import JSONValueInput + +JSONObjectInput = TypeAliasType("JSONObjectInput", Dict[str, Optional[JSONValueInput]]) diff --git a/src/dedalus_labs/types/shared_params/json_value_input.py b/src/dedalus_labs/types/shared_params/json_value_input.py new file mode 100644 index 0000000..67731df --- /dev/null +++ b/src/dedalus_labs/types/shared_params/json_value_input.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Optional +from typing_extensions import TypeAliasType + +from ..._types import SequenceNotStr + +__all__ = ["JSONValueInput"] + +JSONValueInput = TypeAliasType( + "JSONValueInput", + Union[str, float, bool, Dict[str, Optional["JSONValueInput"]], SequenceNotStr[Optional["JSONValueInput"]]], +) diff --git a/src/dedalus_labs/types/shared_params/mcp_credentials.py b/src/dedalus_labs/types/shared_params/mcp_credentials.py new file mode 100644 index 0000000..8b02381 --- /dev/null +++ b/src/dedalus_labs/types/shared_params/mcp_credentials.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Union +from typing_extensions import Required, TypeAlias, TypedDict + +__all__ = ["MCPCredentials", "Credential"] + + +class Credential(TypedDict, total=False): + """Credential for MCP server authentication. + + Passed at endpoint level (e.g., chat.completions.create) and matched + to MCP servers by connection name. Wire format matches dedalus_mcp.Credential.to_dict(). + """ + + connection_name: Required[str] + """Connection name. Must match a connection in MCPServer.connections.""" + + values: Required[Dict[str, Union[str, int, bool]]] + """Credential values. Keys are credential field names, values are the secrets.""" + + +MCPCredentials: TypeAlias = List[Credential] diff --git a/src/dedalus_labs/types/shared_params/mcp_server_spec.py b/src/dedalus_labs/types/shared_params/mcp_server_spec.py new file mode 100644 index 0000000..1dbb6d1 --- /dev/null +++ b/src/dedalus_labs/types/shared_params/mcp_server_spec.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Optional +from typing_extensions import Required, TypedDict + +__all__ = ["MCPServerSpec"] + + +class MCPServerSpec(TypedDict, total=False): + """Structured MCP server specification. + + Slug-based: {"slug": "dedalus-labs/brave-search", "name": "github-integration", "version": "v1.0.0"} + URL-based: {"url": "https://mcp.dedaluslabs.ai/acme/my-server/mcp", "name": "custom-server"} + """ + + name: Required[str] + """Server instance name for credential matching.""" + + credentials: Optional[Dict[str, str]] + """Encrypted credential blobs keyed by connection name. + + Values are base64url ciphertext produced by the SDK (client-side encryption with + the AS public key). + """ + + slug: Optional[str] + """Marketplace identifier.""" + + url: Optional[str] + """Direct URL to MCP server endpoint (Pro users).""" + + version: Optional[str] + """Version constraint for slug-based servers.""" diff --git a/src/dedalus_labs/types/shared_params/mcp_servers.py b/src/dedalus_labs/types/shared_params/mcp_servers.py new file mode 100644 index 0000000..d055fa3 --- /dev/null +++ b/src/dedalus_labs/types/shared_params/mcp_servers.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union +from typing_extensions import TypeAlias + +from .mcp_server_spec import MCPServerSpec + +__all__ = ["MCPServers", "MCPServerItem"] + +MCPServerItem: TypeAlias = Union[str, MCPServerSpec] + +MCPServers: TypeAlias = List[MCPServerItem] diff --git a/src/dedalus_labs/types/shared_params/model_settings.py b/src/dedalus_labs/types/shared_params/model_settings.py new file mode 100644 index 0000000..01f98f0 --- /dev/null +++ b/src/dedalus_labs/types/shared_params/model_settings.py @@ -0,0 +1,124 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Iterable, Optional +from typing_extensions import Literal, TypedDict + +from ..._types import SequenceNotStr +from .reasoning import Reasoning +from .tool_choice import ToolChoice + +__all__ = ["ModelSettings"] + + +class ModelSettings(TypedDict, total=False): + attributes: Dict[str, object] + + audio: Optional["JSONObjectInput"] + + deferred: Optional[bool] + + extra_args: Optional[Dict[str, object]] + + extra_headers: Optional[Dict[str, str]] + + extra_query: Optional[Dict[str, object]] + + frequency_penalty: Optional[float] + + generation_config: Optional["JSONObjectInput"] + + include_usage: Optional[bool] + + input_audio_format: Optional[str] + + input_audio_transcription: Optional["JSONObjectInput"] + + logit_bias: Optional[Dict[str, int]] + + logprobs: Optional[bool] + + max_completion_tokens: Optional[int] + + max_tokens: Optional[int] + + metadata: Optional[Dict[str, str]] + + modalities: Optional[SequenceNotStr[str]] + + n: Optional[int] + + output_audio_format: Optional[str] + + parallel_tool_calls: Optional[bool] + + prediction: Optional["JSONObjectInput"] + + presence_penalty: Optional[float] + + prompt_cache_key: Optional[str] + + reasoning: Optional[Reasoning] + """**gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + """ + + reasoning_effort: Optional[str] + + response_format: Optional["JSONObjectInput"] + + safety_identifier: Optional[str] + + safety_settings: Optional[Iterable["JSONObjectInput"]] + + search_parameters: Optional["JSONObjectInput"] + + seed: Optional[int] + + service_tier: Optional[str] + + stop: Union[str, SequenceNotStr[str], None] + + store: Optional[bool] + + stream: Optional[bool] + + stream_options: Optional["JSONObjectInput"] + + structured_output: object + + system_instruction: Optional["JSONObjectInput"] + + temperature: Optional[float] + + thinking: Optional["JSONObjectInput"] + + timeout: Optional[float] + + tool_choice: Optional[ToolChoice] + + tool_config: Optional["JSONObjectInput"] + + top_k: Optional[int] + + top_logprobs: Optional[int] + + top_p: Optional[float] + + truncation: Optional[Literal["auto", "disabled"]] + + turn_detection: Optional["JSONObjectInput"] + + user: Optional[str] + + verbosity: Optional[str] + + voice: Optional[str] + + web_search_options: Optional["JSONObjectInput"] + + +from .json_object_input import JSONObjectInput diff --git a/src/dedalus_labs/types/shared_params/reasoning.py b/src/dedalus_labs/types/shared_params/reasoning.py new file mode 100644 index 0000000..ea0bb7c --- /dev/null +++ b/src/dedalus_labs/types/shared_params/reasoning.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Optional +from typing_extensions import Literal, TypeAlias, TypedDict + +__all__ = ["Reasoning"] + + +class ReasoningTyped(TypedDict, total=False): + """**gpt-5 and o-series models only** + + Configuration options for + [reasoning models](https://platform.openai.com/docs/guides/reasoning). + """ + + effort: Optional[Literal["none", "minimal", "low", "medium", "high", "xhigh"]] + + generate_summary: Optional[Literal["auto", "concise", "detailed"]] + + summary: Optional[Literal["auto", "concise", "detailed"]] + + +Reasoning: TypeAlias = Union[ReasoningTyped, Dict[str, object]] diff --git a/src/dedalus_labs/types/shared_params/response_format_json_object.py b/src/dedalus_labs/types/shared_params/response_format_json_object.py index d4d1dea..0b46d5b 100644 --- a/src/dedalus_labs/types/shared_params/response_format_json_object.py +++ b/src/dedalus_labs/types/shared_params/response_format_json_object.py @@ -8,5 +8,16 @@ class ResponseFormatJSONObject(TypedDict, total=False): + """JSON object response format. + + An older method of generating JSON responses. + Using `json_schema` is recommended for models that support it. Note that the + model will not generate JSON without a system or user message instructing it + to do so. + + Fields: + - type (required): Literal["json_object"] + """ + type: Required[Literal["json_object"]] """The type of response format being defined. Always `json_object`.""" diff --git a/src/dedalus_labs/types/shared_params/response_format_json_schema.py b/src/dedalus_labs/types/shared_params/response_format_json_schema.py index 5b0a13e..5ec2bc3 100644 --- a/src/dedalus_labs/types/shared_params/response_format_json_schema.py +++ b/src/dedalus_labs/types/shared_params/response_format_json_schema.py @@ -2,13 +2,15 @@ from __future__ import annotations -from typing import Dict, Optional +from typing import Optional from typing_extensions import Literal, Required, TypedDict __all__ = ["ResponseFormatJSONSchema", "JSONSchema"] class JSONSchema(TypedDict, total=False): + """Structured Outputs configuration options, including a JSON Schema.""" + name: Required[str] """The name of the response format. @@ -22,7 +24,7 @@ class JSONSchema(TypedDict, total=False): how to respond in the format. """ - schema: Dict[str, object] + schema: "JSONObjectInput" """ The schema for the response format, described as a JSON Schema object. Learn how to build JSON schemas [here](https://json-schema.org/). @@ -39,8 +41,21 @@ class JSONSchema(TypedDict, total=False): class ResponseFormatJSONSchema(TypedDict, total=False): + """JSON Schema response format. + + Used to generate structured JSON responses. + Learn more about [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs). + + Fields: + - type (required): Literal["json_schema"] + - json_schema (required): JSONSchema + """ + json_schema: Required[JSONSchema] """Structured Outputs configuration options, including a JSON Schema.""" type: Required[Literal["json_schema"]] """The type of response format being defined. Always `json_schema`.""" + + +from .json_object_input import JSONObjectInput diff --git a/src/dedalus_labs/types/shared_params/response_format_text.py b/src/dedalus_labs/types/shared_params/response_format_text.py index c3ef2b0..64cdcc2 100644 --- a/src/dedalus_labs/types/shared_params/response_format_text.py +++ b/src/dedalus_labs/types/shared_params/response_format_text.py @@ -8,5 +8,11 @@ class ResponseFormatText(TypedDict, total=False): + """Default response format. Used to generate text responses. + + Fields: + - type (required): Literal["text"] + """ + type: Required[Literal["text"]] """The type of response format being defined. Always `text`.""" diff --git a/src/dedalus_labs/types/shared_params/tool_choice.py b/src/dedalus_labs/types/shared_params/tool_choice.py new file mode 100644 index 0000000..16ebad3 --- /dev/null +++ b/src/dedalus_labs/types/shared_params/tool_choice.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union +from typing_extensions import Literal, Required, TypeAlias, TypedDict + +__all__ = ["ToolChoice", "MCPToolChoice"] + + +class MCPToolChoice(TypedDict, total=False): + name: Required[str] + + server_label: Required[str] + + +ToolChoice: TypeAlias = Union[Literal["auto", "required", "none"], str, Dict[str, object], MCPToolChoice] diff --git a/src/dedalus_sdk/lib/.keep b/src/dedalus_sdk/lib/.keep deleted file mode 100644 index 5e2c99f..0000000 --- a/src/dedalus_sdk/lib/.keep +++ /dev/null @@ -1,4 +0,0 @@ -File generated from our OpenAPI spec by Stainless. - -This directory can be used to store custom files to expand the SDK. -It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. \ No newline at end of file diff --git a/tests/api_resources/audio/test_speech.py b/tests/api_resources/audio/test_speech.py index bdd52bc..b85b94e 100644 --- a/tests/api_resources/audio/test_speech.py +++ b/tests/api_resources/audio/test_speech.py @@ -28,9 +28,9 @@ class TestSpeech: def test_method_create(self, client: Dedalus, respx_mock: MockRouter) -> None: respx_mock.post("/v1/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) speech = client.audio.speech.create( - input="Hello, how are you today?", - model="openai/tts-1", - voice="alloy", + input="input", + model="string", + voice="string", ) assert speech.is_closed assert speech.json() == {"foo": "bar"} @@ -42,12 +42,12 @@ def test_method_create(self, client: Dedalus, respx_mock: MockRouter) -> None: def test_method_create_with_all_params(self, client: Dedalus, respx_mock: MockRouter) -> None: respx_mock.post("/v1/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) speech = client.audio.speech.create( - input="Hello, how are you today?", - model="openai/tts-1", - voice="alloy", + input="input", + model="string", + voice="string", instructions="instructions", response_format="mp3", - speed=1, + speed=0.25, stream_format="sse", ) assert speech.is_closed @@ -61,9 +61,9 @@ def test_raw_response_create(self, client: Dedalus, respx_mock: MockRouter) -> N respx_mock.post("/v1/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) speech = client.audio.speech.with_raw_response.create( - input="Hello, how are you today?", - model="openai/tts-1", - voice="alloy", + input="input", + model="string", + voice="string", ) assert speech.is_closed is True @@ -76,9 +76,9 @@ def test_raw_response_create(self, client: Dedalus, respx_mock: MockRouter) -> N def test_streaming_response_create(self, client: Dedalus, respx_mock: MockRouter) -> None: respx_mock.post("/v1/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) with client.audio.speech.with_streaming_response.create( - input="Hello, how are you today?", - model="openai/tts-1", - voice="alloy", + input="input", + model="string", + voice="string", ) as speech: assert not speech.is_closed assert speech.http_request.headers.get("X-Stainless-Lang") == "python" @@ -100,9 +100,9 @@ class TestAsyncSpeech: async def test_method_create(self, async_client: AsyncDedalus, respx_mock: MockRouter) -> None: respx_mock.post("/v1/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) speech = await async_client.audio.speech.create( - input="Hello, how are you today?", - model="openai/tts-1", - voice="alloy", + input="input", + model="string", + voice="string", ) assert speech.is_closed assert await speech.json() == {"foo": "bar"} @@ -114,12 +114,12 @@ async def test_method_create(self, async_client: AsyncDedalus, respx_mock: MockR async def test_method_create_with_all_params(self, async_client: AsyncDedalus, respx_mock: MockRouter) -> None: respx_mock.post("/v1/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) speech = await async_client.audio.speech.create( - input="Hello, how are you today?", - model="openai/tts-1", - voice="alloy", + input="input", + model="string", + voice="string", instructions="instructions", response_format="mp3", - speed=1, + speed=0.25, stream_format="sse", ) assert speech.is_closed @@ -133,9 +133,9 @@ async def test_raw_response_create(self, async_client: AsyncDedalus, respx_mock: respx_mock.post("/v1/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) speech = await async_client.audio.speech.with_raw_response.create( - input="Hello, how are you today?", - model="openai/tts-1", - voice="alloy", + input="input", + model="string", + voice="string", ) assert speech.is_closed is True @@ -148,9 +148,9 @@ async def test_raw_response_create(self, async_client: AsyncDedalus, respx_mock: async def test_streaming_response_create(self, async_client: AsyncDedalus, respx_mock: MockRouter) -> None: respx_mock.post("/v1/audio/speech").mock(return_value=httpx.Response(200, json={"foo": "bar"})) async with async_client.audio.speech.with_streaming_response.create( - input="Hello, how are you today?", - model="openai/tts-1", - voice="alloy", + input="input", + model="string", + voice="string", ) as speech: assert not speech.is_closed assert speech.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/chat/test_completions.py b/tests/api_resources/chat/test_completions.py index 87def64..d9c8383 100644 --- a/tests/api_resources/chat/test_completions.py +++ b/tests/api_resources/chat/test_completions.py @@ -9,7 +9,9 @@ from tests.utils import assert_matches_type from dedalus_labs import Dedalus, AsyncDedalus -from dedalus_labs.types.chat import Completion +from dedalus_labs.types.chat import ( + ChatCompletion, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -21,139 +23,144 @@ class TestCompletions: @parametrize def test_method_create_overload_1(self, client: Dedalus) -> None: completion = client.chat.completions.create( - model="openai/gpt-4", + model="openai/gpt-5", ) - assert_matches_type(Completion, completion, path=["response"]) + assert_matches_type(ChatCompletion, completion, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create_with_all_params_overload_1(self, client: Dedalus) -> None: completion = client.chat.completions.create( - model="openai/gpt-4", + model="openai/gpt-5", agent_attributes={ "accuracy": 0.9, "complexity": 0.8, - "efficiency": 0.7, }, audio={ - "format": "bar", - "voice": "bar", + "format": "wav", + "voice": "string", }, - auto_execute_tools=True, - deferred=True, - disable_automatic_function_calling=True, - frequency_penalty=-0.5, - function_call="string", - functions=[{"foo": "bar"}], - generation_config={ - "candidateCount": "bar", - "responseMimeType": "bar", + automatic_tool_execution=True, + cached_content="cached_content", + credentials={ + "connection_name": "external-service", + "values": {"api_key": "sk-..."}, }, + deferred=True, + frequency_penalty=-2, + function_call="function_call", + functions=[ + { + "name": "name", + "description": "description", + "parameters": {"foo": "string"}, + } + ], + generation_config={"foo": "string"}, guardrails=[{"foo": "bar"}], handoff_config={"foo": "bar"}, - input="Translate this paragraph into French.", - instructions="You are a concise assistant.", - logit_bias={"50256": -100}, + logit_bias={"foo": 0}, logprobs=True, - max_completion_tokens=1000, - max_tokens=100, + max_completion_tokens=0, + max_tokens=1, max_turns=5, - mcp_servers=["dedalus-labs/brave-search", "dedalus-labs/github-api"], + mcp_servers="dedalus-labs/example-server", messages=[ { - "content": "bar", - "role": "bar", + "content": "string", + "role": "developer", + "name": "name", } ], - metadata={ - "session": "abc", - "user_id": "123", - }, - modalities=["text"], + metadata={"foo": "string"}, + modalities=["string"], model_attributes={ - "anthropic/claude-3-5-sonnet": { - "cost": 0.7, - "creativity": 0.8, - "intelligence": 0.95, - }, - "openai/gpt-4": { - "cost": 0.8, - "intelligence": 0.9, + "gpt-5": { + "accuracy": 0.95, "speed": 0.6, - }, - "openai/gpt-4o-mini": { - "cost": 0.2, - "intelligence": 0.7, - "speed": 0.9, - }, + } }, n=1, parallel_tool_calls=True, - prediction={"foo": "bar"}, - presence_penalty=-0.5, + prediction={ + "content": "string", + "type": "content", + }, + presence_penalty=-2, prompt_cache_key="prompt_cache_key", - reasoning_effort="medium", + prompt_cache_retention="prompt_cache_retention", + prompt_mode="reasoning", + reasoning_effort="reasoning_effort", response_format={"type": "text"}, + safe_prompt=True, safety_identifier="safety_identifier", safety_settings=[ { - "category": "bar", - "threshold": "bar", + "category": "HARM_CATEGORY_UNSPECIFIED", + "threshold": "HARM_BLOCK_THRESHOLD_UNSPECIFIED", } ], - search_parameters={"foo": "bar"}, - seed=42, - service_tier="auto", - stop=["\n", "END"], + search_parameters={"foo": "string"}, + seed=0, + service_tier="service_tier", + stop=["string"], store=True, stream=False, - stream_options={"include_usage": "bar"}, - system="You are a helpful assistant.", + stream_options={"foo": "string"}, + system_instruction={"foo": "string"}, temperature=0, thinking={ - "budget_tokens": 2048, + "budget_tokens": 1024, "type": "enabled", }, - tool_choice="auto", - tool_config={"function_calling_config": "bar"}, + tool_choice={ + "type": "auto", + "disable_parallel_tool_use": True, + }, + tool_config={"foo": "string"}, tools=[ { - "function": "bar", - "type": "bar", + "function": { + "name": "name", + "description": "description", + "parameters": {"foo": "string"}, + "strict": True, + }, + "type": "function", } ], - top_k=40, - top_logprobs=5, - top_p=0.1, - user="user-123", - verbosity="low", - web_search_options={"foo": "bar"}, + top_k=0, + top_logprobs=0, + top_p=0, + user="user", + verbosity="verbosity", + web_search_options={"foo": "string"}, ) - assert_matches_type(Completion, completion, path=["response"]) + assert_matches_type(ChatCompletion, completion, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_create_overload_1(self, client: Dedalus) -> None: response = client.chat.completions.with_raw_response.create( - model="openai/gpt-4", + model="openai/gpt-5", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" completion = response.parse() - assert_matches_type(Completion, completion, path=["response"]) + assert_matches_type(ChatCompletion, completion, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_create_overload_1(self, client: Dedalus) -> None: with client.chat.completions.with_streaming_response.create( - model="openai/gpt-4", + model="openai/gpt-5", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" completion = response.parse() - assert_matches_type(Completion, completion, path=["response"]) + assert_matches_type(ChatCompletion, completion, path=["response"]) assert cast(Any, response.is_closed) is True @@ -161,7 +168,7 @@ def test_streaming_response_create_overload_1(self, client: Dedalus) -> None: @parametrize def test_method_create_overload_2(self, client: Dedalus) -> None: completion_stream = client.chat.completions.create( - model="openai/gpt-4", + model="openai/gpt-5", stream=True, ) completion_stream.response.close() @@ -170,105 +177,110 @@ def test_method_create_overload_2(self, client: Dedalus) -> None: @parametrize def test_method_create_with_all_params_overload_2(self, client: Dedalus) -> None: completion_stream = client.chat.completions.create( - model="openai/gpt-4", + model="openai/gpt-5", stream=True, agent_attributes={ "accuracy": 0.9, "complexity": 0.8, - "efficiency": 0.7, }, audio={ - "format": "bar", - "voice": "bar", + "format": "wav", + "voice": "string", }, - auto_execute_tools=True, - deferred=True, - disable_automatic_function_calling=True, - frequency_penalty=-0.5, - function_call="string", - functions=[{"foo": "bar"}], - generation_config={ - "candidateCount": "bar", - "responseMimeType": "bar", + automatic_tool_execution=True, + cached_content="cached_content", + credentials={ + "connection_name": "external-service", + "values": {"api_key": "sk-..."}, }, + deferred=True, + frequency_penalty=-2, + function_call="function_call", + functions=[ + { + "name": "name", + "description": "description", + "parameters": {"foo": "string"}, + } + ], + generation_config={"foo": "string"}, guardrails=[{"foo": "bar"}], handoff_config={"foo": "bar"}, - input="Translate this paragraph into French.", - instructions="You are a concise assistant.", - logit_bias={"50256": -100}, + logit_bias={"foo": 0}, logprobs=True, - max_completion_tokens=1000, - max_tokens=100, + max_completion_tokens=0, + max_tokens=1, max_turns=5, - mcp_servers=["dedalus-labs/brave-search", "dedalus-labs/github-api"], + mcp_servers="dedalus-labs/example-server", messages=[ { - "content": "bar", - "role": "bar", + "content": "string", + "role": "developer", + "name": "name", } ], - metadata={ - "session": "abc", - "user_id": "123", - }, - modalities=["text"], + metadata={"foo": "string"}, + modalities=["string"], model_attributes={ - "anthropic/claude-3-5-sonnet": { - "cost": 0.7, - "creativity": 0.8, - "intelligence": 0.95, - }, - "openai/gpt-4": { - "cost": 0.8, - "intelligence": 0.9, + "gpt-5": { + "accuracy": 0.95, "speed": 0.6, - }, - "openai/gpt-4o-mini": { - "cost": 0.2, - "intelligence": 0.7, - "speed": 0.9, - }, + } }, n=1, parallel_tool_calls=True, - prediction={"foo": "bar"}, - presence_penalty=-0.5, + prediction={ + "content": "string", + "type": "content", + }, + presence_penalty=-2, prompt_cache_key="prompt_cache_key", - reasoning_effort="medium", + prompt_cache_retention="prompt_cache_retention", + prompt_mode="reasoning", + reasoning_effort="reasoning_effort", response_format={"type": "text"}, + safe_prompt=True, safety_identifier="safety_identifier", safety_settings=[ { - "category": "bar", - "threshold": "bar", + "category": "HARM_CATEGORY_UNSPECIFIED", + "threshold": "HARM_BLOCK_THRESHOLD_UNSPECIFIED", } ], - search_parameters={"foo": "bar"}, - seed=42, - service_tier="auto", - stop=["\n", "END"], + search_parameters={"foo": "string"}, + seed=0, + service_tier="service_tier", + stop=["string"], store=True, - stream_options={"include_usage": "bar"}, - system="You are a helpful assistant.", + stream_options={"foo": "string"}, + system_instruction={"foo": "string"}, temperature=0, thinking={ - "budget_tokens": 2048, + "budget_tokens": 1024, "type": "enabled", }, - tool_choice="auto", - tool_config={"function_calling_config": "bar"}, + tool_choice={ + "type": "auto", + "disable_parallel_tool_use": True, + }, + tool_config={"foo": "string"}, tools=[ { - "function": "bar", - "type": "bar", + "function": { + "name": "name", + "description": "description", + "parameters": {"foo": "string"}, + "strict": True, + }, + "type": "function", } ], - top_k=40, - top_logprobs=5, - top_p=0.1, - user="user-123", - verbosity="low", - web_search_options={"foo": "bar"}, + top_k=0, + top_logprobs=0, + top_p=0, + user="user", + verbosity="verbosity", + web_search_options={"foo": "string"}, ) completion_stream.response.close() @@ -276,7 +288,7 @@ def test_method_create_with_all_params_overload_2(self, client: Dedalus) -> None @parametrize def test_raw_response_create_overload_2(self, client: Dedalus) -> None: response = client.chat.completions.with_raw_response.create( - model="openai/gpt-4", + model="openai/gpt-5", stream=True, ) @@ -288,7 +300,7 @@ def test_raw_response_create_overload_2(self, client: Dedalus) -> None: @parametrize def test_streaming_response_create_overload_2(self, client: Dedalus) -> None: with client.chat.completions.with_streaming_response.create( - model="openai/gpt-4", + model="openai/gpt-5", stream=True, ) as response: assert not response.is_closed @@ -309,139 +321,144 @@ class TestAsyncCompletions: @parametrize async def test_method_create_overload_1(self, async_client: AsyncDedalus) -> None: completion = await async_client.chat.completions.create( - model="openai/gpt-4", + model="openai/gpt-5", ) - assert_matches_type(Completion, completion, path=["response"]) + assert_matches_type(ChatCompletion, completion, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create_with_all_params_overload_1(self, async_client: AsyncDedalus) -> None: completion = await async_client.chat.completions.create( - model="openai/gpt-4", + model="openai/gpt-5", agent_attributes={ "accuracy": 0.9, "complexity": 0.8, - "efficiency": 0.7, }, audio={ - "format": "bar", - "voice": "bar", + "format": "wav", + "voice": "string", }, - auto_execute_tools=True, - deferred=True, - disable_automatic_function_calling=True, - frequency_penalty=-0.5, - function_call="string", - functions=[{"foo": "bar"}], - generation_config={ - "candidateCount": "bar", - "responseMimeType": "bar", + automatic_tool_execution=True, + cached_content="cached_content", + credentials={ + "connection_name": "external-service", + "values": {"api_key": "sk-..."}, }, + deferred=True, + frequency_penalty=-2, + function_call="function_call", + functions=[ + { + "name": "name", + "description": "description", + "parameters": {"foo": "string"}, + } + ], + generation_config={"foo": "string"}, guardrails=[{"foo": "bar"}], handoff_config={"foo": "bar"}, - input="Translate this paragraph into French.", - instructions="You are a concise assistant.", - logit_bias={"50256": -100}, + logit_bias={"foo": 0}, logprobs=True, - max_completion_tokens=1000, - max_tokens=100, + max_completion_tokens=0, + max_tokens=1, max_turns=5, - mcp_servers=["dedalus-labs/brave-search", "dedalus-labs/github-api"], + mcp_servers="dedalus-labs/example-server", messages=[ { - "content": "bar", - "role": "bar", + "content": "string", + "role": "developer", + "name": "name", } ], - metadata={ - "session": "abc", - "user_id": "123", - }, - modalities=["text"], + metadata={"foo": "string"}, + modalities=["string"], model_attributes={ - "anthropic/claude-3-5-sonnet": { - "cost": 0.7, - "creativity": 0.8, - "intelligence": 0.95, - }, - "openai/gpt-4": { - "cost": 0.8, - "intelligence": 0.9, + "gpt-5": { + "accuracy": 0.95, "speed": 0.6, - }, - "openai/gpt-4o-mini": { - "cost": 0.2, - "intelligence": 0.7, - "speed": 0.9, - }, + } }, n=1, parallel_tool_calls=True, - prediction={"foo": "bar"}, - presence_penalty=-0.5, + prediction={ + "content": "string", + "type": "content", + }, + presence_penalty=-2, prompt_cache_key="prompt_cache_key", - reasoning_effort="medium", + prompt_cache_retention="prompt_cache_retention", + prompt_mode="reasoning", + reasoning_effort="reasoning_effort", response_format={"type": "text"}, + safe_prompt=True, safety_identifier="safety_identifier", safety_settings=[ { - "category": "bar", - "threshold": "bar", + "category": "HARM_CATEGORY_UNSPECIFIED", + "threshold": "HARM_BLOCK_THRESHOLD_UNSPECIFIED", } ], - search_parameters={"foo": "bar"}, - seed=42, - service_tier="auto", - stop=["\n", "END"], + search_parameters={"foo": "string"}, + seed=0, + service_tier="service_tier", + stop=["string"], store=True, stream=False, - stream_options={"include_usage": "bar"}, - system="You are a helpful assistant.", + stream_options={"foo": "string"}, + system_instruction={"foo": "string"}, temperature=0, thinking={ - "budget_tokens": 2048, + "budget_tokens": 1024, "type": "enabled", }, - tool_choice="auto", - tool_config={"function_calling_config": "bar"}, + tool_choice={ + "type": "auto", + "disable_parallel_tool_use": True, + }, + tool_config={"foo": "string"}, tools=[ { - "function": "bar", - "type": "bar", + "function": { + "name": "name", + "description": "description", + "parameters": {"foo": "string"}, + "strict": True, + }, + "type": "function", } ], - top_k=40, - top_logprobs=5, - top_p=0.1, - user="user-123", - verbosity="low", - web_search_options={"foo": "bar"}, + top_k=0, + top_logprobs=0, + top_p=0, + user="user", + verbosity="verbosity", + web_search_options={"foo": "string"}, ) - assert_matches_type(Completion, completion, path=["response"]) + assert_matches_type(ChatCompletion, completion, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_create_overload_1(self, async_client: AsyncDedalus) -> None: response = await async_client.chat.completions.with_raw_response.create( - model="openai/gpt-4", + model="openai/gpt-5", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" completion = await response.parse() - assert_matches_type(Completion, completion, path=["response"]) + assert_matches_type(ChatCompletion, completion, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_create_overload_1(self, async_client: AsyncDedalus) -> None: async with async_client.chat.completions.with_streaming_response.create( - model="openai/gpt-4", + model="openai/gpt-5", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" completion = await response.parse() - assert_matches_type(Completion, completion, path=["response"]) + assert_matches_type(ChatCompletion, completion, path=["response"]) assert cast(Any, response.is_closed) is True @@ -449,7 +466,7 @@ async def test_streaming_response_create_overload_1(self, async_client: AsyncDed @parametrize async def test_method_create_overload_2(self, async_client: AsyncDedalus) -> None: completion_stream = await async_client.chat.completions.create( - model="openai/gpt-4", + model="openai/gpt-5", stream=True, ) await completion_stream.response.aclose() @@ -458,105 +475,110 @@ async def test_method_create_overload_2(self, async_client: AsyncDedalus) -> Non @parametrize async def test_method_create_with_all_params_overload_2(self, async_client: AsyncDedalus) -> None: completion_stream = await async_client.chat.completions.create( - model="openai/gpt-4", + model="openai/gpt-5", stream=True, agent_attributes={ "accuracy": 0.9, "complexity": 0.8, - "efficiency": 0.7, }, audio={ - "format": "bar", - "voice": "bar", + "format": "wav", + "voice": "string", }, - auto_execute_tools=True, - deferred=True, - disable_automatic_function_calling=True, - frequency_penalty=-0.5, - function_call="string", - functions=[{"foo": "bar"}], - generation_config={ - "candidateCount": "bar", - "responseMimeType": "bar", + automatic_tool_execution=True, + cached_content="cached_content", + credentials={ + "connection_name": "external-service", + "values": {"api_key": "sk-..."}, }, + deferred=True, + frequency_penalty=-2, + function_call="function_call", + functions=[ + { + "name": "name", + "description": "description", + "parameters": {"foo": "string"}, + } + ], + generation_config={"foo": "string"}, guardrails=[{"foo": "bar"}], handoff_config={"foo": "bar"}, - input="Translate this paragraph into French.", - instructions="You are a concise assistant.", - logit_bias={"50256": -100}, + logit_bias={"foo": 0}, logprobs=True, - max_completion_tokens=1000, - max_tokens=100, + max_completion_tokens=0, + max_tokens=1, max_turns=5, - mcp_servers=["dedalus-labs/brave-search", "dedalus-labs/github-api"], + mcp_servers="dedalus-labs/example-server", messages=[ { - "content": "bar", - "role": "bar", + "content": "string", + "role": "developer", + "name": "name", } ], - metadata={ - "session": "abc", - "user_id": "123", - }, - modalities=["text"], + metadata={"foo": "string"}, + modalities=["string"], model_attributes={ - "anthropic/claude-3-5-sonnet": { - "cost": 0.7, - "creativity": 0.8, - "intelligence": 0.95, - }, - "openai/gpt-4": { - "cost": 0.8, - "intelligence": 0.9, + "gpt-5": { + "accuracy": 0.95, "speed": 0.6, - }, - "openai/gpt-4o-mini": { - "cost": 0.2, - "intelligence": 0.7, - "speed": 0.9, - }, + } }, n=1, parallel_tool_calls=True, - prediction={"foo": "bar"}, - presence_penalty=-0.5, + prediction={ + "content": "string", + "type": "content", + }, + presence_penalty=-2, prompt_cache_key="prompt_cache_key", - reasoning_effort="medium", + prompt_cache_retention="prompt_cache_retention", + prompt_mode="reasoning", + reasoning_effort="reasoning_effort", response_format={"type": "text"}, + safe_prompt=True, safety_identifier="safety_identifier", safety_settings=[ { - "category": "bar", - "threshold": "bar", + "category": "HARM_CATEGORY_UNSPECIFIED", + "threshold": "HARM_BLOCK_THRESHOLD_UNSPECIFIED", } ], - search_parameters={"foo": "bar"}, - seed=42, - service_tier="auto", - stop=["\n", "END"], + search_parameters={"foo": "string"}, + seed=0, + service_tier="service_tier", + stop=["string"], store=True, - stream_options={"include_usage": "bar"}, - system="You are a helpful assistant.", + stream_options={"foo": "string"}, + system_instruction={"foo": "string"}, temperature=0, thinking={ - "budget_tokens": 2048, + "budget_tokens": 1024, "type": "enabled", }, - tool_choice="auto", - tool_config={"function_calling_config": "bar"}, + tool_choice={ + "type": "auto", + "disable_parallel_tool_use": True, + }, + tool_config={"foo": "string"}, tools=[ { - "function": "bar", - "type": "bar", + "function": { + "name": "name", + "description": "description", + "parameters": {"foo": "string"}, + "strict": True, + }, + "type": "function", } ], - top_k=40, - top_logprobs=5, - top_p=0.1, - user="user-123", - verbosity="low", - web_search_options={"foo": "bar"}, + top_k=0, + top_logprobs=0, + top_p=0, + user="user", + verbosity="verbosity", + web_search_options={"foo": "string"}, ) await completion_stream.response.aclose() @@ -564,7 +586,7 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn @parametrize async def test_raw_response_create_overload_2(self, async_client: AsyncDedalus) -> None: response = await async_client.chat.completions.with_raw_response.create( - model="openai/gpt-4", + model="openai/gpt-5", stream=True, ) @@ -576,7 +598,7 @@ async def test_raw_response_create_overload_2(self, async_client: AsyncDedalus) @parametrize async def test_streaming_response_create_overload_2(self, async_client: AsyncDedalus) -> None: async with async_client.chat.completions.with_streaming_response.create( - model="openai/gpt-4", + model="openai/gpt-5", stream=True, ) as response: assert not response.is_closed diff --git a/tests/api_resources/test_health.py b/tests/api_resources/test_health.py deleted file mode 100644 index d0719c2..0000000 --- a/tests/api_resources/test_health.py +++ /dev/null @@ -1,80 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from dedalus_labs import Dedalus, AsyncDedalus -from dedalus_labs.types import HealthCheckResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestHealth: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_method_check(self, client: Dedalus) -> None: - health = client.health.check() - assert_matches_type(HealthCheckResponse, health, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_raw_response_check(self, client: Dedalus) -> None: - response = client.health.with_raw_response.check() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - health = response.parse() - assert_matches_type(HealthCheckResponse, health, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_streaming_response_check(self, client: Dedalus) -> None: - with client.health.with_streaming_response.check() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - health = response.parse() - assert_matches_type(HealthCheckResponse, health, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncHealth: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_method_check(self, async_client: AsyncDedalus) -> None: - health = await async_client.health.check() - assert_matches_type(HealthCheckResponse, health, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_raw_response_check(self, async_client: AsyncDedalus) -> None: - response = await async_client.health.with_raw_response.check() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - health = await response.parse() - assert_matches_type(HealthCheckResponse, health, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_streaming_response_check(self, async_client: AsyncDedalus) -> None: - async with async_client.health.with_streaming_response.check() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - health = await response.parse() - assert_matches_type(HealthCheckResponse, health, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_root.py b/tests/api_resources/test_root.py deleted file mode 100644 index a5ac01a..0000000 --- a/tests/api_resources/test_root.py +++ /dev/null @@ -1,80 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from tests.utils import assert_matches_type -from dedalus_labs import Dedalus, AsyncDedalus -from dedalus_labs.types import RootGetResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestRoot: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_method_get(self, client: Dedalus) -> None: - root = client.root.get() - assert_matches_type(RootGetResponse, root, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_raw_response_get(self, client: Dedalus) -> None: - response = client.root.with_raw_response.get() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - root = response.parse() - assert_matches_type(RootGetResponse, root, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_streaming_response_get(self, client: Dedalus) -> None: - with client.root.with_streaming_response.get() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - root = response.parse() - assert_matches_type(RootGetResponse, root, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncRoot: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_method_get(self, async_client: AsyncDedalus) -> None: - root = await async_client.root.get() - assert_matches_type(RootGetResponse, root, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_raw_response_get(self, async_client: AsyncDedalus) -> None: - response = await async_client.root.with_raw_response.get() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - root = await response.parse() - assert_matches_type(RootGetResponse, root, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_streaming_response_get(self, async_client: AsyncDedalus) -> None: - async with async_client.root.with_streaming_response.get() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - root = await response.parse() - assert_matches_type(RootGetResponse, root, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py new file mode 100644 index 0000000..56e116a --- /dev/null +++ b/tests/lib/__init__.py @@ -0,0 +1 @@ +# Structured outputs test suite diff --git a/tests/lib/chat/__init__.py b/tests/lib/chat/__init__.py new file mode 100644 index 0000000..af1da48 --- /dev/null +++ b/tests/lib/chat/__init__.py @@ -0,0 +1 @@ +# Chat completions structured outputs tests diff --git a/tests/lib/chat/fixtures/__init__.py b/tests/lib/chat/fixtures/__init__.py new file mode 100644 index 0000000..c8b3788 --- /dev/null +++ b/tests/lib/chat/fixtures/__init__.py @@ -0,0 +1 @@ +# Test fixtures for chat completions diff --git a/tests/lib/chat/fixtures/streaming_basic.txt b/tests/lib/chat/fixtures/streaming_basic.txt new file mode 100644 index 0000000..a97ba7b --- /dev/null +++ b/tests/lib/chat/fixtures/streaming_basic.txt @@ -0,0 +1,11 @@ +data: {"id":"chatcmpl-test123","object":"chat.completion.chunk","created":1727346142,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-test123","object":"chat.completion.chunk","created":1727346142,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"Hello"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-test123","object":"chat.completion.chunk","created":1727346142,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":" there"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-test123","object":"chat.completion.chunk","created":1727346142,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"!"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-test123","object":"chat.completion.chunk","created":1727346142,"model":"gpt-4o","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"system_fingerprint":"fp_test"} + +data: [DONE] diff --git a/tests/lib/chat/fixtures/streaming_max_tokens.txt b/tests/lib/chat/fixtures/streaming_max_tokens.txt new file mode 100644 index 0000000..f71ddf6 --- /dev/null +++ b/tests/lib/chat/fixtures/streaming_max_tokens.txt @@ -0,0 +1,7 @@ +data: {"id":"chatcmpl-length123","object":"chat.completion.chunk","created":1727346163,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-length123","object":"chat.completion.chunk","created":1727346163,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"{\""},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-length123","object":"chat.completion.chunk","created":1727346163,"model":"gpt-4o","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"length"}],"system_fingerprint":"fp_test","usage":{"prompt_tokens":79,"completion_tokens":1,"total_tokens":80}} + +data: [DONE] diff --git a/tests/lib/chat/fixtures/streaming_refusal.txt b/tests/lib/chat/fixtures/streaming_refusal.txt new file mode 100644 index 0000000..4cab7a7 --- /dev/null +++ b/tests/lib/chat/fixtures/streaming_refusal.txt @@ -0,0 +1,21 @@ +data: {"id":"chatcmpl-refusal123","object":"chat.completion.chunk","created":1727346164,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant","content":null,"refusal":""},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-refusal123","object":"chat.completion.chunk","created":1727346164,"model":"gpt-4o","choices":[{"index":0,"delta":{"refusal":"I'm"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-refusal123","object":"chat.completion.chunk","created":1727346164,"model":"gpt-4o","choices":[{"index":0,"delta":{"refusal":" sorry"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-refusal123","object":"chat.completion.chunk","created":1727346164,"model":"gpt-4o","choices":[{"index":0,"delta":{"refusal":","},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-refusal123","object":"chat.completion.chunk","created":1727346164,"model":"gpt-4o","choices":[{"index":0,"delta":{"refusal":" I"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-refusal123","object":"chat.completion.chunk","created":1727346164,"model":"gpt-4o","choices":[{"index":0,"delta":{"refusal":" can't"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-refusal123","object":"chat.completion.chunk","created":1727346164,"model":"gpt-4o","choices":[{"index":0,"delta":{"refusal":" assist"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-refusal123","object":"chat.completion.chunk","created":1727346164,"model":"gpt-4o","choices":[{"index":0,"delta":{"refusal":" with"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-refusal123","object":"chat.completion.chunk","created":1727346164,"model":"gpt-4o","choices":[{"index":0,"delta":{"refusal":" that."},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-refusal123","object":"chat.completion.chunk","created":1727346164,"model":"gpt-4o","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"system_fingerprint":"fp_test","usage":{"prompt_tokens":79,"completion_tokens":12,"total_tokens":91}} + +data: [DONE] diff --git a/tests/lib/chat/fixtures/streaming_structured.txt b/tests/lib/chat/fixtures/streaming_structured.txt new file mode 100644 index 0000000..7949024 --- /dev/null +++ b/tests/lib/chat/fixtures/streaming_structured.txt @@ -0,0 +1,31 @@ +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"{"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"\"city\""},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":":"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"\"San Francisco\""},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"\"temperature\""},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":":"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"65"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"\"units\""},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":":"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"\"f\""},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{"content":"}"},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-struct123","object":"chat.completion.chunk","created":1727346143,"model":"gpt-4o","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"system_fingerprint":"fp_test","usage":{"prompt_tokens":79,"completion_tokens":14,"total_tokens":93}} + +data: [DONE] diff --git a/tests/lib/chat/fixtures/streaming_tool_call.txt b/tests/lib/chat/fixtures/streaming_tool_call.txt new file mode 100644 index 0000000..525fa95 --- /dev/null +++ b/tests/lib/chat/fixtures/streaming_tool_call.txt @@ -0,0 +1,31 @@ +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_abc123","type":"function","function":{"name":"GetWeatherArgs","arguments":""}}]},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{"}}]},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"city\""}}]},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":":"}}]},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"Edinburgh\""}}]},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":","}}]},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"country\""}}]},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":":"}}]},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"UK\""}}]},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":","}}]},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"units\""}}]},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":":"}}]},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"c\""}}]},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"}"}}]},"logprobs":null,"finish_reason":null}],"system_fingerprint":"fp_test"} + +data: {"id":"chatcmpl-tool123","object":"chat.completion.chunk","created":1727346165,"model":"gpt-4o","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}],"system_fingerprint":"fp_test","usage":{"prompt_tokens":76,"completion_tokens":24,"total_tokens":100}} + +data: [DONE] diff --git a/tests/lib/chat/helpers.py b/tests/lib/chat/helpers.py new file mode 100644 index 0000000..bddcadf --- /dev/null +++ b/tests/lib/chat/helpers.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +import os +from typing import TypeVar, Iterator +from typing_extensions import AsyncIterator + +_T = TypeVar("_T") + + +def load_fixture(fixture_name: str) -> str: + """Load a fixture file from the fixtures directory.""" + current_dir = os.path.dirname(os.path.abspath(__file__)) + fixtures_dir = os.path.join(current_dir, "fixtures") + with open(os.path.join(fixtures_dir, fixture_name), "r") as f: + return f.read() + + +def get_response(fixture_name: str) -> Iterator[bytes]: + """Convert a fixture file into a stream of bytes for testing.""" + content = load_fixture(fixture_name) + for line in content.splitlines(): + yield line.encode() + b"\n" + + +async def to_async_iter(iter: Iterator[_T]) -> AsyncIterator[_T]: + """Convert a synchronous iterator to an asynchronous one.""" + for event in iter: + yield event diff --git a/tests/lib/chat/test_completions.py b/tests/lib/chat/test_completions.py new file mode 100644 index 0000000..8f1c3cb --- /dev/null +++ b/tests/lib/chat/test_completions.py @@ -0,0 +1,282 @@ +"""Tests for non-streaming structured outputs via parse().""" + +from __future__ import annotations + +import json +from typing import List +from typing_extensions import Literal + +import httpx +import pytest +from respx import MockRouter +from pydantic import BaseModel + +from dedalus_labs import Dedalus, AsyncDedalus +from dedalus_labs.lib._tools import pydantic_function_tool +from dedalus_labs.types.chat.parsed_chat_completion import ( + ParsedChatCompletion, + ParsedChoice, + ParsedChatCompletionMessage, +) + +from ...conftest import base_url + + +class Location(BaseModel): + city: str + temperature: float + units: Literal["c", "f"] + + +class CalendarEvent(BaseModel): + name: str + date: str + participants: List[str] + + +class GetWeatherArgs(BaseModel): + """Get the temperature for the given country/city combo""" + + city: str + country: str + units: Literal["c", "f"] = "c" + + +def make_completion_response( + content: str | None = None, + refusal: str | None = None, + tool_calls: list | None = None, + finish_reason: str = "stop", +) -> dict: + """Create a mock chat completion response.""" + message: dict = {"role": "assistant"} + if content is not None: + message["content"] = content + if refusal is not None: + message["refusal"] = refusal + if tool_calls is not None: + message["tool_calls"] = tool_calls + + return { + "id": "chatcmpl-test123", + "object": "chat.completion", + "created": 1727346143, + "model": "gpt-4o", + "choices": [ + { + "index": 0, + "message": message, + "logprobs": None, + "finish_reason": finish_reason, + } + ], + "usage": { + "prompt_tokens": 79, + "completion_tokens": 14, + "total_tokens": 93, + }, + "system_fingerprint": "fp_test", + } + + +class TestSyncParse: + @pytest.mark.respx(base_url=base_url) + def test_parse_pydantic_model(self, client: Dedalus, respx_mock: MockRouter) -> None: + """Test parsing a response into a Pydantic model.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + json=make_completion_response( + content='{"city":"San Francisco","temperature":65,"units":"f"}' + ), + ) + ) + + completion = client.chat.completions.parse( + model="gpt-4o", + messages=[{"role": "user", "content": "What's the weather like in SF?"}], + response_format=Location, + ) + + assert isinstance(completion, ParsedChatCompletion) + assert len(completion.choices) == 1 + + message = completion.choices[0].message + assert message.parsed is not None + assert isinstance(message.parsed, Location) + assert message.parsed.city == "San Francisco" + assert message.parsed.temperature == 65.0 + assert message.parsed.units == "f" + + @pytest.mark.respx(base_url=base_url) + def test_parse_pydantic_model_with_list(self, client: Dedalus, respx_mock: MockRouter) -> None: + """Test parsing a response with a list field.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + json=make_completion_response( + content='{"name":"Science Fair","date":"Friday","participants":["Alice","Bob"]}' + ), + ) + ) + + completion = client.chat.completions.parse( + model="gpt-4o", + messages=[ + {"role": "system", "content": "Extract the event information."}, + {"role": "user", "content": "Alice and Bob are going to a science fair on Friday."}, + ], + response_format=CalendarEvent, + ) + + assert completion.choices[0].message.parsed is not None + parsed = completion.choices[0].message.parsed + assert isinstance(parsed, CalendarEvent) + assert parsed.name == "Science Fair" + assert parsed.date == "Friday" + assert parsed.participants == ["Alice", "Bob"] + + @pytest.mark.respx(base_url=base_url) + def test_parse_without_response_format(self, client: Dedalus, respx_mock: MockRouter) -> None: + """Test parse() without a response_format returns parsed=None.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + json=make_completion_response(content="Hello! How can I help you today?"), + ) + ) + + completion = client.chat.completions.parse( + model="gpt-4o", + messages=[{"role": "user", "content": "Hello"}], + ) + + assert completion.choices[0].message.content == "Hello! How can I help you today?" + assert completion.choices[0].message.parsed is None + + @pytest.mark.respx(base_url=base_url) + def test_parse_refusal(self, client: Dedalus, respx_mock: MockRouter) -> None: + """Test handling of model refusals.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + json=make_completion_response( + content=None, refusal="I'm sorry, I can't assist with that." + ), + ) + ) + + completion = client.chat.completions.parse( + model="gpt-4o", + messages=[{"role": "user", "content": "How do I make something dangerous?"}], + response_format=Location, + ) + + message = completion.choices[0].message + assert message.refusal == "I'm sorry, I can't assist with that." + assert message.parsed is None + assert message.content is None + + @pytest.mark.respx(base_url=base_url) + def test_parse_pydantic_tool(self, client: Dedalus, respx_mock: MockRouter) -> None: + """Test parsing tool calls with Pydantic models.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + json=make_completion_response( + content=None, + tool_calls=[ + { + "id": "call_abc123", + "type": "function", + "function": { + "name": "GetWeatherArgs", + "arguments": '{"city":"Edinburgh","country":"UK","units":"c"}', + }, + } + ], + finish_reason="tool_calls", + ), + ) + ) + + completion = client.chat.completions.parse( + model="gpt-4o", + messages=[{"role": "user", "content": "What's the weather like in Edinburgh?"}], + tools=[pydantic_function_tool(GetWeatherArgs)], + ) + + message = completion.choices[0].message + assert message.tool_calls is not None + assert len(message.tool_calls) == 1 + + tool_call = message.tool_calls[0] + assert tool_call.function.name == "GetWeatherArgs" + assert tool_call.function.parsed_arguments is not None + assert isinstance(tool_call.function.parsed_arguments, GetWeatherArgs) + assert tool_call.function.parsed_arguments.city == "Edinburgh" + assert tool_call.function.parsed_arguments.country == "UK" + assert tool_call.function.parsed_arguments.units == "c" + + +class TestAsyncParse: + @pytest.mark.asyncio + @pytest.mark.respx(base_url=base_url) + async def test_parse_pydantic_model(self, async_client: AsyncDedalus, respx_mock: MockRouter) -> None: + """Test async parsing a response into a Pydantic model.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + json=make_completion_response( + content='{"city":"San Francisco","temperature":65,"units":"f"}' + ), + ) + ) + + completion = await async_client.chat.completions.parse( + model="gpt-4o", + messages=[{"role": "user", "content": "What's the weather like in SF?"}], + response_format=Location, + ) + + assert isinstance(completion, ParsedChatCompletion) + message = completion.choices[0].message + assert message.parsed is not None + assert isinstance(message.parsed, Location) + assert message.parsed.city == "San Francisco" + + @pytest.mark.asyncio + @pytest.mark.respx(base_url=base_url) + async def test_parse_pydantic_tool(self, async_client: AsyncDedalus, respx_mock: MockRouter) -> None: + """Test async parsing tool calls with Pydantic models.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + json=make_completion_response( + content=None, + tool_calls=[ + { + "id": "call_abc123", + "type": "function", + "function": { + "name": "GetWeatherArgs", + "arguments": '{"city":"Edinburgh","country":"UK","units":"c"}', + }, + } + ], + finish_reason="tool_calls", + ), + ) + ) + + completion = await async_client.chat.completions.parse( + model="gpt-4o", + messages=[{"role": "user", "content": "What's the weather like in Edinburgh?"}], + tools=[pydantic_function_tool(GetWeatherArgs)], + ) + + message = completion.choices[0].message + assert message.tool_calls is not None + tool_call = message.tool_calls[0] + assert isinstance(tool_call.function.parsed_arguments, GetWeatherArgs) + assert tool_call.function.parsed_arguments.city == "Edinburgh" diff --git a/tests/lib/chat/test_completions_streaming.py b/tests/lib/chat/test_completions_streaming.py new file mode 100644 index 0000000..f050c77 --- /dev/null +++ b/tests/lib/chat/test_completions_streaming.py @@ -0,0 +1,325 @@ +"""Tests for streaming structured outputs via stream().""" + +from __future__ import annotations + +from typing import List, Generic, Iterator, cast +from typing_extensions import Literal, TypeVar + +import httpx +import pytest +from respx import MockRouter +from pydantic import BaseModel + +from dedalus_labs import Dedalus, AsyncDedalus +from dedalus_labs.lib._tools import pydantic_function_tool +from dedalus_labs.lib.streaming.chat import ( + ChatCompletionStream, + ChatCompletionStreamEvent, + ChatCompletionStreamState, + ContentDoneEvent, + RefusalDoneEvent, + FunctionToolCallArgumentsDoneEvent, +) +from dedalus_labs._exceptions import LengthFinishReasonError + +from .helpers import get_response, to_async_iter +from ...conftest import base_url + + +ResponseFormatT = TypeVar("ResponseFormatT") + + +class Location(BaseModel): + city: str + temperature: float + units: Literal["c", "f"] + + +class GetWeatherArgs(BaseModel): + """Get the temperature for the given country/city combo""" + + city: str + country: str + units: Literal["c", "f"] = "c" + + +class StreamListener(Generic[ResponseFormatT]): + """Helper to collect stream events for testing.""" + + def __init__(self, stream: ChatCompletionStream[ResponseFormatT]) -> None: + self.stream = stream + self.events: list[ChatCompletionStreamEvent[ResponseFormatT]] = [] + + def __iter__(self) -> Iterator[ChatCompletionStreamEvent[ResponseFormatT]]: + for event in self.stream: + self.events.append(event) + yield event + + def get_event_by_type(self, event_type: str) -> ChatCompletionStreamEvent[ResponseFormatT] | None: + return next((e for e in self.events if e.type == event_type), None) + + +class TestSyncStream: + @pytest.mark.respx(base_url=base_url) + def test_stream_basic(self, client: Dedalus, respx_mock: MockRouter) -> None: + """Test basic streaming without structured output.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + content=b"".join(get_response("streaming_basic.txt")), + headers={"content-type": "text/event-stream"}, + ) + ) + + with client.chat.completions.stream( + model="gpt-4o", + messages=[{"role": "user", "content": "Say hello there!"}], + ) as stream: + listener = StreamListener(stream) + for _ in listener: + pass + + completion = stream.get_final_completion() + assert completion.choices[0].message.content == "Hello there!" + assert completion.choices[0].finish_reason == "stop" + + content_done = listener.get_event_by_type("content.done") + assert content_done is not None + assert content_done.content == "Hello there!" # type: ignore + + @pytest.mark.respx(base_url=base_url) + def test_stream_pydantic_model(self, client: Dedalus, respx_mock: MockRouter) -> None: + """Test streaming with Pydantic model parsing.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + content=b"".join(get_response("streaming_structured.txt")), + headers={"content-type": "text/event-stream"}, + ) + ) + + with client.chat.completions.stream( + model="gpt-4o", + messages=[{"role": "user", "content": "What's the weather like in SF?"}], + response_format=Location, + ) as stream: + listener = StreamListener(stream) + for _ in listener: + pass + + completion = stream.get_final_completion() + message = completion.choices[0].message + + assert message.parsed is not None + assert isinstance(message.parsed, Location) + assert message.parsed.city == "San Francisco" + assert message.parsed.temperature == 65 + assert message.parsed.units == "f" + + # Verify content.done event has parsed model + content_done = listener.get_event_by_type("content.done") + assert content_done is not None + assert isinstance(content_done, ContentDoneEvent) + assert isinstance(content_done.parsed, Location) + assert content_done.parsed.city == "San Francisco" + + @pytest.mark.respx(base_url=base_url) + def test_stream_pydantic_tool(self, client: Dedalus, respx_mock: MockRouter) -> None: + """Test streaming with Pydantic tool call parsing.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + content=b"".join(get_response("streaming_tool_call.txt")), + headers={"content-type": "text/event-stream"}, + ) + ) + + with client.chat.completions.stream( + model="gpt-4o", + messages=[{"role": "user", "content": "What's the weather like in Edinburgh?"}], + tools=[pydantic_function_tool(GetWeatherArgs)], + ) as stream: + listener = StreamListener(stream) + for _ in listener: + pass + + completion = stream.get_final_completion() + message = completion.choices[0].message + + assert message.tool_calls is not None + assert len(message.tool_calls) == 1 + + tool_call = message.tool_calls[0] + assert tool_call.function.name == "GetWeatherArgs" + assert tool_call.function.parsed_arguments is not None + assert isinstance(tool_call.function.parsed_arguments, GetWeatherArgs) + assert tool_call.function.parsed_arguments.city == "Edinburgh" + assert tool_call.function.parsed_arguments.country == "UK" + assert tool_call.function.parsed_arguments.units == "c" + + # Verify tool_calls.function.arguments.done event + tool_done = listener.get_event_by_type("tool_calls.function.arguments.done") + assert tool_done is not None + assert isinstance(tool_done, FunctionToolCallArgumentsDoneEvent) + assert tool_done.name == "GetWeatherArgs" + assert isinstance(tool_done.parsed_arguments, GetWeatherArgs) + + @pytest.mark.respx(base_url=base_url) + def test_stream_refusal(self, client: Dedalus, respx_mock: MockRouter) -> None: + """Test streaming refusal handling.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + content=b"".join(get_response("streaming_refusal.txt")), + headers={"content-type": "text/event-stream"}, + ) + ) + + with client.chat.completions.stream( + model="gpt-4o", + messages=[{"role": "user", "content": "How do I make something dangerous?"}], + response_format=Location, + ) as stream: + listener = StreamListener(stream) + for _ in listener: + pass + + completion = stream.get_final_completion() + message = completion.choices[0].message + + assert message.refusal == "I'm sorry, I can't assist with that." + assert message.parsed is None + + # Verify refusal.done event + refusal_done = listener.get_event_by_type("refusal.done") + assert refusal_done is not None + assert isinstance(refusal_done, RefusalDoneEvent) + assert refusal_done.refusal == "I'm sorry, I can't assist with that." + + @pytest.mark.respx(base_url=base_url) + def test_stream_max_tokens_error(self, client: Dedalus, respx_mock: MockRouter) -> None: + """Test that LengthFinishReasonError is raised when max_tokens is reached with structured output.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + content=b"".join(get_response("streaming_max_tokens.txt")), + headers={"content-type": "text/event-stream"}, + ) + ) + + with pytest.raises(LengthFinishReasonError): + with client.chat.completions.stream( + model="gpt-4o", + messages=[{"role": "user", "content": "What's the weather like in SF?"}], + response_format=Location, + max_tokens=1, + ) as stream: + for _ in stream: + pass + + @pytest.mark.respx(base_url=base_url) + def test_stream_context_manager_cleanup(self, client: Dedalus, respx_mock: MockRouter) -> None: + """Test that context manager properly closes resources.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + content=b"".join(get_response("streaming_basic.txt")), + headers={"content-type": "text/event-stream"}, + ) + ) + + with client.chat.completions.stream( + model="gpt-4o", + messages=[{"role": "user", "content": "Hello"}], + ) as stream: + # Don't consume the stream - just check cleanup + pass + + # Response should be closed after exiting context + assert stream._response.is_closed + + @pytest.mark.respx(base_url=base_url) + def test_stream_state_helper(self, client: Dedalus, respx_mock: MockRouter) -> None: + """Test ChatCompletionStreamState helper for manual accumulation.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + content=b"".join(get_response("streaming_basic.txt")), + headers={"content-type": "text/event-stream"}, + ) + ) + + state = ChatCompletionStreamState() + + response = client.chat.completions.create( + model="gpt-4o", + messages=[{"role": "user", "content": "Say hello there!"}], + stream=True, + ) + + for chunk in response: + state.handle_chunk(chunk) + + completion = state.get_final_completion() + assert completion.choices[0].message.content == "Hello there!" + + +class TestAsyncStream: + @pytest.mark.asyncio + @pytest.mark.respx(base_url=base_url) + async def test_stream_pydantic_model(self, async_client: AsyncDedalus, respx_mock: MockRouter) -> None: + """Test async streaming with Pydantic model parsing.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + content=to_async_iter(get_response("streaming_structured.txt")), + headers={"content-type": "text/event-stream"}, + ) + ) + + events: list[ChatCompletionStreamEvent[Location]] = [] + + async with async_client.chat.completions.stream( + model="gpt-4o", + messages=[{"role": "user", "content": "What's the weather like in SF?"}], + response_format=Location, + ) as stream: + async for event in stream: + events.append(event) + + completion = await stream.get_final_completion() + message = completion.choices[0].message + + assert message.parsed is not None + assert isinstance(message.parsed, Location) + assert message.parsed.city == "San Francisco" + assert message.parsed.temperature == 65 + assert message.parsed.units == "f" + + @pytest.mark.asyncio + @pytest.mark.respx(base_url=base_url) + async def test_stream_pydantic_tool(self, async_client: AsyncDedalus, respx_mock: MockRouter) -> None: + """Test async streaming with Pydantic tool call parsing.""" + respx_mock.post("/v1/chat/completions").mock( + return_value=httpx.Response( + 200, + content=to_async_iter(get_response("streaming_tool_call.txt")), + headers={"content-type": "text/event-stream"}, + ) + ) + + async with async_client.chat.completions.stream( + model="gpt-4o", + messages=[{"role": "user", "content": "What's the weather like in Edinburgh?"}], + tools=[pydantic_function_tool(GetWeatherArgs)], + ) as stream: + async for _ in stream: + pass + + completion = await stream.get_final_completion() + message = completion.choices[0].message + + assert message.tool_calls is not None + tool_call = message.tool_calls[0] + assert isinstance(tool_call.function.parsed_arguments, GetWeatherArgs) + assert tool_call.function.parsed_arguments.city == "Edinburgh" diff --git a/tests/lib/utils.py b/tests/lib/utils.py new file mode 100644 index 0000000..bf31829 --- /dev/null +++ b/tests/lib/utils.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import io +from typing import Any, Iterable +from typing_extensions import TypeAlias + +import rich +import pytest +import pydantic + +ReprArgs: TypeAlias = "Iterable[tuple[str | None, Any]]" + + +def rich_print_str(obj: object) -> str: + """Pretty print an object to a string using rich.""" + buf = io.StringIO() + console = rich.console.Console(file=buf, force_terminal=True, width=120) + console.print(obj) + return buf.getvalue().rstrip("\n") + + +def print_obj(obj: object, monkeypatch: pytest.MonkeyPatch) -> str: + """Pretty print an object to a string with deterministic Pydantic field ordering.""" + original_repr = pydantic.BaseModel.__repr_args__ + + def __repr_args__(self: pydantic.BaseModel) -> ReprArgs: + return sorted(original_repr(self), key=lambda arg: arg[0] or "") + + with monkeypatch.context() as m: + m.setattr(pydantic.BaseModel, "__repr_args__", __repr_args__) + return rich_print_str(obj) diff --git a/tests/test_client.py b/tests/test_client.py index 5f367ea..7939a7d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -92,7 +92,7 @@ def test_copy_default_options(self, client: Dedalus) -> None: # options that have a default are overridden correctly copied = client.copy(max_retries=7) assert copied.max_retries == 7 - assert client.max_retries == 2 + assert client.max_retries == 0 copied2 = copied.copy(max_retries=6) assert copied2.max_retries == 6 @@ -355,7 +355,7 @@ def test_validate_headers(self) -> None: with pytest.raises( TypeError, - match="Could not resolve authentication method. Expected the api_key to be set. Or for the `Authorization` headers to be explicitly omitted", + match="Could not resolve authentication method. Expected either api_key or x_api_key to be set. Or for one of the `Authorization` or `x-api-key` headers to be explicitly omitted", ): client2._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -765,21 +765,21 @@ class Model(BaseModel): "remaining_retries,retry_after,timeout", [ [3, "20", 20], - [3, "0", 0.5], - [3, "-10", 0.5], + [3, "0", 0.1], + [3, "-10", 0.1], [3, "60", 60], - [3, "61", 0.5], + [3, "61", 0.1], [3, "Fri, 29 Sep 2023 16:26:57 GMT", 20], - [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5], - [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5], + [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.1], + [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.1], [3, "Fri, 29 Sep 2023 16:27:37 GMT", 60], - [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5], - [3, "99999999999999999999999999999999999", 0.5], - [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5], - [3, "", 0.5], - [2, "", 0.5 * 2.0], - [1, "", 0.5 * 4.0], - [-1100, "", 8], # test large number potentially overflowing + [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.1], + [3, "99999999999999999999999999999999999", 0.1], + [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.1], + [3, "", 0.1], + [2, "", 0.1 * 2.0], + [1, "", 0.1 * 4.0], + [-1100, "", 3], # test large number potentially overflowing ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @@ -789,25 +789,25 @@ def test_parse_retry_after_header( headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) calculated = client._calculate_retry_timeout(remaining_retries, options, headers) - assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] + assert calculated == pytest.approx(timeout, 0.1 * 0.875) # pyright: ignore[reportUnknownMemberType] @mock.patch("dedalus_labs._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Dedalus) -> None: - respx_mock.get("/health").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.post("/v1/chat/completions").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - client.health.with_streaming_response.check().__enter__() + client.chat.completions.with_streaming_response.create(model="openai/gpt-5").__enter__() assert _get_open_connections(client) == 0 @mock.patch("dedalus_labs._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Dedalus) -> None: - respx_mock.get("/health").mock(return_value=httpx.Response(500)) + respx_mock.post("/v1/chat/completions").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - client.health.with_streaming_response.check().__enter__() + client.chat.completions.with_streaming_response.create(model="openai/gpt-5").__enter__() assert _get_open_connections(client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -834,9 +834,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.get("/health").mock(side_effect=retry_handler) + respx_mock.post("/v1/chat/completions").mock(side_effect=retry_handler) - response = client.health.with_raw_response.check() + response = client.chat.completions.with_raw_response.create(model="openai/gpt-5") assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -858,9 +858,11 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.get("/health").mock(side_effect=retry_handler) + respx_mock.post("/v1/chat/completions").mock(side_effect=retry_handler) - response = client.health.with_raw_response.check(extra_headers={"x-stainless-retry-count": Omit()}) + response = client.chat.completions.with_raw_response.create( + model="openai/gpt-5", extra_headers={"x-stainless-retry-count": Omit()} + ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -881,9 +883,11 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.get("/health").mock(side_effect=retry_handler) + respx_mock.post("/v1/chat/completions").mock(side_effect=retry_handler) - response = client.health.with_raw_response.check(extra_headers={"x-stainless-retry-count": "42"}) + response = client.chat.completions.with_raw_response.create( + model="openai/gpt-5", extra_headers={"x-stainless-retry-count": "42"} + ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" @@ -968,7 +972,7 @@ def test_copy_default_options(self, async_client: AsyncDedalus) -> None: # options that have a default are overridden correctly copied = async_client.copy(max_retries=7) assert copied.max_retries == 7 - assert async_client.max_retries == 2 + assert async_client.max_retries == 0 copied2 = copied.copy(max_retries=6) assert copied2.max_retries == 6 @@ -1235,7 +1239,7 @@ def test_validate_headers(self) -> None: with pytest.raises( TypeError, - match="Could not resolve authentication method. Expected the api_key to be set. Or for the `Authorization` headers to be explicitly omitted", + match="Could not resolve authentication method. Expected either api_key or x_api_key to be set. Or for one of the `Authorization` or `x-api-key` headers to be explicitly omitted", ): client2._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1660,21 +1664,21 @@ class Model(BaseModel): "remaining_retries,retry_after,timeout", [ [3, "20", 20], - [3, "0", 0.5], - [3, "-10", 0.5], + [3, "0", 0.1], + [3, "-10", 0.1], [3, "60", 60], - [3, "61", 0.5], + [3, "61", 0.1], [3, "Fri, 29 Sep 2023 16:26:57 GMT", 20], - [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.5], - [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.5], + [3, "Fri, 29 Sep 2023 16:26:37 GMT", 0.1], + [3, "Fri, 29 Sep 2023 16:26:27 GMT", 0.1], [3, "Fri, 29 Sep 2023 16:27:37 GMT", 60], - [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.5], - [3, "99999999999999999999999999999999999", 0.5], - [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.5], - [3, "", 0.5], - [2, "", 0.5 * 2.0], - [1, "", 0.5 * 4.0], - [-1100, "", 8], # test large number potentially overflowing + [3, "Fri, 29 Sep 2023 16:27:38 GMT", 0.1], + [3, "99999999999999999999999999999999999", 0.1], + [3, "Zun, 29 Sep 2023 16:26:27 GMT", 0.1], + [3, "", 0.1], + [2, "", 0.1 * 2.0], + [1, "", 0.1 * 4.0], + [-1100, "", 3], # test large number potentially overflowing ], ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) @@ -1684,27 +1688,27 @@ async def test_parse_retry_after_header( headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) calculated = async_client._calculate_retry_timeout(remaining_retries, options, headers) - assert calculated == pytest.approx(timeout, 0.5 * 0.875) # pyright: ignore[reportUnknownMemberType] + assert calculated == pytest.approx(timeout, 0.1 * 0.875) # pyright: ignore[reportUnknownMemberType] @mock.patch("dedalus_labs._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) async def test_retrying_timeout_errors_doesnt_leak( self, respx_mock: MockRouter, async_client: AsyncDedalus ) -> None: - respx_mock.get("/health").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.post("/v1/chat/completions").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await async_client.health.with_streaming_response.check().__aenter__() + await async_client.chat.completions.with_streaming_response.create(model="openai/gpt-5").__aenter__() assert _get_open_connections(async_client) == 0 @mock.patch("dedalus_labs._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncDedalus) -> None: - respx_mock.get("/health").mock(return_value=httpx.Response(500)) + respx_mock.post("/v1/chat/completions").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await async_client.health.with_streaming_response.check().__aenter__() + await async_client.chat.completions.with_streaming_response.create(model="openai/gpt-5").__aenter__() assert _get_open_connections(async_client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -1731,9 +1735,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.get("/health").mock(side_effect=retry_handler) + respx_mock.post("/v1/chat/completions").mock(side_effect=retry_handler) - response = await client.health.with_raw_response.check() + response = await client.chat.completions.with_raw_response.create(model="openai/gpt-5") assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1755,9 +1759,11 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.get("/health").mock(side_effect=retry_handler) + respx_mock.post("/v1/chat/completions").mock(side_effect=retry_handler) - response = await client.health.with_raw_response.check(extra_headers={"x-stainless-retry-count": Omit()}) + response = await client.chat.completions.with_raw_response.create( + model="openai/gpt-5", extra_headers={"x-stainless-retry-count": Omit()} + ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -1778,9 +1784,11 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.get("/health").mock(side_effect=retry_handler) + respx_mock.post("/v1/chat/completions").mock(side_effect=retry_handler) - response = await client.health.with_raw_response.check(extra_headers={"x-stainless-retry-count": "42"}) + response = await client.chat.completions.with_raw_response.create( + model="openai/gpt-5", extra_headers={"x-stainless-retry-count": "42"} + ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" diff --git a/tests/test_encryption.py b/tests/test_encryption.py new file mode 100644 index 0000000..c1e09ea --- /dev/null +++ b/tests/test_encryption.py @@ -0,0 +1,256 @@ +# ============================================================================== +# © 2025 Dedalus Labs, Inc. and affiliates +# Licensed under MIT +# github.com/dedalus-labs/dedalus-sdk-python/LICENSE +# ============================================================================== + +"""Tests for credential encryption (envelope v1 format).""" + +from __future__ import annotations + +import base64 +import json +from typing import Any + +import pytest + +# Skip all tests if cryptography is not installed +pytest.importorskip("cryptography") + +from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.ciphers.aead import AESGCM +from cryptography.hazmat.backends import default_backend + +from dedalus_labs.lib.crypto.encryption import ( + jwk_to_public_key, + encrypt_credentials, +) + + +# Envelope v1 constants (must match encryption.py) +_ENVELOPE_VERSION = 0x01 +_NONCE_LEN = 12 +_TAG_LEN = 16 + + +def _b64url_encode(data: bytes) -> str: + """Base64url encode without padding (test helper).""" + return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii") + + +def _b64url_decode(s: str) -> bytes: + """Base64url decode with padding restoration (test helper).""" + pad = 4 - len(s) % 4 + if pad != 4: + s += "=" * pad + return base64.urlsafe_b64decode(s) + + +@pytest.fixture +def rsa_keypair() -> tuple[Any, Any]: + """Generate RSA keypair for testing.""" + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + backend=default_backend(), + ) + return private_key, private_key.public_key() + + +@pytest.fixture +def rsa_keypair_3072() -> tuple[Any, Any]: + """Generate 3072-bit RSA keypair (production size).""" + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=3072, + backend=default_backend(), + ) + return private_key, private_key.public_key() + + +@pytest.fixture +def rsa_jwk(rsa_keypair: tuple[Any, Any]) -> dict[str, Any]: + """Create JWK from keypair.""" + _, public_key = rsa_keypair + numbers = public_key.public_numbers() + + n_bytes = numbers.n.to_bytes((numbers.n.bit_length() + 7) // 8, "big") + e_bytes = numbers.e.to_bytes((numbers.e.bit_length() + 7) // 8, "big") + + return { + "kty": "RSA", + "use": "enc", + "kid": "test-key-1", + "n": _b64url_encode(n_bytes), + "e": _b64url_encode(e_bytes), + } + + +def decrypt_envelope_v1(private_key: Any, envelope: bytes) -> bytes: + """Decrypt envelope v1 format (test helper).""" + key_size = private_key.key_size // 8 + + version = envelope[0] + assert version == _ENVELOPE_VERSION, f"Expected version 0x01, got 0x{version:02x}" + + wrapped_key = envelope[1 : 1 + key_size] + nonce = envelope[1 + key_size : 1 + key_size + _NONCE_LEN] + ciphertext_with_tag = envelope[1 + key_size + _NONCE_LEN :] + + aes_key = private_key.decrypt( + wrapped_key, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None, + ), + ) + + return AESGCM(aes_key).decrypt(nonce, ciphertext_with_tag, None) + + +class TestJwkToPublicKey: + """Test JWK to public key conversion.""" + + def test_valid_jwk(self, rsa_jwk: dict[str, Any], rsa_keypair: tuple[Any, Any]) -> None: + """Convert valid JWK to public key.""" + _, expected_public = rsa_keypair + public_key = jwk_to_public_key(rsa_jwk) + + assert public_key.public_numbers().n == expected_public.public_numbers().n + assert public_key.public_numbers().e == expected_public.public_numbers().e + + def test_wrong_kty_raises(self) -> None: + """Raise on non-RSA key type.""" + with pytest.raises(ValueError, match="expected RSA key type"): + jwk_to_public_key({"kty": "EC", "n": "xxx", "e": "xxx"}) + + def test_missing_n_raises(self, rsa_jwk: dict[str, Any]) -> None: + """Raise on missing n parameter.""" + del rsa_jwk["n"] + with pytest.raises(ValueError, match="missing required JWK field"): + jwk_to_public_key(rsa_jwk) + + def test_small_key_rejected(self) -> None: + """Reject keys smaller than minimum size.""" + small_key = rsa.generate_private_key(65537, 1024, default_backend()) + numbers = small_key.public_key().public_numbers() + n_bytes = numbers.n.to_bytes((numbers.n.bit_length() + 7) // 8, "big") + e_bytes = numbers.e.to_bytes((numbers.e.bit_length() + 7) // 8, "big") + + jwk = {"kty": "RSA", "n": _b64url_encode(n_bytes), "e": _b64url_encode(e_bytes)} + + with pytest.raises(ValueError, match="below minimum"): + jwk_to_public_key(jwk, min_key_size=2048) + + +class TestEncryptCredentials: + """Test credential encryption (envelope v1).""" + + def test_envelope_format(self, rsa_keypair: tuple[Any, Any]) -> None: + """Encrypt produces valid envelope v1 format.""" + private_key, public_key = rsa_keypair + credentials = {"token": "ghp_xxx123"} + + ciphertext_b64 = encrypt_credentials(public_key, credentials) + envelope = _b64url_decode(ciphertext_b64) + + key_size = private_key.key_size // 8 + min_len = 1 + key_size + _NONCE_LEN + _TAG_LEN + assert len(envelope) >= min_len + assert envelope[0] == _ENVELOPE_VERSION + + def test_roundtrip(self, rsa_keypair: tuple[Any, Any]) -> None: + """Encrypted credentials can be decrypted with private key.""" + private_key, public_key = rsa_keypair + credentials = {"api_key": "sk_test_123", "org_id": "org_456"} + + ciphertext_b64 = encrypt_credentials(public_key, credentials) + envelope = _b64url_decode(ciphertext_b64) + plaintext = decrypt_envelope_v1(private_key, envelope) + + assert json.loads(plaintext) == credentials + + def test_large_payload(self, rsa_keypair: tuple[Any, Any]) -> None: + """Envelope v1 handles payloads larger than RSA limit.""" + private_key, public_key = rsa_keypair + credentials = {"large_token": "x" * 1000, "another": "y" * 500} + + ciphertext_b64 = encrypt_credentials(public_key, credentials) + envelope = _b64url_decode(ciphertext_b64) + plaintext = decrypt_envelope_v1(private_key, envelope) + + assert json.loads(plaintext) == credentials + + def test_randomized(self, rsa_keypair: tuple[Any, Any]) -> None: + """Same plaintext produces different ciphertext each time.""" + _, public_key = rsa_keypair + credentials = {"token": "same_value"} + + ct1 = encrypt_credentials(public_key, credentials) + ct2 = encrypt_credentials(public_key, credentials) + + assert ct1 != ct2 + + def test_with_3072_key(self, rsa_keypair_3072: tuple[Any, Any]) -> None: + """Works with production-size 3072-bit keys.""" + private_key, public_key = rsa_keypair_3072 + credentials = {"token": "production_token"} + + ciphertext_b64 = encrypt_credentials(public_key, credentials) + envelope = _b64url_decode(ciphertext_b64) + plaintext = decrypt_envelope_v1(private_key, envelope) + + assert json.loads(plaintext) == credentials + + +class TestSecurityInvariants: + """Verify security properties.""" + + def test_plaintext_not_in_ciphertext(self, rsa_keypair: tuple[Any, Any]) -> None: + """Plaintext must not appear in ciphertext.""" + _, public_key = rsa_keypair + secret = "ghp_super_secret_token_12345" + + ciphertext = encrypt_credentials(public_key, {"token": secret}) + + assert secret not in ciphertext + assert "ghp_" not in ciphertext + + def test_wrong_key_fails(self, rsa_keypair: tuple[Any, Any]) -> None: + """Decryption fails with wrong private key.""" + _, public_key = rsa_keypair + attacker_key = rsa.generate_private_key(65537, 2048, default_backend()) + + ciphertext_b64 = encrypt_credentials(public_key, {"token": "secret"}) + envelope = _b64url_decode(ciphertext_b64) + + with pytest.raises(Exception): + decrypt_envelope_v1(attacker_key, envelope) + + def test_tampered_ciphertext_fails(self, rsa_keypair: tuple[Any, Any]) -> None: + """GCM authentication rejects tampered ciphertext.""" + private_key, public_key = rsa_keypair + + ciphertext_b64 = encrypt_credentials(public_key, {"token": "test"}) + envelope = bytearray(_b64url_decode(ciphertext_b64)) + + # Tamper with ciphertext portion + key_size = private_key.key_size // 8 + envelope[1 + key_size + _NONCE_LEN + 5] ^= 0xFF + + with pytest.raises(Exception): + decrypt_envelope_v1(private_key, bytes(envelope)) + + def test_tampered_wrapped_key_fails(self, rsa_keypair: tuple[Any, Any]) -> None: + """Tampered wrapped key is rejected.""" + private_key, public_key = rsa_keypair + + ciphertext_b64 = encrypt_credentials(public_key, {"token": "test"}) + envelope = bytearray(_b64url_decode(ciphertext_b64)) + + envelope[10] ^= 0xFF + + with pytest.raises(Exception): + decrypt_envelope_v1(private_key, bytes(envelope)) diff --git a/tests/test_mcp_wire.py b/tests/test_mcp_wire.py new file mode 100644 index 0000000..f7eb810 --- /dev/null +++ b/tests/test_mcp_wire.py @@ -0,0 +1,291 @@ +# ============================================================================== +# © 2025 Dedalus Labs, Inc. and affiliates +# Licensed under MIT +# github.com/dedalus-labs/dedalus-sdk-python/LICENSE +# ============================================================================== + +"""Tests for MCP server wire format serialization.""" + +from __future__ import annotations + +import json +from typing import Any + +import pytest +from pydantic import ValidationError + +from dedalus_labs.lib.mcp import ( + MCPServerWireSpec, + serialize_mcp_servers, + MCPServerProtocol, + is_mcp_server, +) + + +# --- Fixtures ---------------------------------------------------------------- + + +class FakeMCPServer: + """Minimal implementation satisfying MCPServerProtocol.""" + + def __init__(self, name: str, url: str | None = None) -> None: + self._name = name + self._url = url + + @property + def name(self) -> str: + return self._name + + @property + def url(self) -> str | None: + return self._url + + def serve(self, *args: Any, **kwargs: Any) -> None: + pass + + +class IncompleteServer: + """Missing required protocol attributes (no url, no serve).""" + + def __init__(self) -> None: + self.name = "incomplete" + + +# --- MCPServerWireSpec Construction ------------------------------------------ + + +class TestMCPServerWireSpecConstruction: + """Factory methods for creating wire specs.""" + + def test_from_slug_simple(self) -> None: + """Simple marketplace slug.""" + spec = MCPServerWireSpec.from_slug("dedalus-labs/example-server") + assert spec.slug == "dedalus-labs/example-server" + assert spec.version is None + + def test_from_slug_with_version(self) -> None: + """Slug with explicit version parameter.""" + spec = MCPServerWireSpec.from_slug("dedalus-labs/example-server", version="v1.2.0") + assert spec.version == "v1.2.0" + + def test_from_slug_with_embedded_version(self) -> None: + """Slug@version syntax parsed correctly.""" + spec = MCPServerWireSpec.from_slug("dedalus-labs/example-server@v2") + assert spec.slug == "dedalus-labs/example-server" + assert spec.version == "v2" + + def test_from_url(self) -> None: + """Direct URL.""" + spec = MCPServerWireSpec.from_url(url="http://127.0.0.1:8000/mcp") + assert spec.url == "http://127.0.0.1:8000/mcp" + + +# --- MCPServerWireSpec Validation -------------------------------------------- + + +class TestMCPServerWireSpecValidation: + """Pydantic validation rules for wire specs.""" + + def test_requires_slug_or_url(self) -> None: + """Must provide either slug or url.""" + with pytest.raises(ValidationError) as exc_info: + MCPServerWireSpec() + assert "requires either 'slug' or 'url'" in str(exc_info.value) + + def test_rejects_both_slug_and_url(self) -> None: + """Cannot provide both slug and url.""" + with pytest.raises(ValidationError) as exc_info: + MCPServerWireSpec( + slug="dedalus-labs/example-server", + url="http://localhost:8000/mcp", + ) + assert "cannot have both" in str(exc_info.value) + + def test_url_must_start_with_http(self) -> None: + """URL must have http:// or https:// scheme.""" + with pytest.raises(ValidationError) as exc_info: + MCPServerWireSpec(url="localhost:8000/mcp") + assert "must start with http://" in str(exc_info.value) + + def test_https_url_accepted(self) -> None: + """HTTPS URLs are valid.""" + spec = MCPServerWireSpec(url="https://mcp.dedaluslabs.ai/acme/my-server/mcp") + assert spec.url == "https://mcp.dedaluslabs.ai/acme/my-server/mcp" + + def test_localhost_url_accepted(self) -> None: + """Localhost URLs are valid for dev.""" + spec = MCPServerWireSpec(url="http://127.0.0.1:8000/mcp") + assert spec.url == "http://127.0.0.1:8000/mcp" + + def test_slug_format_validation(self) -> None: + """Slug must match org/project pattern.""" + MCPServerWireSpec(slug="dedalus-labs/example-server") + MCPServerWireSpec(slug="org_123/project_456") + MCPServerWireSpec(slug="a/b") + + with pytest.raises(ValidationError): + MCPServerWireSpec(slug="invalid-no-slash") + + with pytest.raises(ValidationError): + MCPServerWireSpec(slug="too/many/slashes") + + def test_slug_with_at_sign_rejected_by_pattern(self) -> None: + """Slug pattern doesn't allow @ - use from_slug() for version parsing.""" + with pytest.raises(ValidationError) as exc_info: + MCPServerWireSpec(slug="org/project@v1", version="v2") + assert "string_pattern_mismatch" in str(exc_info.value).lower() + + # Correct way: use from_slug() which parses the version + spec = MCPServerWireSpec.from_slug("org/project@v1") + assert spec.slug == "org/project" + assert spec.version == "v1" + + def test_extra_fields_forbidden(self) -> None: + """Extra fields rejected (ConfigDict extra='forbid').""" + with pytest.raises(ValidationError) as exc_info: + MCPServerWireSpec(slug="org/test", unknown_field="value") # type: ignore[call-arg] + assert "extra" in str(exc_info.value).lower() + + +# --- MCPServerWireSpec Serialization ----------------------------------------- + + +class TestMCPServerWireSpecSerialization: + """to_wire() output for different spec types.""" + + def test_simple_slug_serializes_to_string(self) -> None: + """Simple slug-only specs serialize to plain string (efficient).""" + spec = MCPServerWireSpec.from_slug("dedalus-labs/example-server") + wire = spec.to_wire() + assert wire == "dedalus-labs/example-server" + assert isinstance(wire, str) + + def test_versioned_slug_serializes_to_dict(self) -> None: + """Slug with version serializes to dict.""" + spec = MCPServerWireSpec.from_slug("dedalus-labs/example-server", version="v1.0.0") + wire = spec.to_wire() + assert wire == {"slug": "dedalus-labs/example-server", "version": "v1.0.0"} + + def test_url_spec_serializes_to_dict(self) -> None: + """URL-based specs serialize to dict with just url.""" + spec = MCPServerWireSpec.from_url(url="http://127.0.0.1:8000/mcp") + wire = spec.to_wire() + assert wire == {"url": "http://127.0.0.1:8000/mcp"} + + def test_serialization_is_json_compatible(self) -> None: + """Wire format round-trips through JSON.""" + spec = MCPServerWireSpec.from_url(url="http://127.0.0.1:8000/mcp") + json_str = json.dumps(spec.to_wire()) + assert '"url": "http://127.0.0.1:8000/mcp"' in json_str + + +# --- MCPServerProtocol ------------------------------------------------------- + + +class TestMCPServerProtocol: + """Structural typing for MCP server objects.""" + + def test_fake_server_satisfies_protocol(self) -> None: + """FakeMCPServer satisfies MCPServerProtocol.""" + server = FakeMCPServer(name="test", url="http://localhost:8000/mcp") + assert is_mcp_server(server) + assert isinstance(server, MCPServerProtocol) + + def test_string_does_not_satisfy_protocol(self) -> None: + """Plain strings are not MCPServerProtocol.""" + assert not is_mcp_server("dedalus-labs/example-server") + + def test_dict_does_not_satisfy_protocol(self) -> None: + """Dicts are not MCPServerProtocol.""" + assert not is_mcp_server({"name": "test", "url": "http://localhost/mcp"}) + + def test_incomplete_server_does_not_satisfy(self) -> None: + """Missing attributes means protocol not satisfied.""" + assert not is_mcp_server(IncompleteServer()) + + +# --- serialize_mcp_servers --------------------------------------------------- + + +class TestSerializeMCPServers: + """End-to-end serialization of mixed mcp_servers input.""" + + def test_none_returns_empty_list(self) -> None: + """None input returns empty list.""" + assert serialize_mcp_servers(None) == [] + + def test_single_string_slug(self) -> None: + """Single slug string passes through.""" + assert serialize_mcp_servers("dedalus-labs/example-server") == ["dedalus-labs/example-server"] + + def test_single_string_url(self) -> None: + """Single URL string passes through.""" + assert serialize_mcp_servers("http://localhost:8000/mcp") == ["http://localhost:8000/mcp"] + + def test_single_mcp_server_object(self) -> None: + """MCPServerProtocol object serializes to URL dict.""" + server = FakeMCPServer(name="calculator", url="http://127.0.0.1:8000/mcp") + result = serialize_mcp_servers(server) + assert result == [{"url": "http://127.0.0.1:8000/mcp"}] + + def test_list_of_slugs(self) -> None: + """List of slug strings.""" + result = serialize_mcp_servers(["dedalus-labs/example-server", "dedalus-labs/weather"]) + assert result == ["dedalus-labs/example-server", "dedalus-labs/weather"] + + def test_versioned_slug_in_list(self) -> None: + """Slug@version syntax expands to dict.""" + result = serialize_mcp_servers(["dedalus-labs/example-server@v2"]) + assert result == [{"slug": "dedalus-labs/example-server", "version": "v2"}] + + def test_mixed_list(self) -> None: + """Mixed list of slugs, URLs, and server objects.""" + server = FakeMCPServer(name="local", url="http://127.0.0.1:8000/mcp") + result = serialize_mcp_servers([server, "dedalus-labs/example-server", "dedalus-labs/weather@v2"]) + + assert len(result) == 3 + assert result[0] == {"url": "http://127.0.0.1:8000/mcp"} + assert result[1] == "dedalus-labs/example-server" + assert result[2] == {"slug": "dedalus-labs/weather", "version": "v2"} + + def test_server_without_url_uses_name_as_slug(self) -> None: + """Server object without URL returns name as slug.""" + server = FakeMCPServer(name="org/my-server", url=None) + result = serialize_mcp_servers(server) + assert result == ["org/my-server"] + + def test_dict_input_validated(self) -> None: + """Dict inputs pass through MCPServerWireSpec validation.""" + result = serialize_mcp_servers([{"slug": "dedalus-labs/test"}]) + assert result == ["dedalus-labs/test"] + + +# --- JSON Compatibility ------------------------------------------------------ + + +class TestJSONCompatibility: + """Wire format is JSON-serializable and API-compatible.""" + + def test_full_payload_structure(self) -> None: + """Complete API payload round-trips through JSON.""" + server = FakeMCPServer(name="calculator", url="http://127.0.0.1:8000/mcp") + wire_data = serialize_mcp_servers([server, "dedalus-labs/example-server", "dedalus-labs/weather@v2"]) + + payload = { + "model": "openai/gpt-5-nano", + "messages": [{"role": "user", "content": "What is 2 + 2?"}], + "mcp_servers": wire_data, + } + + parsed = json.loads(json.dumps(payload)) + assert parsed["mcp_servers"][0] == {"url": "http://127.0.0.1:8000/mcp"} + assert parsed["mcp_servers"][1] == "dedalus-labs/example-server" + assert parsed["mcp_servers"][2]["slug"] == "dedalus-labs/weather" + + def test_unicode_in_url(self) -> None: + """Unicode in URL paths are handled.""" + # Dedalus-hosted URL with unicode in path token + spec = MCPServerWireSpec(url="http://mcp.dedaluslabs.ai/acme/計算機/mcp") + result = spec.to_wire() + json_str = json.dumps(result, ensure_ascii=False) + assert "計算機" in json_str diff --git a/tests/test_mcp_wire_connections.py b/tests/test_mcp_wire_connections.py new file mode 100644 index 0000000..f71d8de --- /dev/null +++ b/tests/test_mcp_wire_connections.py @@ -0,0 +1,308 @@ +# ============================================================================== +# © 2025 Dedalus Labs, Inc. and affiliates +# Licensed under MIT +# github.com/dedalus-labs/dedalus-sdk-python/LICENSE +# ============================================================================== + +"""Tests for Connection/Credential wire format serialization.""" + +from __future__ import annotations + +from typing import Any + +import pytest + +from dedalus_labs.lib.mcp import ( + serialize_connection, + collect_unique_connections, + match_credentials_to_connections, + validate_credentials_for_servers, +) + + +# --- Mock objects for testing --- + + +class MockConnection: + """Mock Connection object implementing the protocol.""" + + def __init__( + self, + name: str, + base_url: str | None = None, + timeout_ms: int = 30000, + ) -> None: + self._name = name + self._base_url = base_url + self._timeout_ms = timeout_ms + + @property + def name(self) -> str: + return self._name + + @property + def base_url(self) -> str | None: + return self._base_url + + @property + def timeout_ms(self) -> int: + return self._timeout_ms + + def to_dict(self) -> dict[str, Any]: + result: dict[str, Any] = {"name": self._name} + if self._base_url is not None: + result["base_url"] = self._base_url + if self._timeout_ms != 30000: + result["timeout_ms"] = self._timeout_ms + return result + + +class MockCredential: + """Mock Secret object implementing the protocol.""" + + def __init__(self, connection: MockConnection, **values: Any) -> None: + self._connection = connection + self._values = values + + @property + def connection(self) -> MockConnection: + return self._connection + + @property + def values(self) -> dict[str, Any]: + return dict(self._values) + + def to_dict(self) -> dict[str, Any]: + return { + "connection_name": self._connection.name, + "values": dict(self._values), + } + + def values_for_encryption(self) -> dict[str, Any]: + return dict(self._values) + + +class TestSerializeConnection: + """Test serialize_connection helper.""" + + def test_with_connection_object(self) -> None: + """Serialize Connection object with to_dict().""" + conn = MockConnection("github", "https://api.github.com", 60000) + + result = serialize_connection(conn) + + assert result["name"] == "github" + assert result["base_url"] == "https://api.github.com" + assert result["timeout_ms"] == 60000 + + def test_with_dict(self) -> None: + """Pass-through for dict input.""" + data = {"name": "dedalus", "base_url": "https://api.dedaluslabs.ai/v1"} + + result = serialize_connection(data) + + assert result == data + + def test_duck_type_extraction(self) -> None: + """Extract fields from object without to_dict().""" + + class BareConnection: + name = "bare" + base_url = "https://bare.api.com" + timeout_ms = 15000 + + result = serialize_connection(BareConnection()) + + assert result["name"] == "bare" + assert result["base_url"] == "https://bare.api.com" + assert result["timeout_ms"] == 15000 + + +class TestMatchSecretsToConnections: + """Test match_credentials_to_connections helper.""" + + def test_basic_matching(self) -> None: + """Match secrets to connections by name.""" + github = MockConnection("github") + dedalus = MockConnection("dedalus") + + github_secret = MockCredential(github, token="ghp_xxx") + dedalus_secret = MockCredential(dedalus, api_key="sk_xxx") + + pairs = match_credentials_to_connections( + [github, dedalus], + [dedalus_secret, github_secret], # Different order + ) + + assert len(pairs) == 2 + # Pairs should be in connection order + assert pairs[0][0].name == "github" + assert pairs[0][1].values == {"token": "ghp_xxx"} + assert pairs[1][0].name == "dedalus" + assert pairs[1][1].values == {"api_key": "sk_xxx"} + + def test_missing_secret_raises(self) -> None: + """Raise ValueError if connection has no secret.""" + github = MockConnection("github") + dedalus = MockConnection("dedalus") + + github_secret = MockCredential(github, token="ghp_xxx") + + with pytest.raises( + ValueError, match="Missing credentials for connections.*dedalus" + ): + match_credentials_to_connections([github, dedalus], [github_secret]) + + def test_with_dict_inputs(self) -> None: + """Works with dict inputs too.""" + connections = [{"name": "api"}] + secrets = [{"connection_name": "api", "values": {"key": "xxx"}}] + + pairs = match_credentials_to_connections(connections, secrets) + + assert len(pairs) == 1 + assert pairs[0][0]["name"] == "api" + assert pairs[0][1]["values"] == {"key": "xxx"} + + def test_missing_multiple_secrets(self) -> None: + """Error message lists all missing secrets.""" + github = MockConnection("github") + dedalus = MockConnection("dedalus") + slack = MockConnection("slack") + + github_secret = MockCredential(github, token="ghp_xxx") + + with pytest.raises(ValueError) as exc: + match_credentials_to_connections([github, dedalus, slack], [github_secret]) + + assert "dedalus" in str(exc.value) + assert "slack" in str(exc.value) + + +# --- Mock server for multi-server tests --- + + +class MockServer: + """Mock MCPServer for testing.""" + + def __init__(self, name: str, connections: list[Any] | None = None) -> None: + self.name = name + self.connections = connections or [] + + +class TestCollectUniqueConnections: + """Test collect_unique_connections helper.""" + + def test_single_server(self) -> None: + """Collect connections from single server.""" + github = MockConnection("github") + dedalus = MockConnection("dedalus") + server = MockServer("bot", connections=[github, dedalus]) + + result = collect_unique_connections([server]) + + assert len(result) == 2 + assert result[0].name == "github" + assert result[1].name == "dedalus" + + def test_shared_connection_deduplicated(self) -> None: + """Shared Connection appears only once.""" + github = MockConnection("github") + + server_a = MockServer("issues", connections=[github]) + server_b = MockServer("prs", connections=[github]) + + result = collect_unique_connections([server_a, server_b]) + + assert len(result) == 1 + assert result[0].name == "github" + + def test_same_name_different_objects(self) -> None: + """Connections with same name are deduplicated.""" + # Even if different objects, same name means same logical connection + github_a = MockConnection("github", base_url="https://api.github.com") + github_b = MockConnection("github", base_url="https://api.github.com") + + server_a = MockServer("a", connections=[github_a]) + server_b = MockServer("b", connections=[github_b]) + + result = collect_unique_connections([server_a, server_b]) + + # Should only include first occurrence + assert len(result) == 1 + assert result[0] is github_a + + def test_multiple_servers_multiple_connections(self) -> None: + """Collect and deduplicate across multiple servers.""" + github = MockConnection("github") + dedalus = MockConnection("dedalus") + slack = MockConnection("slack") + + server_a = MockServer("bot1", connections=[github, dedalus]) + server_b = MockServer("bot2", connections=[github, slack]) + + result = collect_unique_connections([server_a, server_b]) + + assert len(result) == 3 + names = [c.name for c in result] + assert names == ["github", "dedalus", "slack"] + + def test_server_without_connections(self) -> None: + """Handle servers with no connections.""" + server_a = MockServer("empty") + server_b = MockServer("has", connections=[MockConnection("api")]) + + result = collect_unique_connections([server_a, server_b]) + + assert len(result) == 1 + + +class TestValidateSecretsForServers: + """Test validate_credentials_for_servers (main SDK init validation).""" + + def test_all_connections_have_secrets(self) -> None: + """Success when all connections have matching secrets.""" + github = MockConnection("github") + dedalus = MockConnection("dedalus") + + server = MockServer("bot", connections=[github, dedalus]) + + github_secret = MockCredential(github, token="ghp_xxx") + dedalus_secret = MockCredential(dedalus, api_key="sk_xxx") + + pairs = validate_credentials_for_servers( + [server], [github_secret, dedalus_secret] + ) + + assert len(pairs) == 2 + + def test_shared_connection_one_secret(self) -> None: + """One Secret covers shared Connection across servers.""" + github = MockConnection("github") + + server_a = MockServer("issues", connections=[github]) + server_b = MockServer("prs", connections=[github]) + + github_secret = MockCredential(github, token="ghp_xxx") + + pairs = validate_credentials_for_servers( + [server_a, server_b], + [github_secret], # Only one secret needed + ) + + assert len(pairs) == 1 + assert pairs[0][0].name == "github" + + def test_missing_secret_fails_fast(self) -> None: + """Raise immediately if any connection lacks a secret.""" + github = MockConnection("github") + dedalus = MockConnection("dedalus") + + server = MockServer("bot", connections=[github, dedalus]) + github_secret = MockCredential(github, token="ghp_xxx") + + with pytest.raises(ValueError) as exc: + validate_credentials_for_servers([server], [github_secret]) + + assert "dedalus" in str(exc.value) + assert "Missing credentials" in str(exc.value) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..a3a5b7c --- /dev/null +++ b/uv.lock @@ -0,0 +1,2126 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.14' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version < '3.10' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and extra == 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version < '3.10' and extra == 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version < '3.10' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", +] +conflicts = [[ + { package = "dedalus-labs", group = "pydantic-v1" }, + { package = "dedalus-labs", group = "pydantic-v2" }, +]] + +[[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.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/34/939730e66b716b76046dedfe0842995842fa906ccc4964bba414ff69e429/aiohttp-3.13.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2372b15a5f62ed37789a6b383ff7344fc5b9f243999b0cd9b629d8bc5f5b4155", size = 736471, upload-time = "2025-10-28T20:55:27.924Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/dcbdf2df7f6ca72b0bb4c0b4509701f2d8942cf54e29ca197389c214c07f/aiohttp-3.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7f8659a48995edee7229522984bd1009c1213929c769c2daa80b40fe49a180c", size = 493985, upload-time = "2025-10-28T20:55:29.456Z" }, + { url = "https://files.pythonhosted.org/packages/9d/87/71c8867e0a1d0882dcbc94af767784c3cb381c1c4db0943ab4aae4fed65e/aiohttp-3.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:939ced4a7add92296b0ad38892ce62b98c619288a081170695c6babe4f50e636", size = 489274, upload-time = "2025-10-28T20:55:31.134Z" }, + { url = "https://files.pythonhosted.org/packages/38/0f/46c24e8dae237295eaadd113edd56dee96ef6462adf19b88592d44891dc5/aiohttp-3.13.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6315fb6977f1d0dd41a107c527fee2ed5ab0550b7d885bc15fee20ccb17891da", size = 1668171, upload-time = "2025-10-28T20:55:36.065Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c6/4cdfb4440d0e28483681a48f69841fa5e39366347d66ef808cbdadddb20e/aiohttp-3.13.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6e7352512f763f760baaed2637055c49134fd1d35b37c2dedfac35bfe5cf8725", size = 1636036, upload-time = "2025-10-28T20:55:37.576Z" }, + { url = "https://files.pythonhosted.org/packages/84/37/8708cf678628216fb678ab327a4e1711c576d6673998f4f43e86e9ae90dd/aiohttp-3.13.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e09a0a06348a2dd73e7213353c90d709502d9786219f69b731f6caa0efeb46f5", size = 1727975, upload-time = "2025-10-28T20:55:39.457Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2e/3ebfe12fdcb9b5f66e8a0a42dffcd7636844c8a018f261efb2419f68220b/aiohttp-3.13.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a09a6d073fb5789456545bdee2474d14395792faa0527887f2f4ec1a486a59d3", size = 1815823, upload-time = "2025-10-28T20:55:40.958Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4f/ca2ef819488cbb41844c6cf92ca6dd15b9441e6207c58e5ae0e0fc8d70ad/aiohttp-3.13.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b59d13c443f8e049d9e94099c7e412e34610f1f49be0f230ec656a10692a5802", size = 1669374, upload-time = "2025-10-28T20:55:42.745Z" }, + { url = "https://files.pythonhosted.org/packages/f8/fe/1fe2e1179a0d91ce09c99069684aab619bf2ccde9b20bd6ca44f8837203e/aiohttp-3.13.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:20db2d67985d71ca033443a1ba2001c4b5693fe09b0e29f6d9358a99d4d62a8a", size = 1555315, upload-time = "2025-10-28T20:55:44.264Z" }, + { url = "https://files.pythonhosted.org/packages/5a/2b/f3781899b81c45d7cbc7140cddb8a3481c195e7cbff8e36374759d2ab5a5/aiohttp-3.13.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:960c2fc686ba27b535f9fd2b52d87ecd7e4fd1cf877f6a5cba8afb5b4a8bd204", size = 1639140, upload-time = "2025-10-28T20:55:46.626Z" }, + { url = "https://files.pythonhosted.org/packages/72/27/c37e85cd3ece6f6c772e549bd5a253d0c122557b25855fb274224811e4f2/aiohttp-3.13.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6c00dbcf5f0d88796151e264a8eab23de2997c9303dd7c0bf622e23b24d3ce22", size = 1645496, upload-time = "2025-10-28T20:55:48.933Z" }, + { url = "https://files.pythonhosted.org/packages/66/20/3af1ab663151bd3780b123e907761cdb86ec2c4e44b2d9b195ebc91fbe37/aiohttp-3.13.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fed38a5edb7945f4d1bcabe2fcd05db4f6ec7e0e82560088b754f7e08d93772d", size = 1697625, upload-time = "2025-10-28T20:55:50.377Z" }, + { url = "https://files.pythonhosted.org/packages/95/eb/ae5cab15efa365e13d56b31b0d085a62600298bf398a7986f8388f73b598/aiohttp-3.13.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:b395bbca716c38bef3c764f187860e88c724b342c26275bc03e906142fc5964f", size = 1542025, upload-time = "2025-10-28T20:55:51.861Z" }, + { url = "https://files.pythonhosted.org/packages/e9/2d/1683e8d67ec72d911397fe4e575688d2a9b8f6a6e03c8fdc9f3fd3d4c03f/aiohttp-3.13.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:204ffff2426c25dfda401ba08da85f9c59525cdc42bda26660463dd1cbcfec6f", size = 1714918, upload-time = "2025-10-28T20:55:53.515Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ffe8e0e1c57c5e542d47ffa1fcf95ef2b3ea573bf7c4d2ee877252431efc/aiohttp-3.13.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:05c4dd3c48fb5f15db31f57eb35374cb0c09afdde532e7fb70a75aede0ed30f6", size = 1656113, upload-time = "2025-10-28T20:55:55.438Z" }, + { url = "https://files.pythonhosted.org/packages/0d/42/d511aff5c3a2b06c09d7d214f508a4ad8ac7799817f7c3d23e7336b5e896/aiohttp-3.13.2-cp310-cp310-win32.whl", hash = "sha256:e574a7d61cf10351d734bcddabbe15ede0eaa8a02070d85446875dc11189a251", size = 432290, upload-time = "2025-10-28T20:55:56.96Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ea/1c2eb7098b5bad4532994f2b7a8228d27674035c9b3234fe02c37469ef14/aiohttp-3.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:364f55663085d658b8462a1c3f17b2b84a5c2e1ba858e1b79bff7b2e24ad1514", size = 455075, upload-time = "2025-10-28T20:55:58.373Z" }, + { url = "https://files.pythonhosted.org/packages/35/74/b321e7d7ca762638cdf8cdeceb39755d9c745aff7a64c8789be96ddf6e96/aiohttp-3.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4647d02df098f6434bafd7f32ad14942f05a9caa06c7016fdcc816f343997dd0", size = 743409, upload-time = "2025-10-28T20:56:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/99/3d/91524b905ec473beaf35158d17f82ef5a38033e5809fe8742e3657cdbb97/aiohttp-3.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e3403f24bcb9c3b29113611c3c16a2a447c3953ecf86b79775e7be06f7ae7ccb", size = 497006, upload-time = "2025-10-28T20:56:01.85Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d3/7f68bc02a67716fe80f063e19adbd80a642e30682ce74071269e17d2dba1/aiohttp-3.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:43dff14e35aba17e3d6d5ba628858fb8cb51e30f44724a2d2f0c75be492c55e9", size = 493195, upload-time = "2025-10-28T20:56:03.314Z" }, + { url = "https://files.pythonhosted.org/packages/98/31/913f774a4708775433b7375c4f867d58ba58ead833af96c8af3621a0d243/aiohttp-3.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2a9ea08e8c58bb17655630198833109227dea914cd20be660f52215f6de5613", size = 1747759, upload-time = "2025-10-28T20:56:04.904Z" }, + { url = "https://files.pythonhosted.org/packages/e8/63/04efe156f4326f31c7c4a97144f82132c3bb21859b7bb84748d452ccc17c/aiohttp-3.13.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53b07472f235eb80e826ad038c9d106c2f653584753f3ddab907c83f49eedead", size = 1704456, upload-time = "2025-10-28T20:56:06.986Z" }, + { url = "https://files.pythonhosted.org/packages/8e/02/4e16154d8e0a9cf4ae76f692941fd52543bbb148f02f098ca73cab9b1c1b/aiohttp-3.13.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e736c93e9c274fce6419af4aac199984d866e55f8a4cec9114671d0ea9688780", size = 1807572, upload-time = "2025-10-28T20:56:08.558Z" }, + { url = "https://files.pythonhosted.org/packages/34/58/b0583defb38689e7f06798f0285b1ffb3a6fb371f38363ce5fd772112724/aiohttp-3.13.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ff5e771f5dcbc81c64898c597a434f7682f2259e0cd666932a913d53d1341d1a", size = 1895954, upload-time = "2025-10-28T20:56:10.545Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f3/083907ee3437425b4e376aa58b2c915eb1a33703ec0dc30040f7ae3368c6/aiohttp-3.13.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3b6fb0c207cc661fa0bf8c66d8d9b657331ccc814f4719468af61034b478592", size = 1747092, upload-time = "2025-10-28T20:56:12.118Z" }, + { url = "https://files.pythonhosted.org/packages/ac/61/98a47319b4e425cc134e05e5f3fc512bf9a04bf65aafd9fdcda5d57ec693/aiohttp-3.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:97a0895a8e840ab3520e2288db7cace3a1981300d48babeb50e7425609e2e0ab", size = 1606815, upload-time = "2025-10-28T20:56:14.191Z" }, + { url = "https://files.pythonhosted.org/packages/97/4b/e78b854d82f66bb974189135d31fce265dee0f5344f64dd0d345158a5973/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9e8f8afb552297aca127c90cb840e9a1d4bfd6a10d7d8f2d9176e1acc69bad30", size = 1723789, upload-time = "2025-10-28T20:56:16.101Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fc/9d2ccc794fc9b9acd1379d625c3a8c64a45508b5091c546dea273a41929e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed2f9c7216e53c3df02264f25d824b079cc5914f9e2deba94155190ef648ee40", size = 1718104, upload-time = "2025-10-28T20:56:17.655Z" }, + { url = "https://files.pythonhosted.org/packages/66/65/34564b8765ea5c7d79d23c9113135d1dd3609173da13084830f1507d56cf/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:99c5280a329d5fa18ef30fd10c793a190d996567667908bef8a7f81f8202b948", size = 1785584, upload-time = "2025-10-28T20:56:19.238Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/f6a7a426e02fc82781afd62016417b3948e2207426d90a0e478790d1c8a4/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ca6ffef405fc9c09a746cb5d019c1672cd7f402542e379afc66b370833170cf", size = 1595126, upload-time = "2025-10-28T20:56:20.836Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c7/8e22d5d28f94f67d2af496f14a83b3c155d915d1fe53d94b66d425ec5b42/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:47f438b1a28e926c37632bff3c44df7d27c9b57aaf4e34b1def3c07111fdb782", size = 1800665, upload-time = "2025-10-28T20:56:22.922Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/91133c8b68b1da9fc16555706aa7276fdf781ae2bb0876c838dd86b8116e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9acda8604a57bb60544e4646a4615c1866ee6c04a8edef9b8ee6fd1d8fa2ddc8", size = 1739532, upload-time = "2025-10-28T20:56:25.924Z" }, + { url = "https://files.pythonhosted.org/packages/17/6b/3747644d26a998774b21a616016620293ddefa4d63af6286f389aedac844/aiohttp-3.13.2-cp311-cp311-win32.whl", hash = "sha256:868e195e39b24aaa930b063c08bb0c17924899c16c672a28a65afded9c46c6ec", size = 431876, upload-time = "2025-10-28T20:56:27.524Z" }, + { url = "https://files.pythonhosted.org/packages/c3/63/688462108c1a00eb9f05765331c107f95ae86f6b197b865d29e930b7e462/aiohttp-3.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:7fd19df530c292542636c2a9a85854fab93474396a52f1695e799186bbd7f24c", size = 456205, upload-time = "2025-10-28T20:56:29.062Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/01f00e9856d0a73260e86dd8ed0c2234a466c5c1712ce1c281548df39777/aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b", size = 737623, upload-time = "2025-10-28T20:56:30.797Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1b/4be39c445e2b2bd0aab4ba736deb649fabf14f6757f405f0c9685019b9e9/aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc", size = 492664, upload-time = "2025-10-28T20:56:32.708Z" }, + { url = "https://files.pythonhosted.org/packages/28/66/d35dcfea8050e131cdd731dff36434390479b4045a8d0b9d7111b0a968f1/aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7", size = 491808, upload-time = "2025-10-28T20:56:34.57Z" }, + { url = "https://files.pythonhosted.org/packages/00/29/8e4609b93e10a853b65f8291e64985de66d4f5848c5637cddc70e98f01f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb", size = 1738863, upload-time = "2025-10-28T20:56:36.377Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fa/4ebdf4adcc0def75ced1a0d2d227577cd7b1b85beb7edad85fcc87693c75/aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3", size = 1700586, upload-time = "2025-10-28T20:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/da/04/73f5f02ff348a3558763ff6abe99c223381b0bace05cd4530a0258e52597/aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f", size = 1768625, upload-time = "2025-10-28T20:56:39.75Z" }, + { url = "https://files.pythonhosted.org/packages/f8/49/a825b79ffec124317265ca7d2344a86bcffeb960743487cb11988ffb3494/aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6", size = 1867281, upload-time = "2025-10-28T20:56:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/b9/48/adf56e05f81eac31edcfae45c90928f4ad50ef2e3ea72cb8376162a368f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e", size = 1752431, upload-time = "2025-10-28T20:56:43.162Z" }, + { url = "https://files.pythonhosted.org/packages/30/ab/593855356eead019a74e862f21523db09c27f12fd24af72dbc3555b9bfd9/aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7", size = 1562846, upload-time = "2025-10-28T20:56:44.85Z" }, + { url = "https://files.pythonhosted.org/packages/39/0f/9f3d32271aa8dc35036e9668e31870a9d3b9542dd6b3e2c8a30931cb27ae/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d", size = 1699606, upload-time = "2025-10-28T20:56:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3c/52d2658c5699b6ef7692a3f7128b2d2d4d9775f2a68093f74bca06cf01e1/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b", size = 1720663, upload-time = "2025-10-28T20:56:48.528Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d4/8f8f3ff1fb7fb9e3f04fcad4e89d8a1cd8fc7d05de67e3de5b15b33008ff/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8", size = 1737939, upload-time = "2025-10-28T20:56:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/03/d3/ddd348f8a27a634daae39a1b8e291ff19c77867af438af844bf8b7e3231b/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16", size = 1555132, upload-time = "2025-10-28T20:56:52.568Z" }, + { url = "https://files.pythonhosted.org/packages/39/b8/46790692dc46218406f94374903ba47552f2f9f90dad554eed61bfb7b64c/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169", size = 1764802, upload-time = "2025-10-28T20:56:54.292Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e4/19ce547b58ab2a385e5f0b8aa3db38674785085abcf79b6e0edd1632b12f/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248", size = 1719512, upload-time = "2025-10-28T20:56:56.428Z" }, + { url = "https://files.pythonhosted.org/packages/70/30/6355a737fed29dcb6dfdd48682d5790cb5eab050f7b4e01f49b121d3acad/aiohttp-3.13.2-cp312-cp312-win32.whl", hash = "sha256:27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e", size = 426690, upload-time = "2025-10-28T20:56:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/b10ac09069973d112de6ef980c1f6bb31cb7dcd0bc363acbdad58f927873/aiohttp-3.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45", size = 453465, upload-time = "2025-10-28T20:57:00.795Z" }, + { url = "https://files.pythonhosted.org/packages/bf/78/7e90ca79e5aa39f9694dcfd74f4720782d3c6828113bb1f3197f7e7c4a56/aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be", size = 732139, upload-time = "2025-10-28T20:57:02.455Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742", size = 490082, upload-time = "2025-10-28T20:57:04.784Z" }, + { url = "https://files.pythonhosted.org/packages/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293", size = 489035, upload-time = "2025-10-28T20:57:06.894Z" }, + { url = "https://files.pythonhosted.org/packages/d2/04/db5279e38471b7ac801d7d36a57d1230feeee130bbe2a74f72731b23c2b1/aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811", size = 1720387, upload-time = "2025-10-28T20:57:08.685Z" }, + { url = "https://files.pythonhosted.org/packages/31/07/8ea4326bd7dae2bd59828f69d7fdc6e04523caa55e4a70f4a8725a7e4ed2/aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a", size = 1688314, upload-time = "2025-10-28T20:57:10.693Z" }, + { url = "https://files.pythonhosted.org/packages/48/ab/3d98007b5b87ffd519d065225438cc3b668b2f245572a8cb53da5dd2b1bc/aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4", size = 1756317, upload-time = "2025-10-28T20:57:12.563Z" }, + { url = "https://files.pythonhosted.org/packages/97/3d/801ca172b3d857fafb7b50c7c03f91b72b867a13abca982ed6b3081774ef/aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a", size = 1858539, upload-time = "2025-10-28T20:57:14.623Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e", size = 1739597, upload-time = "2025-10-28T20:57:16.399Z" }, + { url = "https://files.pythonhosted.org/packages/c4/52/7bd3c6693da58ba16e657eb904a5b6decfc48ecd06e9ac098591653b1566/aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb", size = 1555006, upload-time = "2025-10-28T20:57:18.288Z" }, + { url = "https://files.pythonhosted.org/packages/48/30/9586667acec5993b6f41d2ebcf96e97a1255a85f62f3c653110a5de4d346/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded", size = 1683220, upload-time = "2025-10-28T20:57:20.241Z" }, + { url = "https://files.pythonhosted.org/packages/71/01/3afe4c96854cfd7b30d78333852e8e851dceaec1c40fd00fec90c6402dd2/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b", size = 1712570, upload-time = "2025-10-28T20:57:22.253Z" }, + { url = "https://files.pythonhosted.org/packages/11/2c/22799d8e720f4697a9e66fd9c02479e40a49de3de2f0bbe7f9f78a987808/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8", size = 1733407, upload-time = "2025-10-28T20:57:24.37Z" }, + { url = "https://files.pythonhosted.org/packages/34/cb/90f15dd029f07cebbd91f8238a8b363978b530cd128488085b5703683594/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04", size = 1550093, upload-time = "2025-10-28T20:57:26.257Z" }, + { url = "https://files.pythonhosted.org/packages/69/46/12dce9be9d3303ecbf4d30ad45a7683dc63d90733c2d9fe512be6716cd40/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476", size = 1758084, upload-time = "2025-10-28T20:57:28.349Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/0932b558da0c302ffd639fc6362a313b98fdf235dc417bc2493da8394df7/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23", size = 1716987, upload-time = "2025-10-28T20:57:30.233Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8b/f5bd1a75003daed099baec373aed678f2e9b34f2ad40d85baa1368556396/aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254", size = 425859, upload-time = "2025-10-28T20:57:32.105Z" }, + { url = "https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a", size = 452192, upload-time = "2025-10-28T20:57:34.166Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/e2abae1bd815f01c957cbf7be817b3043304e1c87bad526292a0410fdcf9/aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b", size = 735234, upload-time = "2025-10-28T20:57:36.415Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/1ee62dde9b335e4ed41db6bba02613295a0d5b41f74a783c142745a12763/aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61", size = 490733, upload-time = "2025-10-28T20:57:38.205Z" }, + { url = "https://files.pythonhosted.org/packages/1a/aa/7a451b1d6a04e8d15a362af3e9b897de71d86feac3babf8894545d08d537/aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4", size = 491303, upload-time = "2025-10-28T20:57:40.122Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/209958dbb9b01174870f6a7538cd1f3f28274fdbc88a750c238e2c456295/aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b", size = 1717965, upload-time = "2025-10-28T20:57:42.28Z" }, + { url = "https://files.pythonhosted.org/packages/08/aa/6a01848d6432f241416bc4866cae8dc03f05a5a884d2311280f6a09c73d6/aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694", size = 1667221, upload-time = "2025-10-28T20:57:44.869Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/36c1992432d31bbc789fa0b93c768d2e9047ec8c7177e5cd84ea85155f36/aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906", size = 1757178, upload-time = "2025-10-28T20:57:47.216Z" }, + { url = "https://files.pythonhosted.org/packages/ac/b4/8e940dfb03b7e0f68a82b88fd182b9be0a65cb3f35612fe38c038c3112cf/aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9", size = 1838001, upload-time = "2025-10-28T20:57:49.337Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ef/39f3448795499c440ab66084a9db7d20ca7662e94305f175a80f5b7e0072/aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011", size = 1716325, upload-time = "2025-10-28T20:57:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/d7/51/b311500ffc860b181c05d91c59a1313bdd05c82960fdd4035a15740d431e/aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6", size = 1547978, upload-time = "2025-10-28T20:57:53.554Z" }, + { url = "https://files.pythonhosted.org/packages/31/64/b9d733296ef79815226dab8c586ff9e3df41c6aff2e16c06697b2d2e6775/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213", size = 1682042, upload-time = "2025-10-28T20:57:55.617Z" }, + { url = "https://files.pythonhosted.org/packages/3f/30/43d3e0f9d6473a6db7d472104c4eff4417b1e9df01774cb930338806d36b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49", size = 1680085, upload-time = "2025-10-28T20:57:57.59Z" }, + { url = "https://files.pythonhosted.org/packages/16/51/c709f352c911b1864cfd1087577760ced64b3e5bee2aa88b8c0c8e2e4972/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae", size = 1728238, upload-time = "2025-10-28T20:57:59.525Z" }, + { url = "https://files.pythonhosted.org/packages/19/e2/19bd4c547092b773caeb48ff5ae4b1ae86756a0ee76c16727fcfd281404b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa", size = 1544395, upload-time = "2025-10-28T20:58:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/cf/87/860f2803b27dfc5ed7be532832a3498e4919da61299b4a1f8eb89b8ff44d/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4", size = 1742965, upload-time = "2025-10-28T20:58:03.972Z" }, + { url = "https://files.pythonhosted.org/packages/67/7f/db2fc7618925e8c7a601094d5cbe539f732df4fb570740be88ed9e40e99a/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a", size = 1697585, upload-time = "2025-10-28T20:58:06.189Z" }, + { url = "https://files.pythonhosted.org/packages/0c/07/9127916cb09bb38284db5036036042b7b2c514c8ebaeee79da550c43a6d6/aiohttp-3.13.2-cp314-cp314-win32.whl", hash = "sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940", size = 431621, upload-time = "2025-10-28T20:58:08.636Z" }, + { url = "https://files.pythonhosted.org/packages/fb/41/554a8a380df6d3a2bba8a7726429a23f4ac62aaf38de43bb6d6cde7b4d4d/aiohttp-3.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4", size = 457627, upload-time = "2025-10-28T20:58:11Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8e/3824ef98c039d3951cb65b9205a96dd2b20f22241ee17d89c5701557c826/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673", size = 767360, upload-time = "2025-10-28T20:58:13.358Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0f/6a03e3fc7595421274fa34122c973bde2d89344f8a881b728fa8c774e4f1/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd", size = 504616, upload-time = "2025-10-28T20:58:15.339Z" }, + { url = "https://files.pythonhosted.org/packages/c6/aa/ed341b670f1bc8a6f2c6a718353d13b9546e2cef3544f573c6a1ff0da711/aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3", size = 509131, upload-time = "2025-10-28T20:58:17.693Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f0/c68dac234189dae5c4bbccc0f96ce0cc16b76632cfc3a08fff180045cfa4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf", size = 1864168, upload-time = "2025-10-28T20:58:20.113Z" }, + { url = "https://files.pythonhosted.org/packages/8f/65/75a9a76db8364b5d0e52a0c20eabc5d52297385d9af9c35335b924fafdee/aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e", size = 1719200, upload-time = "2025-10-28T20:58:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/f5/55/8df2ed78d7f41d232f6bd3ff866b6f617026551aa1d07e2f03458f964575/aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5", size = 1843497, upload-time = "2025-10-28T20:58:24.672Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e0/94d7215e405c5a02ccb6a35c7a3a6cfff242f457a00196496935f700cde5/aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad", size = 1935703, upload-time = "2025-10-28T20:58:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/0b/78/1eeb63c3f9b2d1015a4c02788fb543141aad0a03ae3f7a7b669b2483f8d4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e", size = 1792738, upload-time = "2025-10-28T20:58:29.787Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/aaf1eea4c188e51538c04cc568040e3082db263a57086ea74a7d38c39e42/aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61", size = 1624061, upload-time = "2025-10-28T20:58:32.529Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c2/3b6034de81fbcc43de8aeb209073a2286dfb50b86e927b4efd81cf848197/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661", size = 1789201, upload-time = "2025-10-28T20:58:34.618Z" }, + { url = "https://files.pythonhosted.org/packages/c9/38/c15dcf6d4d890217dae79d7213988f4e5fe6183d43893a9cf2fe9e84ca8d/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98", size = 1776868, upload-time = "2025-10-28T20:58:38.835Z" }, + { url = "https://files.pythonhosted.org/packages/04/75/f74fd178ac81adf4f283a74847807ade5150e48feda6aef024403716c30c/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693", size = 1790660, upload-time = "2025-10-28T20:58:41.507Z" }, + { url = "https://files.pythonhosted.org/packages/e7/80/7368bd0d06b16b3aba358c16b919e9c46cf11587dc572091031b0e9e3ef0/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a", size = 1617548, upload-time = "2025-10-28T20:58:43.674Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4b/a6212790c50483cb3212e507378fbe26b5086d73941e1ec4b56a30439688/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be", size = 1817240, upload-time = "2025-10-28T20:58:45.787Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f7/ba5f0ba4ea8d8f3c32850912944532b933acbf0f3a75546b89269b9b7dde/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c", size = 1762334, upload-time = "2025-10-28T20:58:47.936Z" }, + { url = "https://files.pythonhosted.org/packages/7e/83/1a5a1856574588b1cad63609ea9ad75b32a8353ac995d830bf5da9357364/aiohttp-3.13.2-cp314-cp314t-win32.whl", hash = "sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734", size = 464685, upload-time = "2025-10-28T20:58:50.642Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4d/d22668674122c08f4d56972297c51a624e64b3ed1efaa40187607a7cb66e/aiohttp-3.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f", size = 498093, upload-time = "2025-10-28T20:58:52.782Z" }, + { url = "https://files.pythonhosted.org/packages/04/4a/3da532fdf51b5e58fffa1a86d6569184cb1bf4bf81cd4434b6541a8d14fd/aiohttp-3.13.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7fbdf5ad6084f1940ce88933de34b62358d0f4a0b6ec097362dcd3e5a65a4989", size = 739009, upload-time = "2025-10-28T20:58:55.682Z" }, + { url = "https://files.pythonhosted.org/packages/89/74/fefa6f7939cdc1d77e5cad712004e675a8847dccc589dcc3abca7feaed73/aiohttp-3.13.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7c3a50345635a02db61792c85bb86daffac05330f6473d524f1a4e3ef9d0046d", size = 495308, upload-time = "2025-10-28T20:58:58.408Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b4/a0638ae1f12d09a0dc558870968a2f19a1eba1b10ad0a85ef142ddb40b50/aiohttp-3.13.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e87dff73f46e969af38ab3f7cb75316a7c944e2e574ff7c933bc01b10def7f5", size = 490624, upload-time = "2025-10-28T20:59:00.479Z" }, + { url = "https://files.pythonhosted.org/packages/02/73/361cd4cac9d98a5a4183d1f26faf7b777330f8dba838c5aae2412862bdd0/aiohttp-3.13.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2adebd4577724dcae085665f294cc57c8701ddd4d26140504db622b8d566d7aa", size = 1662968, upload-time = "2025-10-28T20:59:03.105Z" }, + { url = "https://files.pythonhosted.org/packages/9e/93/ce2ca7584555a6c7dd78f2e6b539a96c5172d88815e13a05a576e14a5a22/aiohttp-3.13.2-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e036a3a645fe92309ec34b918394bb377950cbb43039a97edae6c08db64b23e2", size = 1627117, upload-time = "2025-10-28T20:59:05.274Z" }, + { url = "https://files.pythonhosted.org/packages/a6/42/7ee0e699111f5fc20a69b3203e8f5d5da0b681f270b90bc088d15e339980/aiohttp-3.13.2-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:23ad365e30108c422d0b4428cf271156dd56790f6dd50d770b8e360e6c5ab2e6", size = 1724037, upload-time = "2025-10-28T20:59:07.522Z" }, + { url = "https://files.pythonhosted.org/packages/66/88/67ad5ff11dd61dd1d7882cda39f085d5fca31cf7e2143f5173429d8a591e/aiohttp-3.13.2-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1f9b2c2d4b9d958b1f9ae0c984ec1dd6b6689e15c75045be8ccb4011426268ca", size = 1812899, upload-time = "2025-10-28T20:59:11.698Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/a46f6e1c2a347b9c7a789292279c159b327fadecbf8340f3b05fffff1151/aiohttp-3.13.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a92cf4b9bea33e15ecbaa5c59921be0f23222608143d025c989924f7e3e0c07", size = 1660961, upload-time = "2025-10-28T20:59:14.425Z" }, + { url = "https://files.pythonhosted.org/packages/44/cc/1af9e466eafd9b5d8922238c69aaf95b656137add4c5db65f63ee129bf3c/aiohttp-3.13.2-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:070599407f4954021509193404c4ac53153525a19531051661440644728ba9a7", size = 1553851, upload-time = "2025-10-28T20:59:17.044Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d1/9e5f4f40f9d0ee5668e9b5e7ebfb0eaf371cc09da03785decdc5da56f4b3/aiohttp-3.13.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:29562998ec66f988d49fb83c9b01694fa927186b781463f376c5845c121e4e0b", size = 1634260, upload-time = "2025-10-28T20:59:19.378Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/5d065091c4ae8b55a153f458f19308191bad3b62a89496aa081385486338/aiohttp-3.13.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4dd3db9d0f4ebca1d887d76f7cdbcd1116ac0d05a9221b9dad82c64a62578c4d", size = 1639499, upload-time = "2025-10-28T20:59:22.013Z" }, + { url = "https://files.pythonhosted.org/packages/a3/de/58ae6dc73691a51ff16f69a94d13657bf417456fa0fdfed2b59dd6b4c293/aiohttp-3.13.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d7bc4b7f9c4921eba72677cd9fedd2308f4a4ca3e12fab58935295ad9ea98700", size = 1694087, upload-time = "2025-10-28T20:59:24.773Z" }, + { url = "https://files.pythonhosted.org/packages/45/fe/4d9df516268867d83041b6c073ee15cd532dbea58b82d675a7e1cf2ec24c/aiohttp-3.13.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:dacd50501cd017f8cccb328da0c90823511d70d24a323196826d923aad865901", size = 1540532, upload-time = "2025-10-28T20:59:27.982Z" }, + { url = "https://files.pythonhosted.org/packages/24/e7/a802619308232499482bf30b3530efb5d141481cfd61850368350fb1acb5/aiohttp-3.13.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:8b2f1414f6a1e0683f212ec80e813f4abef94c739fd090b66c9adf9d2a05feac", size = 1710369, upload-time = "2025-10-28T20:59:30.363Z" }, + { url = "https://files.pythonhosted.org/packages/62/08/e8593f39f025efe96ef59550d17cf097222d84f6f84798bedac5bf037fce/aiohttp-3.13.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04c3971421576ed24c191f610052bcb2f059e395bc2489dd99e397f9bc466329", size = 1649296, upload-time = "2025-10-28T20:59:33.285Z" }, + { url = "https://files.pythonhosted.org/packages/e5/fd/ffbc1b6aa46fc6c284af4a438b2c7eab79af1c8ac4b6d2ced185c17f403e/aiohttp-3.13.2-cp39-cp39-win32.whl", hash = "sha256:9f377d0a924e5cc94dc620bc6366fc3e889586a7f18b748901cf016c916e2084", size = 432980, upload-time = "2025-10-28T20:59:35.515Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a9/d47e7873175a4d8aed425f2cdea2df700b2dd44fac024ffbd83455a69a50/aiohttp-3.13.2-cp39-cp39-win_amd64.whl", hash = "sha256:9c705601e16c03466cb72011bd1af55d68fa65b045356d8f96c216e5f6db0fa5", size = 456021, upload-time = "2025-10-28T20:59:37.659Z" }, +] + +[[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' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +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" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +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.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, +] + +[[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.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cc/08ed5a43f2996a16b462f64a7055c6e962803534924b9b2f1371d8c00b7b/cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf", size = 184288, upload-time = "2025-09-08T23:23:48.404Z" }, + { url = "https://files.pythonhosted.org/packages/3d/de/38d9726324e127f727b4ecc376bc85e505bfe61ef130eaf3f290c6847dd4/cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", size = 180509, upload-time = "2025-09-08T23:23:49.73Z" }, + { url = "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", size = 208813, upload-time = "2025-09-08T23:23:51.263Z" }, + { url = "https://files.pythonhosted.org/packages/15/12/a7a79bd0df4c3bff744b2d7e52cc1b68d5e7e427b384252c42366dc1ecbc/cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", size = 216498, upload-time = "2025-09-08T23:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/5c51c1c7600bdd7ed9a24a203ec255dccdd0ebf4527f7b922a0bde2fb6ed/cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", size = 203243, upload-time = "2025-09-08T23:23:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/32/f2/81b63e288295928739d715d00952c8c6034cb6c6a516b17d37e0c8be5600/cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", size = 203158, upload-time = "2025-09-08T23:23:55.169Z" }, + { url = "https://files.pythonhosted.org/packages/1f/74/cc4096ce66f5939042ae094e2e96f53426a979864aa1f96a621ad128be27/cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", size = 216548, upload-time = "2025-09-08T23:23:56.506Z" }, + { url = "https://files.pythonhosted.org/packages/e8/be/f6424d1dc46b1091ffcc8964fa7c0ab0cd36839dd2761b49c90481a6ba1b/cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", size = 218897, upload-time = "2025-09-08T23:23:57.825Z" }, + { url = "https://files.pythonhosted.org/packages/f7/e0/dda537c2309817edf60109e39265f24f24aa7f050767e22c98c53fe7f48b/cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", size = 211249, upload-time = "2025-09-08T23:23:59.139Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e7/7c769804eb75e4c4b35e658dba01de1640a351a9653c3d49ca89d16ccc91/cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", size = 218041, upload-time = "2025-09-08T23:24:00.496Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d9/6218d78f920dcd7507fc16a766b5ef8f3b913cc7aa938e7fc80b9978d089/cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", size = 172138, upload-time = "2025-09-08T23:24:01.7Z" }, + { url = "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", size = 182794, upload-time = "2025-09-08T23:24:02.943Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, + { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, + { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, + { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, + { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, + { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, + { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, + { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, + { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, + { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, + { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, + { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, + { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, + { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, + { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, + { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, + { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, + { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, + { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cd/1a8633802d766a0fa46f382a77e096d7e209e0817892929655fe0586ae32/cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32", size = 3689163, upload-time = "2025-10-15T23:18:13.821Z" }, + { url = "https://files.pythonhosted.org/packages/4c/59/6b26512964ace6480c3e54681a9859c974172fb141c38df11eadd8416947/cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c", size = 3429474, upload-time = "2025-10-15T23:18:15.477Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" }, + { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" }, + { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" }, + { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" }, +] + +[[package]] +name = "dedalus-labs" +version = "0.2.0" +source = { editable = "." } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic", version = "1.10.24", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-12-dedalus-labs-pydantic-v1'" }, + { name = "pydantic", version = "2.12.5", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'group-12-dedalus-labs-pydantic-v2' or extra != 'group-12-dedalus-labs-pydantic-v1'" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] + +[package.optional-dependencies] +aiohttp = [ + { name = "aiohttp" }, + { name = "httpx-aiohttp" }, +] +auth = [ + { name = "pyjwt", extra = ["crypto"] }, +] + +[package.dev-dependencies] +dev = [ + { name = "dirty-equals" }, + { name = "importlib-metadata" }, + { name = "mypy" }, + { name = "pyright" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "pytest-asyncio", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "pytest-asyncio", version = "1.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "pytest-xdist" }, + { name = "respx" }, + { name = "rich" }, + { name = "ruff" }, + { name = "time-machine", version = "2.19.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "time-machine", version = "3.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +pydantic-v1 = [ + { name = "pydantic", version = "1.10.24", source = { registry = "https://pypi.org/simple" } }, +] +pydantic-v2 = [ + { name = "pydantic", version = "2.12.5", source = { registry = "https://pypi.org/simple" } }, +] + +[package.metadata] +requires-dist = [ + { name = "aiohttp", marker = "extra == 'aiohttp'" }, + { name = "anyio", specifier = ">=3.5.0,<5" }, + { name = "distro", specifier = ">=1.7.0,<2" }, + { name = "httpx", specifier = ">=0.23.0,<1" }, + { name = "httpx-aiohttp", marker = "extra == 'aiohttp'", specifier = ">=0.1.9" }, + { name = "jiter", specifier = ">=0.10.0,<1" }, + { name = "pydantic", specifier = ">=1.9.0,<3" }, + { name = "pyjwt", extras = ["crypto"], marker = "extra == 'auth'", specifier = ">=2.10.1" }, + { name = "sniffio" }, + { name = "typing-extensions", specifier = ">=4.10,<5" }, +] +provides-extras = ["aiohttp", "auth"] + +[package.metadata.requires-dev] +dev = [ + { name = "dirty-equals", specifier = ">=0.6.0" }, + { name = "importlib-metadata", specifier = ">=6.7.0" }, + { name = "mypy", specifier = "==1.17" }, + { name = "pyright", specifier = "==1.1.399" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-xdist", specifier = ">=3.6.1" }, + { name = "respx" }, + { name = "rich", specifier = ">=13.7.1" }, + { name = "ruff" }, + { name = "time-machine" }, +] +pydantic-v1 = [{ name = "pydantic", specifier = ">=1.9.0,<2" }] +pydantic-v2 = [ + { name = "pydantic", marker = "python_full_version < '3.14'", specifier = "~=2.0" }, + { name = "pydantic", marker = "python_full_version >= '3.14'", specifier = "~=2.12" }, +] + +[[package]] +name = "dirty-equals" +version = "0.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/1d/c5913ac9d6615515a00f4bdc71356d302437cb74ff2e9aaccd3c14493b78/dirty_equals-0.11.tar.gz", hash = "sha256:f4ac74ee88f2d11e2fa0f65eb30ee4f07105c5f86f4dc92b09eb1138775027c3", size = 128067, upload-time = "2025-11-17T01:51:24.451Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/8d/dbff05239043271dbeace563a7686212a3dd517864a35623fe4d4a64ca19/dirty_equals-0.11-py3-none-any.whl", hash = "sha256:b1d7093273fc2f9be12f443a8ead954ef6daaf6746fd42ef3a5616433ee85286", size = 28051, upload-time = "2025-11-17T01:51:22.849Z" }, +] + +[[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.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, + { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, + { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, + { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, + { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, + { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/c2/59/ae5cdac87a00962122ea37bb346d41b66aec05f9ce328fa2b9e216f8967b/frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47", size = 86967, upload-time = "2025-10-06T05:37:55.607Z" }, + { url = "https://files.pythonhosted.org/packages/8a/10/17059b2db5a032fd9323c41c39e9d1f5f9d0c8f04d1e4e3e788573086e61/frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca", size = 49984, upload-time = "2025-10-06T05:37:57.049Z" }, + { url = "https://files.pythonhosted.org/packages/4b/de/ad9d82ca8e5fa8f0c636e64606553c79e2b859ad253030b62a21fe9986f5/frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068", size = 50240, upload-time = "2025-10-06T05:37:58.145Z" }, + { url = "https://files.pythonhosted.org/packages/4e/45/3dfb7767c2a67d123650122b62ce13c731b6c745bc14424eea67678b508c/frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95", size = 219472, upload-time = "2025-10-06T05:37:59.239Z" }, + { url = "https://files.pythonhosted.org/packages/0b/bf/5bf23d913a741b960d5c1dac7c1985d8a2a1d015772b2d18ea168b08e7ff/frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459", size = 221531, upload-time = "2025-10-06T05:38:00.521Z" }, + { url = "https://files.pythonhosted.org/packages/d0/03/27ec393f3b55860859f4b74cdc8c2a4af3dbf3533305e8eacf48a4fd9a54/frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675", size = 219211, upload-time = "2025-10-06T05:38:01.842Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ad/0fd00c404fa73fe9b169429e9a972d5ed807973c40ab6b3cf9365a33d360/frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61", size = 231775, upload-time = "2025-10-06T05:38:03.384Z" }, + { url = "https://files.pythonhosted.org/packages/8a/c3/86962566154cb4d2995358bc8331bfc4ea19d07db1a96f64935a1607f2b6/frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6", size = 236631, upload-time = "2025-10-06T05:38:04.609Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/6ffad161dbd83782d2c66dc4d378a9103b31770cb1e67febf43aea42d202/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5", size = 218632, upload-time = "2025-10-06T05:38:05.917Z" }, + { url = "https://files.pythonhosted.org/packages/58/b2/4677eee46e0a97f9b30735e6ad0bf6aba3e497986066eb68807ac85cf60f/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3", size = 235967, upload-time = "2025-10-06T05:38:07.614Z" }, + { url = "https://files.pythonhosted.org/packages/05/f3/86e75f8639c5a93745ca7addbbc9de6af56aebb930d233512b17e46f6493/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1", size = 228799, upload-time = "2025-10-06T05:38:08.845Z" }, + { url = "https://files.pythonhosted.org/packages/30/00/39aad3a7f0d98f5eb1d99a3c311215674ed87061aecee7851974b335c050/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178", size = 230566, upload-time = "2025-10-06T05:38:10.52Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4d/aa144cac44568d137846ddc4d5210fb5d9719eb1d7ec6fa2728a54b5b94a/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda", size = 217715, upload-time = "2025-10-06T05:38:11.832Z" }, + { url = "https://files.pythonhosted.org/packages/64/4c/8f665921667509d25a0dd72540513bc86b356c95541686f6442a3283019f/frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087", size = 39933, upload-time = "2025-10-06T05:38:13.061Z" }, + { url = "https://files.pythonhosted.org/packages/79/bd/bcc926f87027fad5e59926ff12d136e1082a115025d33c032d1cd69ab377/frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a", size = 44121, upload-time = "2025-10-06T05:38:14.572Z" }, + { url = "https://files.pythonhosted.org/packages/4c/07/9c2e4eb7584af4b705237b971b89a4155a8e57599c4483a131a39256a9a0/frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103", size = 40312, upload-time = "2025-10-06T05:38:15.699Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[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 = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +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" }, +] + +[[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 = "httpx-aiohttp" +version = "0.1.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/f2/9a86ce9bc48cf57dabb3a3160dfed26d8bbe5a2478a51f9d1dbf89f2f1fc/httpx_aiohttp-0.1.9.tar.gz", hash = "sha256:4ee8b22e6f2e7c80cd03be29eff98bfe7d89bd77f021ce0b578ee76b73b4bfe6", size = 206023, upload-time = "2025-10-15T08:52:57.475Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/db/5cfa8254a86c34a1ab7fe0dbec9f81bb5ebd831cbdd65aa4be4f37027804/httpx_aiohttp-0.1.9-py3-none-any.whl", hash = "sha256:3dc2845568b07742588710fcf3d72db2cbcdf2acc93376edf85f789c4d8e5fda", size = 6180, upload-time = "2025-10-15T08:52:56.521Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and extra == 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jiter" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/9d/e0660989c1370e25848bb4c52d061c71837239738ad937e83edca174c273/jiter-0.12.0.tar.gz", hash = "sha256:64dfcd7d5c168b38d3f9f8bba7fc639edb3418abcc74f22fdbe6b8938293f30b", size = 168294, upload-time = "2025-11-09T20:49:23.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/91/13cb9505f7be74a933f37da3af22e029f6ba64f5669416cb8b2774bc9682/jiter-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e7acbaba9703d5de82a2c98ae6a0f59ab9770ab5af5fa35e43a303aee962cf65", size = 316652, upload-time = "2025-11-09T20:46:41.021Z" }, + { url = "https://files.pythonhosted.org/packages/4e/76/4e9185e5d9bb4e482cf6dec6410d5f78dfeb374cfcecbbe9888d07c52daa/jiter-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:364f1a7294c91281260364222f535bc427f56d4de1d8ffd718162d21fbbd602e", size = 319829, upload-time = "2025-11-09T20:46:43.281Z" }, + { url = "https://files.pythonhosted.org/packages/86/af/727de50995d3a153138139f259baae2379d8cb0522c0c00419957bc478a6/jiter-0.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ee4d25805d4fb23f0a5167a962ef8e002dbfb29c0989378488e32cf2744b62", size = 350568, upload-time = "2025-11-09T20:46:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c1/d6e9f4b7a3d5ac63bcbdfddeb50b2dcfbdc512c86cffc008584fdc350233/jiter-0.12.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:796f466b7942107eb889c08433b6e31b9a7ed31daceaecf8af1be26fb26c0ca8", size = 369052, upload-time = "2025-11-09T20:46:46.818Z" }, + { url = "https://files.pythonhosted.org/packages/eb/be/00824cd530f30ed73fa8a4f9f3890a705519e31ccb9e929f1e22062e7c76/jiter-0.12.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35506cb71f47dba416694e67af996bbdefb8e3608f1f78799c2e1f9058b01ceb", size = 481585, upload-time = "2025-11-09T20:46:48.319Z" }, + { url = "https://files.pythonhosted.org/packages/74/b6/2ad7990dff9504d4b5052eef64aa9574bd03d722dc7edced97aad0d47be7/jiter-0.12.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:726c764a90c9218ec9e4f99a33d6bf5ec169163f2ca0fc21b654e88c2abc0abc", size = 380541, upload-time = "2025-11-09T20:46:49.643Z" }, + { url = "https://files.pythonhosted.org/packages/b5/c7/f3c26ecbc1adbf1db0d6bba99192143d8fe8504729d9594542ecc4445784/jiter-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa47810c5565274810b726b0dc86d18dce5fd17b190ebdc3890851d7b2a0e74", size = 364423, upload-time = "2025-11-09T20:46:51.731Z" }, + { url = "https://files.pythonhosted.org/packages/18/51/eac547bf3a2d7f7e556927278e14c56a0604b8cddae75815d5739f65f81d/jiter-0.12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ec0259d3f26c62aed4d73b198c53e316ae11f0f69c8fbe6682c6dcfa0fcce2", size = 389958, upload-time = "2025-11-09T20:46:53.432Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1f/9ca592e67175f2db156cff035e0d817d6004e293ee0c1d73692d38fcb596/jiter-0.12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:79307d74ea83465b0152fa23e5e297149506435535282f979f18b9033c0bb025", size = 522084, upload-time = "2025-11-09T20:46:54.848Z" }, + { url = "https://files.pythonhosted.org/packages/83/ff/597d9cdc3028f28224f53e1a9d063628e28b7a5601433e3196edda578cdd/jiter-0.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cf6e6dd18927121fec86739f1a8906944703941d000f0639f3eb6281cc601dca", size = 513054, upload-time = "2025-11-09T20:46:56.487Z" }, + { url = "https://files.pythonhosted.org/packages/24/6d/1970bce1351bd02e3afcc5f49e4f7ef3dabd7fb688f42be7e8091a5b809a/jiter-0.12.0-cp310-cp310-win32.whl", hash = "sha256:b6ae2aec8217327d872cbfb2c1694489057b9433afce447955763e6ab015b4c4", size = 206368, upload-time = "2025-11-09T20:46:58.638Z" }, + { url = "https://files.pythonhosted.org/packages/e3/6b/eb1eb505b2d86709b59ec06681a2b14a94d0941db091f044b9f0e16badc0/jiter-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:c7f49ce90a71e44f7e1aa9e7ec415b9686bbc6a5961e57eab511015e6759bc11", size = 204847, upload-time = "2025-11-09T20:47:00.295Z" }, + { url = "https://files.pythonhosted.org/packages/32/f9/eaca4633486b527ebe7e681c431f529b63fe2709e7c5242fc0f43f77ce63/jiter-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8f8a7e317190b2c2d60eb2e8aa835270b008139562d70fe732e1c0020ec53c9", size = 316435, upload-time = "2025-11-09T20:47:02.087Z" }, + { url = "https://files.pythonhosted.org/packages/10/c1/40c9f7c22f5e6ff715f28113ebaba27ab85f9af2660ad6e1dd6425d14c19/jiter-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2218228a077e784c6c8f1a8e5d6b8cb1dea62ce25811c356364848554b2056cd", size = 320548, upload-time = "2025-11-09T20:47:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1b/efbb68fe87e7711b00d2cfd1f26bb4bfc25a10539aefeaa7727329ffb9cb/jiter-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9354ccaa2982bf2188fd5f57f79f800ef622ec67beb8329903abf6b10da7d423", size = 351915, upload-time = "2025-11-09T20:47:05.171Z" }, + { url = "https://files.pythonhosted.org/packages/15/2d/c06e659888c128ad1e838123d0638f0efad90cc30860cb5f74dd3f2fc0b3/jiter-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2607185ea89b4af9a604d4c7ec40e45d3ad03ee66998b031134bc510232bb7", size = 368966, upload-time = "2025-11-09T20:47:06.508Z" }, + { url = "https://files.pythonhosted.org/packages/6b/20/058db4ae5fb07cf6a4ab2e9b9294416f606d8e467fb74c2184b2a1eeacba/jiter-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a585a5e42d25f2e71db5f10b171f5e5ea641d3aa44f7df745aa965606111cc2", size = 482047, upload-time = "2025-11-09T20:47:08.382Z" }, + { url = "https://files.pythonhosted.org/packages/49/bb/dc2b1c122275e1de2eb12905015d61e8316b2f888bdaac34221c301495d6/jiter-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd9e21d34edff5a663c631f850edcb786719c960ce887a5661e9c828a53a95d9", size = 380835, upload-time = "2025-11-09T20:47:09.81Z" }, + { url = "https://files.pythonhosted.org/packages/23/7d/38f9cd337575349de16da575ee57ddb2d5a64d425c9367f5ef9e4612e32e/jiter-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a612534770470686cd5431478dc5a1b660eceb410abade6b1b74e320ca98de6", size = 364587, upload-time = "2025-11-09T20:47:11.529Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a3/b13e8e61e70f0bb06085099c4e2462647f53cc2ca97614f7fedcaa2bb9f3/jiter-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3985aea37d40a908f887b34d05111e0aae822943796ebf8338877fee2ab67725", size = 390492, upload-time = "2025-11-09T20:47:12.993Z" }, + { url = "https://files.pythonhosted.org/packages/07/71/e0d11422ed027e21422f7bc1883c61deba2d9752b720538430c1deadfbca/jiter-0.12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b1207af186495f48f72529f8d86671903c8c10127cac6381b11dddc4aaa52df6", size = 522046, upload-time = "2025-11-09T20:47:14.6Z" }, + { url = "https://files.pythonhosted.org/packages/9f/59/b968a9aa7102a8375dbbdfbd2aeebe563c7e5dddf0f47c9ef1588a97e224/jiter-0.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef2fb241de583934c9915a33120ecc06d94aa3381a134570f59eed784e87001e", size = 513392, upload-time = "2025-11-09T20:47:16.011Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e4/7df62002499080dbd61b505c5cb351aa09e9959d176cac2aa8da6f93b13b/jiter-0.12.0-cp311-cp311-win32.whl", hash = "sha256:453b6035672fecce8007465896a25b28a6b59cfe8fbc974b2563a92f5a92a67c", size = 206096, upload-time = "2025-11-09T20:47:17.344Z" }, + { url = "https://files.pythonhosted.org/packages/bb/60/1032b30ae0572196b0de0e87dce3b6c26a1eff71aad5fe43dee3082d32e0/jiter-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:ca264b9603973c2ad9435c71a8ec8b49f8f715ab5ba421c85a51cde9887e421f", size = 204899, upload-time = "2025-11-09T20:47:19.365Z" }, + { url = "https://files.pythonhosted.org/packages/49/d5/c145e526fccdb834063fb45c071df78b0cc426bbaf6de38b0781f45d956f/jiter-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:cb00ef392e7d684f2754598c02c409f376ddcef857aae796d559e6cacc2d78a5", size = 188070, upload-time = "2025-11-09T20:47:20.75Z" }, + { url = "https://files.pythonhosted.org/packages/92/c9/5b9f7b4983f1b542c64e84165075335e8a236fa9e2ea03a0c79780062be8/jiter-0.12.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:305e061fa82f4680607a775b2e8e0bcb071cd2205ac38e6ef48c8dd5ebe1cf37", size = 314449, upload-time = "2025-11-09T20:47:22.999Z" }, + { url = "https://files.pythonhosted.org/packages/98/6e/e8efa0e78de00db0aee82c0cf9e8b3f2027efd7f8a71f859d8f4be8e98ef/jiter-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c1860627048e302a528333c9307c818c547f214d8659b0705d2195e1a94b274", size = 319855, upload-time = "2025-11-09T20:47:24.779Z" }, + { url = "https://files.pythonhosted.org/packages/20/26/894cd88e60b5d58af53bec5c6759d1292bd0b37a8b5f60f07abf7a63ae5f/jiter-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df37577a4f8408f7e0ec3205d2a8f87672af8f17008358063a4d6425b6081ce3", size = 350171, upload-time = "2025-11-09T20:47:26.469Z" }, + { url = "https://files.pythonhosted.org/packages/f5/27/a7b818b9979ac31b3763d25f3653ec3a954044d5e9f5d87f2f247d679fd1/jiter-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fdd787356c1c13a4f40b43c2156276ef7a71eb487d98472476476d803fb2cf", size = 365590, upload-time = "2025-11-09T20:47:27.918Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7e/e46195801a97673a83746170b17984aa8ac4a455746354516d02ca5541b4/jiter-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1eb5db8d9c65b112aacf14fcd0faae9913d07a8afea5ed06ccdd12b724e966a1", size = 479462, upload-time = "2025-11-09T20:47:29.654Z" }, + { url = "https://files.pythonhosted.org/packages/ca/75/f833bfb009ab4bd11b1c9406d333e3b4357709ed0570bb48c7c06d78c7dd/jiter-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73c568cc27c473f82480abc15d1301adf333a7ea4f2e813d6a2c7d8b6ba8d0df", size = 378983, upload-time = "2025-11-09T20:47:31.026Z" }, + { url = "https://files.pythonhosted.org/packages/71/b3/7a69d77943cc837d30165643db753471aff5df39692d598da880a6e51c24/jiter-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4321e8a3d868919bcb1abb1db550d41f2b5b326f72df29e53b2df8b006eb9403", size = 361328, upload-time = "2025-11-09T20:47:33.286Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ac/a78f90caf48d65ba70d8c6efc6f23150bc39dc3389d65bbec2a95c7bc628/jiter-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a51bad79f8cc9cac2b4b705039f814049142e0050f30d91695a2d9a6611f126", size = 386740, upload-time = "2025-11-09T20:47:34.703Z" }, + { url = "https://files.pythonhosted.org/packages/39/b6/5d31c2cc8e1b6a6bcf3c5721e4ca0a3633d1ab4754b09bc7084f6c4f5327/jiter-0.12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2a67b678f6a5f1dd6c36d642d7db83e456bc8b104788262aaefc11a22339f5a9", size = 520875, upload-time = "2025-11-09T20:47:36.058Z" }, + { url = "https://files.pythonhosted.org/packages/30/b5/4df540fae4e9f68c54b8dab004bd8c943a752f0b00efd6e7d64aa3850339/jiter-0.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efe1a211fe1fd14762adea941e3cfd6c611a136e28da6c39272dbb7a1bbe6a86", size = 511457, upload-time = "2025-11-09T20:47:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/07/65/86b74010e450a1a77b2c1aabb91d4a91dd3cd5afce99f34d75fd1ac64b19/jiter-0.12.0-cp312-cp312-win32.whl", hash = "sha256:d779d97c834b4278276ec703dc3fc1735fca50af63eb7262f05bdb4e62203d44", size = 204546, upload-time = "2025-11-09T20:47:40.47Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/6659f537f9562d963488e3e55573498a442503ced01f7e169e96a6110383/jiter-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8269062060212b373316fe69236096aaf4c49022d267c6736eebd66bbbc60bb", size = 205196, upload-time = "2025-11-09T20:47:41.794Z" }, + { url = "https://files.pythonhosted.org/packages/21/f4/935304f5169edadfec7f9c01eacbce4c90bb9a82035ac1de1f3bd2d40be6/jiter-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:06cb970936c65de926d648af0ed3d21857f026b1cf5525cb2947aa5e01e05789", size = 186100, upload-time = "2025-11-09T20:47:43.007Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a6/97209693b177716e22576ee1161674d1d58029eb178e01866a0422b69224/jiter-0.12.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6cc49d5130a14b732e0612bc76ae8db3b49898732223ef8b7599aa8d9810683e", size = 313658, upload-time = "2025-11-09T20:47:44.424Z" }, + { url = "https://files.pythonhosted.org/packages/06/4d/125c5c1537c7d8ee73ad3d530a442d6c619714b95027143f1b61c0b4dfe0/jiter-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37f27a32ce36364d2fa4f7fdc507279db604d27d239ea2e044c8f148410defe1", size = 318605, upload-time = "2025-11-09T20:47:45.973Z" }, + { url = "https://files.pythonhosted.org/packages/99/bf/a840b89847885064c41a5f52de6e312e91fa84a520848ee56c97e4fa0205/jiter-0.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc0944aa3d4b4773e348cda635252824a78f4ba44328e042ef1ff3f6080d1cf", size = 349803, upload-time = "2025-11-09T20:47:47.535Z" }, + { url = "https://files.pythonhosted.org/packages/8a/88/e63441c28e0db50e305ae23e19c1d8fae012d78ed55365da392c1f34b09c/jiter-0.12.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da25c62d4ee1ffbacb97fac6dfe4dcd6759ebdc9015991e92a6eae5816287f44", size = 365120, upload-time = "2025-11-09T20:47:49.284Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7c/49b02714af4343970eb8aca63396bc1c82fa01197dbb1e9b0d274b550d4e/jiter-0.12.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:048485c654b838140b007390b8182ba9774621103bd4d77c9c3f6f117474ba45", size = 479918, upload-time = "2025-11-09T20:47:50.807Z" }, + { url = "https://files.pythonhosted.org/packages/69/ba/0a809817fdd5a1db80490b9150645f3aae16afad166960bcd562be194f3b/jiter-0.12.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:635e737fbb7315bef0037c19b88b799143d2d7d3507e61a76751025226b3ac87", size = 379008, upload-time = "2025-11-09T20:47:52.211Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c3/c9fc0232e736c8877d9e6d83d6eeb0ba4e90c6c073835cc2e8f73fdeef51/jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e017c417b1ebda911bd13b1e40612704b1f5420e30695112efdbed8a4b389ed", size = 361785, upload-time = "2025-11-09T20:47:53.512Z" }, + { url = "https://files.pythonhosted.org/packages/96/61/61f69b7e442e97ca6cd53086ddc1cf59fb830549bc72c0a293713a60c525/jiter-0.12.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:89b0bfb8b2bf2351fba36bb211ef8bfceba73ef58e7f0c68fb67b5a2795ca2f9", size = 386108, upload-time = "2025-11-09T20:47:54.893Z" }, + { url = "https://files.pythonhosted.org/packages/e9/2e/76bb3332f28550c8f1eba3bf6e5efe211efda0ddbbaf24976bc7078d42a5/jiter-0.12.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f5aa5427a629a824a543672778c9ce0c5e556550d1569bb6ea28a85015287626", size = 519937, upload-time = "2025-11-09T20:47:56.253Z" }, + { url = "https://files.pythonhosted.org/packages/84/d6/fa96efa87dc8bff2094fb947f51f66368fa56d8d4fc9e77b25d7fbb23375/jiter-0.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed53b3d6acbcb0fd0b90f20c7cb3b24c357fe82a3518934d4edfa8c6898e498c", size = 510853, upload-time = "2025-11-09T20:47:58.32Z" }, + { url = "https://files.pythonhosted.org/packages/8a/28/93f67fdb4d5904a708119a6ab58a8f1ec226ff10a94a282e0215402a8462/jiter-0.12.0-cp313-cp313-win32.whl", hash = "sha256:4747de73d6b8c78f2e253a2787930f4fffc68da7fa319739f57437f95963c4de", size = 204699, upload-time = "2025-11-09T20:47:59.686Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1f/30b0eb087045a0abe2a5c9c0c0c8da110875a1d3be83afd4a9a4e548be3c/jiter-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:e25012eb0c456fcc13354255d0338cd5397cce26c77b2832b3c4e2e255ea5d9a", size = 204258, upload-time = "2025-11-09T20:48:01.01Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f4/2b4daf99b96bce6fc47971890b14b2a36aef88d7beb9f057fafa032c6141/jiter-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:c97b92c54fe6110138c872add030a1f99aea2401ddcdaa21edf74705a646dd60", size = 185503, upload-time = "2025-11-09T20:48:02.35Z" }, + { url = "https://files.pythonhosted.org/packages/39/ca/67bb15a7061d6fe20b9b2a2fd783e296a1e0f93468252c093481a2f00efa/jiter-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:53839b35a38f56b8be26a7851a48b89bc47e5d88e900929df10ed93b95fea3d6", size = 317965, upload-time = "2025-11-09T20:48:03.783Z" }, + { url = "https://files.pythonhosted.org/packages/18/af/1788031cd22e29c3b14bc6ca80b16a39a0b10e611367ffd480c06a259831/jiter-0.12.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f669548e55c91ab47fef8bddd9c954dab1938644e715ea49d7e117015110a4", size = 345831, upload-time = "2025-11-09T20:48:05.55Z" }, + { url = "https://files.pythonhosted.org/packages/05/17/710bf8472d1dff0d3caf4ced6031060091c1320f84ee7d5dcbed1f352417/jiter-0.12.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:351d54f2b09a41600ffea43d081522d792e81dcfb915f6d2d242744c1cc48beb", size = 361272, upload-time = "2025-11-09T20:48:06.951Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f1/1dcc4618b59761fef92d10bcbb0b038b5160be653b003651566a185f1a5c/jiter-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2a5e90604620f94bf62264e7c2c038704d38217b7465b863896c6d7c902b06c7", size = 204604, upload-time = "2025-11-09T20:48:08.328Z" }, + { url = "https://files.pythonhosted.org/packages/d9/32/63cb1d9f1c5c6632a783c0052cde9ef7ba82688f7065e2f0d5f10a7e3edb/jiter-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:88ef757017e78d2860f96250f9393b7b577b06a956ad102c29c8237554380db3", size = 185628, upload-time = "2025-11-09T20:48:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/45c9f0dbe4a1416b2b9a8a6d1236459540f43d7fb8883cff769a8db0612d/jiter-0.12.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c46d927acd09c67a9fb1416df45c5a04c27e83aae969267e98fba35b74e99525", size = 312478, upload-time = "2025-11-09T20:48:10.898Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a7/54ae75613ba9e0f55fcb0bc5d1f807823b5167cc944e9333ff322e9f07dd/jiter-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:774ff60b27a84a85b27b88cd5583899c59940bcc126caca97eb2a9df6aa00c49", size = 318706, upload-time = "2025-11-09T20:48:12.266Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/2aa241ad2c10774baf6c37f8b8e1f39c07db358f1329f4eb40eba179c2a2/jiter-0.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5433fab222fb072237df3f637d01b81f040a07dcac1cb4a5c75c7aa9ed0bef1", size = 351894, upload-time = "2025-11-09T20:48:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/54/4f/0f2759522719133a9042781b18cc94e335b6d290f5e2d3e6899d6af933e3/jiter-0.12.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8c593c6e71c07866ec6bfb790e202a833eeec885022296aff6b9e0b92d6a70e", size = 365714, upload-time = "2025-11-09T20:48:15.083Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6f/806b895f476582c62a2f52c453151edd8a0fde5411b0497baaa41018e878/jiter-0.12.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90d32894d4c6877a87ae00c6b915b609406819dce8bc0d4e962e4de2784e567e", size = 478989, upload-time = "2025-11-09T20:48:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/86/6c/012d894dc6e1033acd8db2b8346add33e413ec1c7c002598915278a37f79/jiter-0.12.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:798e46eed9eb10c3adbbacbd3bdb5ecd4cf7064e453d00dbef08802dae6937ff", size = 378615, upload-time = "2025-11-09T20:48:18.614Z" }, + { url = "https://files.pythonhosted.org/packages/87/30/d718d599f6700163e28e2c71c0bbaf6dace692e7df2592fd793ac9276717/jiter-0.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3f1368f0a6719ea80013a4eb90ba72e75d7ea67cfc7846db2ca504f3df0169a", size = 364745, upload-time = "2025-11-09T20:48:20.117Z" }, + { url = "https://files.pythonhosted.org/packages/8f/85/315b45ce4b6ddc7d7fceca24068543b02bdc8782942f4ee49d652e2cc89f/jiter-0.12.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f04a9d0b4406f7e51279710b27484af411896246200e461d80d3ba0caa901a", size = 386502, upload-time = "2025-11-09T20:48:21.543Z" }, + { url = "https://files.pythonhosted.org/packages/74/0b/ce0434fb40c5b24b368fe81b17074d2840748b4952256bab451b72290a49/jiter-0.12.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:fd990541982a24281d12b67a335e44f117e4c6cbad3c3b75c7dea68bf4ce3a67", size = 519845, upload-time = "2025-11-09T20:48:22.964Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a3/7a7a4488ba052767846b9c916d208b3ed114e3eb670ee984e4c565b9cf0d/jiter-0.12.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b111b0e9152fa7df870ecaebb0bd30240d9f7fff1f2003bcb4ed0f519941820b", size = 510701, upload-time = "2025-11-09T20:48:24.483Z" }, + { url = "https://files.pythonhosted.org/packages/c3/16/052ffbf9d0467b70af24e30f91e0579e13ded0c17bb4a8eb2aed3cb60131/jiter-0.12.0-cp314-cp314-win32.whl", hash = "sha256:a78befb9cc0a45b5a5a0d537b06f8544c2ebb60d19d02c41ff15da28a9e22d42", size = 205029, upload-time = "2025-11-09T20:48:25.749Z" }, + { url = "https://files.pythonhosted.org/packages/e4/18/3cf1f3f0ccc789f76b9a754bdb7a6977e5d1d671ee97a9e14f7eb728d80e/jiter-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:e1fe01c082f6aafbe5c8faf0ff074f38dfb911d53f07ec333ca03f8f6226debf", size = 204960, upload-time = "2025-11-09T20:48:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/02/68/736821e52ecfdeeb0f024b8ab01b5a229f6b9293bbdb444c27efade50b0f/jiter-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:d72f3b5a432a4c546ea4bedc84cce0c3404874f1d1676260b9c7f048a9855451", size = 185529, upload-time = "2025-11-09T20:48:29.125Z" }, + { url = "https://files.pythonhosted.org/packages/30/61/12ed8ee7a643cce29ac97c2281f9ce3956eb76b037e88d290f4ed0d41480/jiter-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e6ded41aeba3603f9728ed2b6196e4df875348ab97b28fc8afff115ed42ba7a7", size = 318974, upload-time = "2025-11-09T20:48:30.87Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c6/f3041ede6d0ed5e0e79ff0de4c8f14f401bbf196f2ef3971cdbe5fd08d1d/jiter-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a947920902420a6ada6ad51892082521978e9dd44a802663b001436e4b771684", size = 345932, upload-time = "2025-11-09T20:48:32.658Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5d/4d94835889edd01ad0e2dbfc05f7bdfaed46292e7b504a6ac7839aa00edb/jiter-0.12.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:add5e227e0554d3a52cf390a7635edaffdf4f8fce4fdbcef3cc2055bb396a30c", size = 367243, upload-time = "2025-11-09T20:48:34.093Z" }, + { url = "https://files.pythonhosted.org/packages/fd/76/0051b0ac2816253a99d27baf3dda198663aff882fa6ea7deeb94046da24e/jiter-0.12.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9b1cda8fcb736250d7e8711d4580ebf004a46771432be0ae4796944b5dfa5d", size = 479315, upload-time = "2025-11-09T20:48:35.507Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/83f793acd68e5cb24e483f44f482a1a15601848b9b6f199dacb970098f77/jiter-0.12.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb12a2223fe0135c7ff1356a143d57f95bbf1f4a66584f1fc74df21d86b993", size = 380714, upload-time = "2025-11-09T20:48:40.014Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/4808a88338ad2c228b1126b93fcd8ba145e919e886fe910d578230dabe3b/jiter-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c596cc0f4cb574877550ce4ecd51f8037469146addd676d7c1a30ebe6391923f", size = 365168, upload-time = "2025-11-09T20:48:41.462Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d4/04619a9e8095b42aef436b5aeb4c0282b4ff1b27d1db1508df9f5dc82750/jiter-0.12.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ab4c823b216a4aeab3fdbf579c5843165756bd9ad87cc6b1c65919c4715f783", size = 387893, upload-time = "2025-11-09T20:48:42.921Z" }, + { url = "https://files.pythonhosted.org/packages/17/ea/d3c7e62e4546fdc39197fa4a4315a563a89b95b6d54c0d25373842a59cbe/jiter-0.12.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e427eee51149edf962203ff8db75a7514ab89be5cb623fb9cea1f20b54f1107b", size = 520828, upload-time = "2025-11-09T20:48:44.278Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0b/c6d3562a03fd767e31cb119d9041ea7958c3c80cb3d753eafb19b3b18349/jiter-0.12.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:edb868841f84c111255ba5e80339d386d937ec1fdce419518ce1bd9370fac5b6", size = 511009, upload-time = "2025-11-09T20:48:45.726Z" }, + { url = "https://files.pythonhosted.org/packages/aa/51/2cb4468b3448a8385ebcd15059d325c9ce67df4e2758d133ab9442b19834/jiter-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8bbcfe2791dfdb7c5e48baf646d37a6a3dcb5a97a032017741dea9f817dca183", size = 205110, upload-time = "2025-11-09T20:48:47.033Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c5/ae5ec83dec9c2d1af805fd5fe8f74ebded9c8670c5210ec7820ce0dbeb1e/jiter-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2fa940963bf02e1d8226027ef461e36af472dea85d36054ff835aeed944dd873", size = 205223, upload-time = "2025-11-09T20:48:49.076Z" }, + { url = "https://files.pythonhosted.org/packages/97/9a/3c5391907277f0e55195550cf3fa8e293ae9ee0c00fb402fec1e38c0c82f/jiter-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:506c9708dd29b27288f9f8f1140c3cb0e3d8ddb045956d7757b1fa0e0f39a473", size = 185564, upload-time = "2025-11-09T20:48:50.376Z" }, + { url = "https://files.pythonhosted.org/packages/7d/da/3e1fbd1f03f89ff0b4469d481be0b5cf2880c8e7b56fd80303b3ab5ae52d/jiter-0.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c9d28b218d5f9e5f69a0787a196322a5056540cb378cac8ff542b4fa7219966c", size = 319378, upload-time = "2025-11-09T20:48:51.761Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/e07d69285e9e19a153050a6d281d2f0968600753a8fed8a3a141d6ffc140/jiter-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0ee12028daf8cfcf880dd492349a122a64f42c059b6c62a2b0c96a83a8da820", size = 312195, upload-time = "2025-11-09T20:48:53.547Z" }, + { url = "https://files.pythonhosted.org/packages/2d/82/1f1cb5231b36af9f3d6d5b6030e70110faf14fd143419fc5fe7d852e691a/jiter-0.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b135ebe757a82d67ed2821526e72d0acf87dd61f6013e20d3c45b8048af927b", size = 352777, upload-time = "2025-11-09T20:48:55.058Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5e/728393bbbc99b31e8f7a4fdd8fa55e455a0a9648f79097d9088baf1f676f/jiter-0.12.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15d7fafb81af8a9e3039fc305529a61cd933eecee33b4251878a1c89859552a3", size = 370738, upload-time = "2025-11-09T20:48:56.632Z" }, + { url = "https://files.pythonhosted.org/packages/30/08/ac92f0df7b14ac82f2fe0a382a8000e600ab90af95798d4a7db0c1bd0736/jiter-0.12.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92d1f41211d8a8fe412faad962d424d334764c01dac6691c44691c2e4d3eedaf", size = 483744, upload-time = "2025-11-09T20:48:58.006Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f4/dbfa4e759a2b82e969a14c3d0a91b176f1ed94717183a2f495cf94a651b9/jiter-0.12.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a64a48d7c917b8f32f25c176df8749ecf08cec17c466114727efe7441e17f6d", size = 382888, upload-time = "2025-11-09T20:48:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d9/b86fff7f748b0bb54222a8f132ffaf4d1be56b4591fa76d3cfdd701a33e5/jiter-0.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:122046f3b3710b85de99d9aa2f3f0492a8233a2f54a64902b096efc27ea747b5", size = 366465, upload-time = "2025-11-09T20:49:01.408Z" }, + { url = "https://files.pythonhosted.org/packages/93/3c/1152d8b433317a568927e13c1b125c680e6c058ff5d304833be8469bd4f2/jiter-0.12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:27ec39225e03c32c6b863ba879deb427882f243ae46f0d82d68b695fa5b48b40", size = 392603, upload-time = "2025-11-09T20:49:02.784Z" }, + { url = "https://files.pythonhosted.org/packages/6e/92/ff19d8fb87f3f9438eb7464862c8d0126455bc046b345d59b21443640c62/jiter-0.12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26b9e155ddc132225a39b1995b3b9f0fe0f79a6d5cbbeacf103271e7d309b404", size = 523780, upload-time = "2025-11-09T20:49:04.42Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/4260e2d84e4a293c36d2a8e8b8dcd69609c671f3bd310e4625359217c517/jiter-0.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ab05b7c58e29bb9e60b70c2e0094c98df79a1e42e397b9bb6eaa989b7a66dd0", size = 514874, upload-time = "2025-11-09T20:49:05.844Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f7/574d2cb79e86feb035ade18c2254da71d04417555907c9df51dd6b183426/jiter-0.12.0-cp39-cp39-win32.whl", hash = "sha256:59f9f9df87ed499136db1c2b6c9efb902f964bed42a582ab7af413b6a293e7b0", size = 208329, upload-time = "2025-11-09T20:49:07.444Z" }, + { url = "https://files.pythonhosted.org/packages/05/ce/50725ec39782d8c973f19ae2d7dd3d192d01332c7cbde48c75e16a3e85a9/jiter-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:d3719596a1ebe7a48a498e8d5d0c4bf7553321d4c3eee1d620628d51351a3928", size = 206557, upload-time = "2025-11-09T20:49:08.888Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/5339ef1ecaa881c6948669956567a64d2670941925f245c434f494ffb0e5/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:4739a4657179ebf08f85914ce50332495811004cc1747852e8b2041ed2aab9b8", size = 311144, upload-time = "2025-11-09T20:49:10.503Z" }, + { url = "https://files.pythonhosted.org/packages/27/74/3446c652bffbd5e81ab354e388b1b5fc1d20daac34ee0ed11ff096b1b01a/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:41da8def934bf7bec16cb24bd33c0ca62126d2d45d81d17b864bd5ad721393c3", size = 305877, upload-time = "2025-11-09T20:49:12.269Z" }, + { url = "https://files.pythonhosted.org/packages/a1/f4/ed76ef9043450f57aac2d4fbeb27175aa0eb9c38f833be6ef6379b3b9a86/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c44ee814f499c082e69872d426b624987dbc5943ab06e9bbaa4f81989fdb79e", size = 340419, upload-time = "2025-11-09T20:49:13.803Z" }, + { url = "https://files.pythonhosted.org/packages/21/01/857d4608f5edb0664aa791a3d45702e1a5bcfff9934da74035e7b9803846/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd2097de91cf03eaa27b3cbdb969addf83f0179c6afc41bbc4513705e013c65d", size = 347212, upload-time = "2025-11-09T20:49:15.643Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f5/12efb8ada5f5c9edc1d4555fe383c1fb2eac05ac5859258a72d61981d999/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e8547883d7b96ef2e5fe22b88f8a4c8725a56e7f4abafff20fd5272d634c7ecb", size = 309974, upload-time = "2025-11-09T20:49:17.187Z" }, + { url = "https://files.pythonhosted.org/packages/85/15/d6eb3b770f6a0d332675141ab3962fd4a7c270ede3515d9f3583e1d28276/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:89163163c0934854a668ed783a2546a0617f71706a2551a4a0666d91ab365d6b", size = 304233, upload-time = "2025-11-09T20:49:18.734Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3e/e7e06743294eea2cf02ced6aa0ff2ad237367394e37a0e2b4a1108c67a36/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96b264ab7d34bbb2312dedc47ce07cd53f06835eacbc16dde3761f47c3a9e7f", size = 338537, upload-time = "2025-11-09T20:49:20.317Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9c/6753e6522b8d0ef07d3a3d239426669e984fb0eba15a315cdbc1253904e4/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24e864cb30ab82311c6425655b0cdab0a98c5d973b065c66a3f020740c2324c", size = 346110, upload-time = "2025-11-09T20:49:21.817Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "mdurl", marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and extra == 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", +] +dependencies = [ + { name = "mdurl", marker = "python_full_version >= '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +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]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/63/7bdd4adc330abcca54c85728db2327130e49e52e8c3ce685cec44e0f2e9f/multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349", size = 77153, upload-time = "2025-10-06T14:48:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/3f/bb/b6c35ff175ed1a3142222b78455ee31be71a8396ed3ab5280fbe3ebe4e85/multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e", size = 44993, upload-time = "2025-10-06T14:48:28.4Z" }, + { url = "https://files.pythonhosted.org/packages/e0/1f/064c77877c5fa6df6d346e68075c0f6998547afe952d6471b4c5f6a7345d/multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3", size = 44607, upload-time = "2025-10-06T14:48:29.581Z" }, + { url = "https://files.pythonhosted.org/packages/04/7a/bf6aa92065dd47f287690000b3d7d332edfccb2277634cadf6a810463c6a/multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046", size = 241847, upload-time = "2025-10-06T14:48:32.107Z" }, + { url = "https://files.pythonhosted.org/packages/94/39/297a8de920f76eda343e4ce05f3b489f0ab3f9504f2576dfb37b7c08ca08/multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32", size = 242616, upload-time = "2025-10-06T14:48:34.054Z" }, + { url = "https://files.pythonhosted.org/packages/39/3a/d0eee2898cfd9d654aea6cb8c4addc2f9756e9a7e09391cfe55541f917f7/multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73", size = 222333, upload-time = "2025-10-06T14:48:35.9Z" }, + { url = "https://files.pythonhosted.org/packages/05/48/3b328851193c7a4240815b71eea165b49248867bbb6153a0aee227a0bb47/multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc", size = 253239, upload-time = "2025-10-06T14:48:37.302Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ca/0706a98c8d126a89245413225ca4a3fefc8435014de309cf8b30acb68841/multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62", size = 251618, upload-time = "2025-10-06T14:48:38.963Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4f/9c7992f245554d8b173f6f0a048ad24b3e645d883f096857ec2c0822b8bd/multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84", size = 241655, upload-time = "2025-10-06T14:48:40.312Z" }, + { url = "https://files.pythonhosted.org/packages/31/79/26a85991ae67efd1c0b1fc2e0c275b8a6aceeb155a68861f63f87a798f16/multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0", size = 239245, upload-time = "2025-10-06T14:48:41.848Z" }, + { url = "https://files.pythonhosted.org/packages/14/1e/75fa96394478930b79d0302eaf9a6c69f34005a1a5251ac8b9c336486ec9/multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e", size = 233523, upload-time = "2025-10-06T14:48:43.749Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5e/085544cb9f9c4ad2b5d97467c15f856df8d9bac410cffd5c43991a5d878b/multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4", size = 243129, upload-time = "2025-10-06T14:48:45.225Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c3/e9d9e2f20c9474e7a8fcef28f863c5cbd29bb5adce6b70cebe8bdad0039d/multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648", size = 248999, upload-time = "2025-10-06T14:48:46.703Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3f/df171b6efa3239ae33b97b887e42671cd1d94d460614bfb2c30ffdab3b95/multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111", size = 243711, upload-time = "2025-10-06T14:48:48.146Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2f/9b5564888c4e14b9af64c54acf149263721a283aaf4aa0ae89b091d5d8c1/multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36", size = 237504, upload-time = "2025-10-06T14:48:49.447Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3a/0bd6ca0f7d96d790542d591c8c3354c1e1b6bfd2024d4d92dc3d87485ec7/multidict-6.7.0-cp310-cp310-win32.whl", hash = "sha256:afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85", size = 41422, upload-time = "2025-10-06T14:48:50.789Z" }, + { url = "https://files.pythonhosted.org/packages/00/35/f6a637ea2c75f0d3b7c7d41b1189189acff0d9deeb8b8f35536bb30f5e33/multidict-6.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7", size = 46050, upload-time = "2025-10-06T14:48:51.938Z" }, + { url = "https://files.pythonhosted.org/packages/e7/b8/f7bf8329b39893d02d9d95cf610c75885d12fc0f402b1c894e1c8e01c916/multidict-6.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0", size = 43153, upload-time = "2025-10-06T14:48:53.146Z" }, + { url = "https://files.pythonhosted.org/packages/34/9e/5c727587644d67b2ed479041e4b1c58e30afc011e3d45d25bbe35781217c/multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc", size = 76604, upload-time = "2025-10-06T14:48:54.277Z" }, + { url = "https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721", size = 44715, upload-time = "2025-10-06T14:48:55.445Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6", size = 44332, upload-time = "2025-10-06T14:48:56.706Z" }, + { url = "https://files.pythonhosted.org/packages/31/61/0c2d50241ada71ff61a79518db85ada85fdabfcf395d5968dae1cbda04e5/multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c", size = 245212, upload-time = "2025-10-06T14:48:58.042Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e0/919666a4e4b57fff1b57f279be1c9316e6cdc5de8a8b525d76f6598fefc7/multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7", size = 246671, upload-time = "2025-10-06T14:49:00.004Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cc/d027d9c5a520f3321b65adea289b965e7bcbd2c34402663f482648c716ce/multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7", size = 225491, upload-time = "2025-10-06T14:49:01.393Z" }, + { url = "https://files.pythonhosted.org/packages/75/c4/bbd633980ce6155a28ff04e6a6492dd3335858394d7bb752d8b108708558/multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9", size = 257322, upload-time = "2025-10-06T14:49:02.745Z" }, + { url = "https://files.pythonhosted.org/packages/4c/6d/d622322d344f1f053eae47e033b0b3f965af01212de21b10bcf91be991fb/multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8", size = 254694, upload-time = "2025-10-06T14:49:04.15Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd", size = 246715, upload-time = "2025-10-06T14:49:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/950818e04f91b9c2b95aab3d923d9eabd01689d0dcd889563988e9ea0fd8/multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb", size = 243189, upload-time = "2025-10-06T14:49:07.37Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3d/77c79e1934cad2ee74991840f8a0110966d9599b3af95964c0cd79bb905b/multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6", size = 237845, upload-time = "2025-10-06T14:49:08.759Z" }, + { url = "https://files.pythonhosted.org/packages/63/1b/834ce32a0a97a3b70f86437f685f880136677ac00d8bce0027e9fd9c2db7/multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2", size = 246374, upload-time = "2025-10-06T14:49:10.574Z" }, + { url = "https://files.pythonhosted.org/packages/23/ef/43d1c3ba205b5dec93dc97f3fba179dfa47910fc73aaaea4f7ceb41cec2a/multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff", size = 253345, upload-time = "2025-10-06T14:49:12.331Z" }, + { url = "https://files.pythonhosted.org/packages/6b/03/eaf95bcc2d19ead522001f6a650ef32811aa9e3624ff0ad37c445c7a588c/multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b", size = 246940, upload-time = "2025-10-06T14:49:13.821Z" }, + { url = "https://files.pythonhosted.org/packages/e8/df/ec8a5fd66ea6cd6f525b1fcbb23511b033c3e9bc42b81384834ffa484a62/multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34", size = 242229, upload-time = "2025-10-06T14:49:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a2/59b405d59fd39ec86d1142630e9049243015a5f5291ba49cadf3c090c541/multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff", size = 41308, upload-time = "2025-10-06T14:49:16.871Z" }, + { url = "https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81", size = 46037, upload-time = "2025-10-06T14:49:18.457Z" }, + { url = "https://files.pythonhosted.org/packages/84/1f/68588e31b000535a3207fd3c909ebeec4fb36b52c442107499c18a896a2a/multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912", size = 43023, upload-time = "2025-10-06T14:49:19.648Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, + { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, + { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, + { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, + { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, + { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, + { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, + { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload-time = "2025-10-06T14:49:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload-time = "2025-10-06T14:49:55.82Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload-time = "2025-10-06T14:49:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload-time = "2025-10-06T14:49:58.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload-time = "2025-10-06T14:49:59.89Z" }, + { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload-time = "2025-10-06T14:50:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload-time = "2025-10-06T14:50:02.955Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload-time = "2025-10-06T14:50:04.446Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload-time = "2025-10-06T14:50:05.98Z" }, + { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload-time = "2025-10-06T14:50:07.511Z" }, + { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload-time = "2025-10-06T14:50:09.074Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload-time = "2025-10-06T14:50:10.714Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload-time = "2025-10-06T14:50:12.28Z" }, + { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload-time = "2025-10-06T14:50:14.16Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload-time = "2025-10-06T14:50:15.639Z" }, + { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload-time = "2025-10-06T14:50:17.066Z" }, + { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload-time = "2025-10-06T14:50:18.264Z" }, + { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload-time = "2025-10-06T14:50:19.853Z" }, + { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload-time = "2025-10-06T14:50:21.223Z" }, + { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload-time = "2025-10-06T14:50:22.871Z" }, + { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload-time = "2025-10-06T14:50:24.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload-time = "2025-10-06T14:50:25.716Z" }, + { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload-time = "2025-10-06T14:50:28.192Z" }, + { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload-time = "2025-10-06T14:50:29.82Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload-time = "2025-10-06T14:50:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload-time = "2025-10-06T14:50:33.26Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload-time = "2025-10-06T14:50:34.808Z" }, + { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload-time = "2025-10-06T14:50:36.436Z" }, + { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload-time = "2025-10-06T14:50:37.953Z" }, + { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload-time = "2025-10-06T14:50:39.574Z" }, + { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload-time = "2025-10-06T14:50:41.612Z" }, + { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload-time = "2025-10-06T14:50:43.972Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload-time = "2025-10-06T14:50:45.648Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload-time = "2025-10-06T14:50:47.154Z" }, + { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload-time = "2025-10-06T14:50:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload-time = "2025-10-06T14:50:50.16Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload-time = "2025-10-06T14:50:51.92Z" }, + { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload-time = "2025-10-06T14:50:53.275Z" }, + { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload-time = "2025-10-06T14:50:54.911Z" }, + { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload-time = "2025-10-06T14:50:56.369Z" }, + { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload-time = "2025-10-06T14:50:57.991Z" }, + { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload-time = "2025-10-06T14:50:59.589Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload-time = "2025-10-06T14:51:01.183Z" }, + { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload-time = "2025-10-06T14:51:02.794Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload-time = "2025-10-06T14:51:04.724Z" }, + { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload-time = "2025-10-06T14:51:06.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload-time = "2025-10-06T14:51:08.091Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload-time = "2025-10-06T14:51:10.365Z" }, + { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload-time = "2025-10-06T14:51:12.466Z" }, + { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload-time = "2025-10-06T14:51:14.48Z" }, + { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload-time = "2025-10-06T14:51:16.072Z" }, + { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload-time = "2025-10-06T14:51:17.544Z" }, + { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload-time = "2025-10-06T14:51:18.875Z" }, + { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload-time = "2025-10-06T14:51:20.225Z" }, + { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload-time = "2025-10-06T14:51:21.588Z" }, + { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload-time = "2025-10-06T14:51:22.93Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload-time = "2025-10-06T14:51:24.352Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload-time = "2025-10-06T14:51:25.822Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload-time = "2025-10-06T14:51:27.604Z" }, + { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload-time = "2025-10-06T14:51:29.664Z" }, + { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload-time = "2025-10-06T14:51:31.684Z" }, + { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload-time = "2025-10-06T14:51:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload-time = "2025-10-06T14:51:36.189Z" }, + { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload-time = "2025-10-06T14:51:41.291Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload-time = "2025-10-06T14:51:43.55Z" }, + { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload-time = "2025-10-06T14:51:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload-time = "2025-10-06T14:51:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload-time = "2025-10-06T14:51:48.541Z" }, + { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload-time = "2025-10-06T14:51:50.355Z" }, + { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload-time = "2025-10-06T14:51:51.883Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload-time = "2025-10-06T14:51:53.672Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload-time = "2025-10-06T14:51:55.415Z" }, + { url = "https://files.pythonhosted.org/packages/90/d7/4cf84257902265c4250769ac49f4eaab81c182ee9aff8bf59d2714dbb174/multidict-6.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:363eb68a0a59bd2303216d2346e6c441ba10d36d1f9969fcb6f1ba700de7bb5c", size = 77073, upload-time = "2025-10-06T14:51:57.386Z" }, + { url = "https://files.pythonhosted.org/packages/6d/51/194e999630a656e76c2965a1590d12faa5cd528170f2abaa04423e09fe8d/multidict-6.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d874eb056410ca05fed180b6642e680373688efafc7f077b2a2f61811e873a40", size = 44928, upload-time = "2025-10-06T14:51:58.791Z" }, + { url = "https://files.pythonhosted.org/packages/e5/6b/2a195373c33068c9158e0941d0b46cfcc9c1d894ca2eb137d1128081dff0/multidict-6.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b55d5497b51afdfde55925e04a022f1de14d4f4f25cdfd4f5d9b0aa96166851", size = 44581, upload-time = "2025-10-06T14:52:00.174Z" }, + { url = "https://files.pythonhosted.org/packages/69/7b/7f4f2e644b6978bf011a5fd9a5ebb7c21de3f38523b1f7897d36a1ac1311/multidict-6.7.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f8e5c0031b90ca9ce555e2e8fd5c3b02a25f14989cbc310701823832c99eb687", size = 239901, upload-time = "2025-10-06T14:52:02.416Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b5/952c72786710a031aa204a9adf7db66d7f97a2c6573889d58b9e60fe6702/multidict-6.7.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cf41880c991716f3c7cec48e2f19ae4045fc9db5fc9cff27347ada24d710bb5", size = 240534, upload-time = "2025-10-06T14:52:04.105Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ef/109fe1f2471e4c458c74242c7e4a833f2d9fc8a6813cd7ee345b0bad18f9/multidict-6.7.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cfc12a8630a29d601f48d47787bd7eb730e475e83edb5d6c5084317463373eb", size = 219545, upload-time = "2025-10-06T14:52:06.208Z" }, + { url = "https://files.pythonhosted.org/packages/42/bd/327d91288114967f9fe90dc53de70aa3fec1b9073e46aa32c4828f771a87/multidict-6.7.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3996b50c3237c4aec17459217c1e7bbdead9a22a0fcd3c365564fbd16439dde6", size = 251187, upload-time = "2025-10-06T14:52:08.049Z" }, + { url = "https://files.pythonhosted.org/packages/f4/13/a8b078ebbaceb7819fd28cd004413c33b98f1b70d542a62e6a00b74fb09f/multidict-6.7.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7f5170993a0dd3ab871c74f45c0a21a4e2c37a2f2b01b5f722a2ad9c6650469e", size = 249379, upload-time = "2025-10-06T14:52:09.831Z" }, + { url = "https://files.pythonhosted.org/packages/e3/6d/ab12e1246be4d65d1f55de1e6f6aaa9b8120eddcfdd1d290439c7833d5ce/multidict-6.7.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec81878ddf0e98817def1e77d4f50dae5ef5b0e4fe796fae3bd674304172416e", size = 239241, upload-time = "2025-10-06T14:52:11.561Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d7/079a93625208c173b8fa756396814397c0fd9fee61ef87b75a748820b86e/multidict-6.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9281bf5b34f59afbc6b1e477a372e9526b66ca446f4bf62592839c195a718b32", size = 237418, upload-time = "2025-10-06T14:52:13.671Z" }, + { url = "https://files.pythonhosted.org/packages/c9/29/03777c2212274aa9440918d604dc9d6af0e6b4558c611c32c3dcf1a13870/multidict-6.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:68af405971779d8b37198726f2b6fe3955db846fee42db7a4286fc542203934c", size = 232987, upload-time = "2025-10-06T14:52:15.708Z" }, + { url = "https://files.pythonhosted.org/packages/d9/00/11188b68d85a84e8050ee34724d6ded19ad03975caebe0c8dcb2829b37bf/multidict-6.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ba3ef510467abb0667421a286dc906e30eb08569365f5cdb131d7aff7c2dd84", size = 240985, upload-time = "2025-10-06T14:52:17.317Z" }, + { url = "https://files.pythonhosted.org/packages/df/0c/12eef6aeda21859c6cdf7d75bd5516d83be3efe3d8cc45fd1a3037f5b9dc/multidict-6.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b61189b29081a20c7e4e0b49b44d5d44bb0dc92be3c6d06a11cc043f81bf9329", size = 246855, upload-time = "2025-10-06T14:52:19.096Z" }, + { url = "https://files.pythonhosted.org/packages/69/f6/076120fd8bb3975f09228e288e08bff6b9f1bfd5166397c7ba284f622ab2/multidict-6.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fb287618b9c7aa3bf8d825f02d9201b2f13078a5ed3b293c8f4d953917d84d5e", size = 241804, upload-time = "2025-10-06T14:52:21.166Z" }, + { url = "https://files.pythonhosted.org/packages/5f/51/41bb950c81437b88a93e6ddfca1d8763569ae861e638442838c4375f7497/multidict-6.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:521f33e377ff64b96c4c556b81c55d0cfffb96a11c194fd0c3f1e56f3d8dd5a4", size = 235321, upload-time = "2025-10-06T14:52:23.208Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cf/5bbd31f055199d56c1f6b04bbadad3ccb24e6d5d4db75db774fc6d6674b8/multidict-6.7.0-cp39-cp39-win32.whl", hash = "sha256:ce8fdc2dca699f8dbf055a61d73eaa10482569ad20ee3c36ef9641f69afa8c91", size = 41435, upload-time = "2025-10-06T14:52:24.735Z" }, + { url = "https://files.pythonhosted.org/packages/af/01/547ffe9c2faec91c26965c152f3fea6cff068b6037401f61d310cc861ff4/multidict-6.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:7e73299c99939f089dd9b2120a04a516b95cdf8c1cd2b18c53ebf0de80b1f18f", size = 46193, upload-time = "2025-10-06T14:52:26.101Z" }, + { url = "https://files.pythonhosted.org/packages/27/77/cfa5461d1d2651d6fc24216c92b4a21d4e385a41c46e0d9f3b070675167b/multidict-6.7.0-cp39-cp39-win_arm64.whl", hash = "sha256:6bdce131e14b04fd34a809b6380dbfd826065c3e2fe8a50dbae659fa0c390546", size = 43118, upload-time = "2025-10-06T14:52:27.876Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, +] + +[[package]] +name = "mypy" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114, upload-time = "2025-07-14T20:34:30.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/31/e762baa3b73905c856d45ab77b4af850e8159dffffd86a52879539a08c6b/mypy-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8e08de6138043108b3b18f09d3f817a4783912e48828ab397ecf183135d84d6", size = 10998313, upload-time = "2025-07-14T20:33:24.519Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c1/25b2f0d46fb7e0b5e2bee61ec3a47fe13eff9e3c2f2234f144858bbe6485/mypy-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce4a17920ec144647d448fc43725b5873548b1aae6c603225626747ededf582d", size = 10128922, upload-time = "2025-07-14T20:34:06.414Z" }, + { url = "https://files.pythonhosted.org/packages/02/78/6d646603a57aa8a2886df1b8881fe777ea60f28098790c1089230cd9c61d/mypy-1.17.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ff25d151cc057fdddb1cb1881ef36e9c41fa2a5e78d8dd71bee6e4dcd2bc05b", size = 11913524, upload-time = "2025-07-14T20:33:19.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/19/dae6c55e87ee426fb76980f7e78484450cad1c01c55a1dc4e91c930bea01/mypy-1.17.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93468cf29aa9a132bceb103bd8475f78cacde2b1b9a94fd978d50d4bdf616c9a", size = 12650527, upload-time = "2025-07-14T20:32:44.095Z" }, + { url = "https://files.pythonhosted.org/packages/86/e1/f916845a235235a6c1e4d4d065a3930113767001d491b8b2e1b61ca56647/mypy-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:98189382b310f16343151f65dd7e6867386d3e35f7878c45cfa11383d175d91f", size = 12897284, upload-time = "2025-07-14T20:33:38.168Z" }, + { url = "https://files.pythonhosted.org/packages/ae/dc/414760708a4ea1b096bd214d26a24e30ac5e917ef293bc33cdb6fe22d2da/mypy-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:c004135a300ab06a045c1c0d8e3f10215e71d7b4f5bb9a42ab80236364429937", size = 9506493, upload-time = "2025-07-14T20:34:01.093Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/82efb502b0b0f661c49aa21cfe3e1999ddf64bf5500fc03b5a1536a39d39/mypy-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d4fe5c72fd262d9c2c91c1117d16aac555e05f5beb2bae6a755274c6eec42be", size = 10914150, upload-time = "2025-07-14T20:31:51.985Z" }, + { url = "https://files.pythonhosted.org/packages/03/96/8ef9a6ff8cedadff4400e2254689ca1dc4b420b92c55255b44573de10c54/mypy-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96b196e5c16f41b4f7736840e8455958e832871990c7ba26bf58175e357ed61", size = 10039845, upload-time = "2025-07-14T20:32:30.527Z" }, + { url = "https://files.pythonhosted.org/packages/df/32/7ce359a56be779d38021d07941cfbb099b41411d72d827230a36203dbb81/mypy-1.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73a0ff2dd10337ceb521c080d4147755ee302dcde6e1a913babd59473904615f", size = 11837246, upload-time = "2025-07-14T20:32:01.28Z" }, + { url = "https://files.pythonhosted.org/packages/82/16/b775047054de4d8dbd668df9137707e54b07fe18c7923839cd1e524bf756/mypy-1.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cfcc1179c4447854e9e406d3af0f77736d631ec87d31c6281ecd5025df625d", size = 12571106, upload-time = "2025-07-14T20:34:26.942Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/fa33eaf29a606102c8d9ffa45a386a04c2203d9ad18bf4eef3e20c43ebc8/mypy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56f180ff6430e6373db7a1d569317675b0a451caf5fef6ce4ab365f5f2f6c3", size = 12759960, upload-time = "2025-07-14T20:33:42.882Z" }, + { url = "https://files.pythonhosted.org/packages/94/75/3f5a29209f27e739ca57e6350bc6b783a38c7621bdf9cac3ab8a08665801/mypy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:eafaf8b9252734400f9b77df98b4eee3d2eecab16104680d51341c75702cad70", size = 9503888, upload-time = "2025-07-14T20:32:34.392Z" }, + { url = "https://files.pythonhosted.org/packages/12/e9/e6824ed620bbf51d3bf4d6cbbe4953e83eaf31a448d1b3cfb3620ccb641c/mypy-1.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f986f1cab8dbec39ba6e0eaa42d4d3ac6686516a5d3dccd64be095db05ebc6bb", size = 11086395, upload-time = "2025-07-14T20:34:11.452Z" }, + { url = "https://files.pythonhosted.org/packages/ba/51/a4afd1ae279707953be175d303f04a5a7bd7e28dc62463ad29c1c857927e/mypy-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:51e455a54d199dd6e931cd7ea987d061c2afbaf0960f7f66deef47c90d1b304d", size = 10120052, upload-time = "2025-07-14T20:33:09.897Z" }, + { url = "https://files.pythonhosted.org/packages/8a/71/19adfeac926ba8205f1d1466d0d360d07b46486bf64360c54cb5a2bd86a8/mypy-1.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3204d773bab5ff4ebbd1f8efa11b498027cd57017c003ae970f310e5b96be8d8", size = 11861806, upload-time = "2025-07-14T20:32:16.028Z" }, + { url = "https://files.pythonhosted.org/packages/0b/64/d6120eca3835baf7179e6797a0b61d6c47e0bc2324b1f6819d8428d5b9ba/mypy-1.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1051df7ec0886fa246a530ae917c473491e9a0ba6938cfd0ec2abc1076495c3e", size = 12744371, upload-time = "2025-07-14T20:33:33.503Z" }, + { url = "https://files.pythonhosted.org/packages/1f/dc/56f53b5255a166f5bd0f137eed960e5065f2744509dfe69474ff0ba772a5/mypy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f773c6d14dcc108a5b141b4456b0871df638eb411a89cd1c0c001fc4a9d08fc8", size = 12914558, upload-time = "2025-07-14T20:33:56.961Z" }, + { url = "https://files.pythonhosted.org/packages/69/ac/070bad311171badc9add2910e7f89271695a25c136de24bbafc7eded56d5/mypy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:1619a485fd0e9c959b943c7b519ed26b712de3002d7de43154a489a2d0fd817d", size = 9585447, upload-time = "2025-07-14T20:32:20.594Z" }, + { url = "https://files.pythonhosted.org/packages/be/7b/5f8ab461369b9e62157072156935cec9d272196556bdc7c2ff5f4c7c0f9b/mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06", size = 11070019, upload-time = "2025-07-14T20:32:07.99Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f8/c49c9e5a2ac0badcc54beb24e774d2499748302c9568f7f09e8730e953fa/mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a", size = 10114457, upload-time = "2025-07-14T20:33:47.285Z" }, + { url = "https://files.pythonhosted.org/packages/89/0c/fb3f9c939ad9beed3e328008b3fb90b20fda2cddc0f7e4c20dbefefc3b33/mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889", size = 11857838, upload-time = "2025-07-14T20:33:14.462Z" }, + { url = "https://files.pythonhosted.org/packages/4c/66/85607ab5137d65e4f54d9797b77d5a038ef34f714929cf8ad30b03f628df/mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba", size = 12731358, upload-time = "2025-07-14T20:32:25.579Z" }, + { url = "https://files.pythonhosted.org/packages/73/d0/341dbbfb35ce53d01f8f2969facbb66486cee9804048bf6c01b048127501/mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658", size = 12917480, upload-time = "2025-07-14T20:34:21.868Z" }, + { url = "https://files.pythonhosted.org/packages/64/63/70c8b7dbfc520089ac48d01367a97e8acd734f65bd07813081f508a8c94c/mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c", size = 9589666, upload-time = "2025-07-14T20:34:16.841Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a0/6263dd11941231f688f0a8f2faf90ceac1dc243d148d314a089d2fe25108/mypy-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:63e751f1b5ab51d6f3d219fe3a2fe4523eaa387d854ad06906c63883fde5b1ab", size = 10988185, upload-time = "2025-07-14T20:33:04.797Z" }, + { url = "https://files.pythonhosted.org/packages/02/13/b8f16d6b0dc80277129559c8e7dbc9011241a0da8f60d031edb0e6e9ac8f/mypy-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7fb09d05e0f1c329a36dcd30e27564a3555717cde87301fae4fb542402ddfad", size = 10120169, upload-time = "2025-07-14T20:32:38.84Z" }, + { url = "https://files.pythonhosted.org/packages/14/ef/978ba79df0d65af680e20d43121363cf643eb79b04bf3880d01fc8afeb6f/mypy-1.17.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b72c34ce05ac3a1361ae2ebb50757fb6e3624032d91488d93544e9f82db0ed6c", size = 11918121, upload-time = "2025-07-14T20:33:52.328Z" }, + { url = "https://files.pythonhosted.org/packages/f4/10/55ef70b104151a0d8280474f05268ff0a2a79be8d788d5e647257d121309/mypy-1.17.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:434ad499ad8dde8b2f6391ddfa982f41cb07ccda8e3c67781b1bfd4e5f9450a8", size = 12648821, upload-time = "2025-07-14T20:32:59.631Z" }, + { url = "https://files.pythonhosted.org/packages/26/8c/7781fcd2e1eef48fbedd3a422c21fe300a8e03ed5be2eb4bd10246a77f4e/mypy-1.17.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f105f61a5eff52e137fd73bee32958b2add9d9f0a856f17314018646af838e97", size = 12896955, upload-time = "2025-07-14T20:32:49.543Z" }, + { url = "https://files.pythonhosted.org/packages/78/13/03ac759dabe86e98ca7b6681f114f90ee03f3ff8365a57049d311bd4a4e3/mypy-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:ba06254a5a22729853209550d80f94e28690d5530c661f9416a68ac097b13fc4", size = 9512957, upload-time = "2025-07-14T20:33:28.619Z" }, + { url = "https://files.pythonhosted.org/packages/e3/fc/ee058cc4316f219078464555873e99d170bde1d9569abd833300dbeb484a/mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496", size = 2283195, upload-time = "2025-07-14T20:31:54.753Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/0e/934b541323035566a9af292dba85a195f7b78179114f2c6ebb24551118a9/propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db", size = 79534, upload-time = "2025-10-08T19:46:02.083Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6b/db0d03d96726d995dc7171286c6ba9d8d14251f37433890f88368951a44e/propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8", size = 45526, upload-time = "2025-10-08T19:46:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c3/82728404aea669e1600f304f2609cde9e665c18df5a11cdd57ed73c1dceb/propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925", size = 47263, upload-time = "2025-10-08T19:46:05.405Z" }, + { url = "https://files.pythonhosted.org/packages/df/1b/39313ddad2bf9187a1432654c38249bab4562ef535ef07f5eb6eb04d0b1b/propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21", size = 201012, upload-time = "2025-10-08T19:46:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/5b/01/f1d0b57d136f294a142acf97f4ed58c8e5b974c21e543000968357115011/propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5", size = 209491, upload-time = "2025-10-08T19:46:08.909Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c8/038d909c61c5bb039070b3fb02ad5cccdb1dde0d714792e251cdb17c9c05/propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db", size = 215319, upload-time = "2025-10-08T19:46:10.7Z" }, + { url = "https://files.pythonhosted.org/packages/08/57/8c87e93142b2c1fa2408e45695205a7ba05fb5db458c0bf5c06ba0e09ea6/propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7", size = 196856, upload-time = "2025-10-08T19:46:12.003Z" }, + { url = "https://files.pythonhosted.org/packages/42/df/5615fec76aa561987a534759b3686008a288e73107faa49a8ae5795a9f7a/propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4", size = 193241, upload-time = "2025-10-08T19:46:13.495Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/62949eb3a7a54afe8327011c90aca7e03547787a88fb8bd9726806482fea/propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60", size = 190552, upload-time = "2025-10-08T19:46:14.938Z" }, + { url = "https://files.pythonhosted.org/packages/30/ee/ab4d727dd70806e5b4de96a798ae7ac6e4d42516f030ee60522474b6b332/propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f", size = 200113, upload-time = "2025-10-08T19:46:16.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0b/38b46208e6711b016aa8966a3ac793eee0d05c7159d8342aa27fc0bc365e/propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900", size = 200778, upload-time = "2025-10-08T19:46:18.023Z" }, + { url = "https://files.pythonhosted.org/packages/cf/81/5abec54355ed344476bee711e9f04815d4b00a311ab0535599204eecc257/propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c", size = 193047, upload-time = "2025-10-08T19:46:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b6/1f237c04e32063cb034acd5f6ef34ef3a394f75502e72703545631ab1ef6/propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb", size = 38093, upload-time = "2025-10-08T19:46:20.643Z" }, + { url = "https://files.pythonhosted.org/packages/a6/67/354aac4e0603a15f76439caf0427781bcd6797f370377f75a642133bc954/propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37", size = 41638, upload-time = "2025-10-08T19:46:21.935Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e1/74e55b9fd1a4c209ff1a9a824bf6c8b3d1fc5a1ac3eabe23462637466785/propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581", size = 38229, upload-time = "2025-10-08T19:46:23.368Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/9b/01/0ebaec9003f5d619a7475165961f8e3083cf8644d704b60395df3601632d/propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff", size = 80277, upload-time = "2025-10-08T19:48:36.647Z" }, + { url = "https://files.pythonhosted.org/packages/34/58/04af97ac586b4ef6b9026c3fd36ee7798b737a832f5d3440a4280dcebd3a/propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb", size = 45865, upload-time = "2025-10-08T19:48:37.859Z" }, + { url = "https://files.pythonhosted.org/packages/7c/19/b65d98ae21384518b291d9939e24a8aeac4fdb5101b732576f8f7540e834/propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac", size = 47636, upload-time = "2025-10-08T19:48:39.038Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0f/317048c6d91c356c7154dca5af019e6effeb7ee15fa6a6db327cc19e12b4/propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888", size = 201126, upload-time = "2025-10-08T19:48:40.774Z" }, + { url = "https://files.pythonhosted.org/packages/71/69/0b2a7a5a6ee83292b4b997dbd80549d8ce7d40b6397c1646c0d9495f5a85/propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc", size = 209837, upload-time = "2025-10-08T19:48:42.167Z" }, + { url = "https://files.pythonhosted.org/packages/a5/92/c699ac495a6698df6e497fc2de27af4b6ace10d8e76528357ce153722e45/propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a", size = 215578, upload-time = "2025-10-08T19:48:43.56Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ee/14de81c5eb02c0ee4f500b4e39c4e1bd0677c06e72379e6ab18923c773fc/propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88", size = 197187, upload-time = "2025-10-08T19:48:45.309Z" }, + { url = "https://files.pythonhosted.org/packages/1d/94/48dce9aaa6d8dd5a0859bad75158ec522546d4ac23f8e2f05fac469477dd/propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00", size = 193478, upload-time = "2025-10-08T19:48:47.743Z" }, + { url = "https://files.pythonhosted.org/packages/60/b5/0516b563e801e1ace212afde869a0596a0d7115eec0b12d296d75633fb29/propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0", size = 190650, upload-time = "2025-10-08T19:48:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/24/89/e0f7d4a5978cd56f8cd67735f74052f257dc471ec901694e430f0d1572fe/propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e", size = 200251, upload-time = "2025-10-08T19:48:51.4Z" }, + { url = "https://files.pythonhosted.org/packages/06/7d/a1fac863d473876ed4406c914f2e14aa82d2f10dd207c9e16fc383cc5a24/propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781", size = 200919, upload-time = "2025-10-08T19:48:53.227Z" }, + { url = "https://files.pythonhosted.org/packages/c3/4e/f86a256ff24944cf5743e4e6c6994e3526f6acfcfb55e21694c2424f758c/propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183", size = 193211, upload-time = "2025-10-08T19:48:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/6e/3f/3fbad5f4356b068f1b047d300a6ff2c66614d7030f078cd50be3fec04228/propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19", size = 38314, upload-time = "2025-10-08T19:48:56.792Z" }, + { url = "https://files.pythonhosted.org/packages/a4/45/d78d136c3a3d215677abb886785aae744da2c3005bcb99e58640c56529b1/propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f", size = 41912, upload-time = "2025-10-08T19:48:57.995Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2a/b0632941f25139f4e58450b307242951f7c2717a5704977c6d5323a800af/propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938", size = 38450, upload-time = "2025-10-08T19:48:59.349Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pydantic" +version = "1.10.24" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version < '3.10'", +] +dependencies = [ + { name = "typing-extensions", marker = "extra == 'group-12-dedalus-labs-pydantic-v1'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/8d/7b346ed940c3e0f9eee7db9be37915a6dac0d9535d736e2ca47a81a066f3/pydantic-1.10.24.tar.gz", hash = "sha256:7e6d1af1bd3d2312079f28c9baf2aafb4a452a06b50717526e5ac562e37baa53", size = 357314, upload-time = "2025-09-25T01:36:33.065Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/6e/71eb0c860bf888e73243fbc22be79c47e68180b65b33036efb5a1f1085de/pydantic-1.10.24-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eef07ea2fba12f9188cfa2c50cb3eaa6516b56c33e2a8cc3cd288b4190ee6c0c", size = 2494239, upload-time = "2025-09-25T01:35:02.451Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1f/a2c09049c2ec33b88b111aa99e4bbfe9e821914dcf2ce662e00fa1423fa8/pydantic-1.10.24-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a42033fac69b9f1f867ecc3a2159f0e94dceb1abfc509ad57e9e88d49774683", size = 2302370, upload-time = "2025-09-25T01:35:05.386Z" }, + { url = "https://files.pythonhosted.org/packages/70/73/fde9af3a76cc5714880828eee50c0f7f1b263d2c77a74c65ba19325b4706/pydantic-1.10.24-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c626596c1b95dc6d45f7129f10b6743fbb50f29d942d25a22b2ceead670c067d", size = 2960499, upload-time = "2025-09-25T01:35:07.243Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d5/9fd98afa478020e9bad54a9ec6e42ba71f8a1a7f6df4d12ce5be76b0a96a/pydantic-1.10.24-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8057172868b0d98f95e6fcddcc5f75d01570e85c6308702dd2c50ea673bc197b", size = 3031125, upload-time = "2025-09-25T01:35:09.048Z" }, + { url = "https://files.pythonhosted.org/packages/1c/99/2fc6df8644c096dc6e3347e1793868a758df874eaf5ba52ca8b5a80e42d8/pydantic-1.10.24-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:82f951210ebcdb778b1d93075af43adcd04e9ebfd4f44b1baa8eeb21fbd71e36", size = 3099888, upload-time = "2025-09-25T01:35:10.894Z" }, + { url = "https://files.pythonhosted.org/packages/71/71/2f4c115951140f525136089da491b0bb4b7d24de8d697913afedde3f326c/pydantic-1.10.24-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b66e4892d8ae005f436a5c5f1519ecf837574d8414b1c93860fb3c13943d9b37", size = 3038385, upload-time = "2025-09-25T01:35:12.744Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/d3893a9b8479a0ea9357ba4a1eb84a5776a80705b5409bda4ad9e7ca0804/pydantic-1.10.24-cp310-cp310-win_amd64.whl", hash = "sha256:50d9f8a207c07f347d4b34806dc576872000d9a60fd481ed9eb78ea8512e0666", size = 2093504, upload-time = "2025-09-25T01:35:14.439Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b5/1b49b94e99ae4cad5f034c4b33e9ab481e53238fb55b59ffed5c6e6ee4cf/pydantic-1.10.24-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:70152291488f8d2bbcf2027b5c28c27724c78a7949c91b466d28ad75d6d12702", size = 2526778, upload-time = "2025-09-25T01:35:16.448Z" }, + { url = "https://files.pythonhosted.org/packages/87/d8/63fb1850ca93511b324d709f1c5bd31131039f9b93d0bc2ae210285db6d1/pydantic-1.10.24-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:956b30638272c51c85caaff76851b60db4b339022c0ee6eca677c41e3646255b", size = 2307760, upload-time = "2025-09-25T01:35:18.234Z" }, + { url = "https://files.pythonhosted.org/packages/2a/b8/428453ce573b8898afaf39a5ce32f7dbacf54f8aad9ce9c0abf19a1cdb2c/pydantic-1.10.24-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bed9d6eea5fabbc6978c42e947190c7bd628ddaff3b56fc963fe696c3710ccd6", size = 2902586, upload-time = "2025-09-25T01:35:20.118Z" }, + { url = "https://files.pythonhosted.org/packages/96/e0/68b5eb3c26b5e7136a8946f00f6d2eb8ef2fde530fcf6b491c66e3989d0d/pydantic-1.10.24-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af8e2b3648128b8cadb1a71e2f8092a6f42d4ca123fad7a8d7ce6db8938b1db3", size = 2976378, upload-time = "2025-09-25T01:35:22.077Z" }, + { url = "https://files.pythonhosted.org/packages/93/8c/2c6c46b7bc265ba35bad019c63f77d9ef44fabc026353768d7e6ea16dd51/pydantic-1.10.24-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:076fff9da02ca716e4c8299c68512fdfbeac32fdefc9c160e6f80bdadca0993d", size = 3063515, upload-time = "2025-09-25T01:35:24.048Z" }, + { url = "https://files.pythonhosted.org/packages/84/be/a051e26eff43b6af69f968c1085cdf9069628a7c3614a9836d3ce71327e4/pydantic-1.10.24-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8f2447ca88a7e14fd4d268857521fb37535c53a367b594fa2d7c2551af905993", size = 2988590, upload-time = "2025-09-25T01:35:25.794Z" }, + { url = "https://files.pythonhosted.org/packages/da/d8/f1aca10d538a0f18d2c99f7e84d3bb5c4abb6bd499272d6c4fc21f39af30/pydantic-1.10.24-cp311-cp311-win_amd64.whl", hash = "sha256:58d42a7c344882c00e3bb7c6c8c6f62db2e3aafa671f307271c45ad96e8ccf7a", size = 2096524, upload-time = "2025-09-25T01:35:27.367Z" }, + { url = "https://files.pythonhosted.org/packages/79/4b/73b59168d0babc14fb40b56795bde269d15709ef33de888e12e4f0add5ea/pydantic-1.10.24-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:17e7610119483f03954569c18d4de16f4e92f1585f20975414033ac2d4a96624", size = 2533707, upload-time = "2025-09-25T01:35:28.953Z" }, + { url = "https://files.pythonhosted.org/packages/6d/36/18e6f421a23ddceecfc5d3800d0e86af05e85574aa9e88cc9e29222db066/pydantic-1.10.24-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e24435a9970dcb2b35648f2cf57505d4bd414fcca1a404c82e28d948183fe0a6", size = 2322935, upload-time = "2025-09-25T01:35:30.838Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/4d3fc4bea30add2f2f3c287e931b276f7e304bcb322fe5b2c05a76ccdee7/pydantic-1.10.24-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a9e92b9c78d7f3cfa085c21c110e7000894446e24a836d006aabfc6ae3f1813", size = 2779568, upload-time = "2025-09-25T01:35:32.309Z" }, + { url = "https://files.pythonhosted.org/packages/15/32/5349a7b6675d4384f07f9d461d8230de877b2b913529aa20e659c84bab07/pydantic-1.10.24-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef14dfa7c98b314a3e449e92df6f1479cafe74c626952f353ff0176b075070de", size = 2829163, upload-time = "2025-09-25T01:35:34.294Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/4d93755e279e8975f7f33adc0af0e9d9aa0db58bcd9c807227d65b396311/pydantic-1.10.24-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52219b4e70c1db185cfd103a804e416384e1c8950168a2d4f385664c7c35d21a", size = 2912003, upload-time = "2025-09-25T01:35:35.935Z" }, + { url = "https://files.pythonhosted.org/packages/db/0c/c839c2a9cf14185c7b5dcc0959d3c3d4a00da400fe02565abf04a7dff6e0/pydantic-1.10.24-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ce0986799248082e9a5a026c9b5d2f9fa2e24d2afb9b0eace9104334a58fdc1", size = 2859825, upload-time = "2025-09-25T01:35:37.657Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0c/f0b8e35033322c176574d6f431455c8d6e3f63811a2c5a00c96b2b97a393/pydantic-1.10.24-cp312-cp312-win_amd64.whl", hash = "sha256:874a78e4ed821258295a472e325eee7de3d91ba7a61d0639ce1b0367a3c63d4c", size = 1969911, upload-time = "2025-09-25T01:35:39.479Z" }, + { url = "https://files.pythonhosted.org/packages/bd/56/9168c282af2bb8bdb102981a9ff0ed41ab4d3735a52b732b2d2ad0e14018/pydantic-1.10.24-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:265788a1120285c4955f8b3d52b3ea6a52c7a74db097c4c13a4d3567f0c6df3c", size = 2589497, upload-time = "2025-09-25T01:35:41.141Z" }, + { url = "https://files.pythonhosted.org/packages/9c/eb/eb4b0e2988a2889a1905c3196f859509e62c208830889d2382928b92fdd2/pydantic-1.10.24-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d255bebd927e5f1e026b32605684f7b6fc36a13e62b07cb97b29027b91657def", size = 2351231, upload-time = "2025-09-25T01:35:43.221Z" }, + { url = "https://files.pythonhosted.org/packages/1e/be/7451b633ffdc2d28de582a339af2275c3ffcca789dda97d8ac9133f0c616/pydantic-1.10.24-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6e45dbc79a44e34c2c83ef1fcb56ff663040474dcf4dfc452db24a1de0f7574", size = 2762972, upload-time = "2025-09-25T01:35:45.304Z" }, + { url = "https://files.pythonhosted.org/packages/9e/fb/5de3cfde0b808f2fa0538ec1f1c186f44d905ecbcc96ba22e2cac1f30b23/pydantic-1.10.24-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af31565b12a7db5bfa5fe8c3a4f8fda4d32f5c2929998b1b241f1c22e9ab6e69", size = 2801015, upload-time = "2025-09-25T01:35:46.774Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6a/9b6b51d19d1af57e8864caff08ce5e8554388b91dc41987ce49315bce3e1/pydantic-1.10.24-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9c377fc30d9ca40dbff5fd79c5a5e1f0d6fff040fa47a18851bb6b0bd040a5d8", size = 2890844, upload-time = "2025-09-25T01:35:48.724Z" }, + { url = "https://files.pythonhosted.org/packages/27/ca/1ab6b16bd792c8a1fb54949d8b5eef8032d672932ca4afc3048e4febfcdc/pydantic-1.10.24-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b644d6f14b2ce617d6def21622f9ba73961a16b7dffdba7f6692e2f66fa05d00", size = 2850844, upload-time = "2025-09-25T01:35:50.279Z" }, + { url = "https://files.pythonhosted.org/packages/86/5f/fcc5635818113858a6b37099fed6b860a15b27bb1d0fb270ceb50d0a91b6/pydantic-1.10.24-cp313-cp313-win_amd64.whl", hash = "sha256:0cbbf306124ae41cc153fdc2559b37faa1bec9a23ef7b082c1756d1315ceffe6", size = 1971713, upload-time = "2025-09-25T01:35:52.027Z" }, + { url = "https://files.pythonhosted.org/packages/a9/29/62dd3ffcf7d003f53e834942e9651c2ddd9dc6fb59e6619317e0ed37cf6b/pydantic-1.10.24-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25fb9a69a21d711deb5acefdab9ff8fb49e6cc77fdd46d38217d433bff2e3de2", size = 2504290, upload-time = "2025-09-25T01:36:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/f2/83/ef9c4be8e7fc96f52320296aed34f7cbe50fa0219833cc2756e611b644f2/pydantic-1.10.24-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6af36a8fb3072526b5b38d3f341b12d8f423188e7d185f130c0079fe02cdec7f", size = 2311007, upload-time = "2025-09-25T01:36:18.75Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b7/ec7da8fbaac8c8100b05301a81fac6b2b7446961edb91bbef4b564834abf/pydantic-1.10.24-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fc35569dfd15d3b3fc06a22abee0a45fdde0784be644e650a8769cd0b2abd94", size = 2968514, upload-time = "2025-09-25T01:36:20.511Z" }, + { url = "https://files.pythonhosted.org/packages/49/84/9e218a35008fbc32dac2974a35a4bd88d7deb0f5b572cf46ccf003a06310/pydantic-1.10.24-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fac7fbcb65171959973f3136d0792c3d1668bc01fd414738f0898b01f692f1b4", size = 3039539, upload-time = "2025-09-25T01:36:24.359Z" }, + { url = "https://files.pythonhosted.org/packages/b0/2f/b13a8c2d641e3af3fbba136202a9808025ee7cde4b1326ce1aabd1c79d51/pydantic-1.10.24-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fc3f4a6544517380658b63b144c7d43d5276a343012913b7e5d18d9fba2f12bb", size = 3108949, upload-time = "2025-09-25T01:36:26.138Z" }, + { url = "https://files.pythonhosted.org/packages/1f/57/dccbf080b35b9797f4d477f4c59935e39e4493cd507f31b5ca5ee49c930d/pydantic-1.10.24-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:415c638ca5fd57b915a62dd38c18c8e0afe5adf5527be6f8ce16b4636b616816", size = 3049395, upload-time = "2025-09-25T01:36:27.782Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ff/2a25855a1495fcbe1d3b8c782276994575e98ce2218dbf57c1f2eee7c894/pydantic-1.10.24-cp39-cp39-win_amd64.whl", hash = "sha256:a5bf94042efbc6ab56b18a5921f426ebbeefc04f554a911d76029e7be9057d01", size = 2100530, upload-time = "2025-09-25T01:36:29.932Z" }, + { url = "https://files.pythonhosted.org/packages/46/7f/a168d7077f85f85128aa5636abf13c804c06235c786f1881e659703899a4/pydantic-1.10.24-py3-none-any.whl", hash = "sha256:093768eba26db55a88b12f3073017e3fdee319ef60d3aef5c6c04a4e484db193", size = 166727, upload-time = "2025-09-25T01:36:31.732Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version < '3.10' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version < '3.10' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", +] +dependencies = [ + { name = "annotated-types", marker = "extra == 'group-12-dedalus-labs-pydantic-v2' or extra != 'group-12-dedalus-labs-pydantic-v1'" }, + { name = "pydantic-core", marker = "extra == 'group-12-dedalus-labs-pydantic-v2' or extra != 'group-12-dedalus-labs-pydantic-v1'" }, + { name = "typing-extensions", marker = "extra == 'group-12-dedalus-labs-pydantic-v2' or extra != 'group-12-dedalus-labs-pydantic-v1'" }, + { name = "typing-inspection", marker = "extra == 'group-12-dedalus-labs-pydantic-v2' or extra != 'group-12-dedalus-labs-pydantic-v1'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "extra == 'group-12-dedalus-labs-pydantic-v2' or extra != 'group-12-dedalus-labs-pydantic-v1'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, + { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, + { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, + { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, + { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/54/db/160dffb57ed9a3705c4cbcbff0ac03bdae45f1ca7d58ab74645550df3fbd/pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf", size = 2107999, upload-time = "2025-11-04T13:42:03.885Z" }, + { url = "https://files.pythonhosted.org/packages/a3/7d/88e7de946f60d9263cc84819f32513520b85c0f8322f9b8f6e4afc938383/pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5", size = 1929745, upload-time = "2025-11-04T13:42:06.075Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c2/aef51e5b283780e85e99ff19db0f05842d2d4a8a8cd15e63b0280029b08f/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d", size = 1920220, upload-time = "2025-11-04T13:42:08.457Z" }, + { url = "https://files.pythonhosted.org/packages/c7/97/492ab10f9ac8695cd76b2fdb24e9e61f394051df71594e9bcc891c9f586e/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60", size = 2067296, upload-time = "2025-11-04T13:42:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/ec/23/984149650e5269c59a2a4c41d234a9570adc68ab29981825cfaf4cfad8f4/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82", size = 2231548, upload-time = "2025-11-04T13:42:13.843Z" }, + { url = "https://files.pythonhosted.org/packages/71/0c/85bcbb885b9732c28bec67a222dbed5ed2d77baee1f8bba2002e8cd00c5c/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5", size = 2362571, upload-time = "2025-11-04T13:42:16.208Z" }, + { url = "https://files.pythonhosted.org/packages/c0/4a/412d2048be12c334003e9b823a3fa3d038e46cc2d64dd8aab50b31b65499/pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3", size = 2068175, upload-time = "2025-11-04T13:42:18.911Z" }, + { url = "https://files.pythonhosted.org/packages/73/f4/c58b6a776b502d0a5540ad02e232514285513572060f0d78f7832ca3c98b/pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425", size = 2177203, upload-time = "2025-11-04T13:42:22.578Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ae/f06ea4c7e7a9eead3d165e7623cd2ea0cb788e277e4f935af63fc98fa4e6/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504", size = 2148191, upload-time = "2025-11-04T13:42:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/c1/57/25a11dcdc656bf5f8b05902c3c2934ac3ea296257cc4a3f79a6319e61856/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5", size = 2343907, upload-time = "2025-11-04T13:42:27.683Z" }, + { url = "https://files.pythonhosted.org/packages/96/82/e33d5f4933d7a03327c0c43c65d575e5919d4974ffc026bc917a5f7b9f61/pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3", size = 2322174, upload-time = "2025-11-04T13:42:30.776Z" }, + { url = "https://files.pythonhosted.org/packages/81/45/4091be67ce9f469e81656f880f3506f6a5624121ec5eb3eab37d7581897d/pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460", size = 1990353, upload-time = "2025-11-04T13:42:33.111Z" }, + { url = "https://files.pythonhosted.org/packages/44/8a/a98aede18db6e9cd5d66bcacd8a409fcf8134204cdede2e7de35c5a2c5ef/pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b", size = 2015698, upload-time = "2025-11-04T13:42:35.484Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, + { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, + { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, + { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, + { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, + { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pyright" +version = "1.1.399" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/9d/d91d5f6d26b2db95476fefc772e2b9a16d54c6bd0ea6bb5c1b6d635ab8b4/pyright-1.1.399.tar.gz", hash = "sha256:439035d707a36c3d1b443aec980bc37053fbda88158eded24b8eedcf1c7b7a1b", size = 3856954, upload-time = "2025-04-10T04:40:25.703Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/b5/380380c9e7a534cb1783c70c3e8ac6d1193c599650a55838d0557586796e/pyright-1.1.399-py3-none-any.whl", hash = "sha256:55f9a875ddf23c9698f24208c764465ffdfd38be6265f7faf9a176e1dc549f3b", size = 5592584, upload-time = "2025-04-10T04:40:23.502Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "(python_full_version < '3.10' and sys_platform == 'win32') or (python_full_version >= '3.10' and extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2') or (sys_platform != 'win32' and extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "packaging", marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "pluggy", marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "pygments", marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "tomli", marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and extra == 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", +] +dependencies = [ + { name = "colorama", marker = "(python_full_version >= '3.10' and sys_platform == 'win32') or (python_full_version < '3.10' and extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2') or (sys_platform != 'win32' and extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "packaging", marker = "python_full_version >= '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "pluggy", marker = "python_full_version >= '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "pygments", marker = "python_full_version >= '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "tomli", marker = "python_full_version == '3.10.*' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "typing-extensions", marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and extra == 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", +] +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version == '3.10.*' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "typing-extensions", marker = "(python_full_version >= '3.10' and python_full_version < '3.13') or (python_full_version < '3.10' and extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2') or (python_full_version >= '3.13' and extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six", marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "respx" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/7c/96bd0bc759cf009675ad1ee1f96535edcb11e9666b985717eb8c87192a95/respx-0.22.0.tar.gz", hash = "sha256:3c8924caa2a50bd71aefc07aa812f2466ff489f1848c96e954a5362d17095d91", size = 28439, upload-time = "2024-12-19T22:33:59.374Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/67/afbb0978d5399bc9ea200f1d4489a23c9a1dad4eee6376242b8182389c79/respx-0.22.0-py2.py3-none-any.whl", hash = "sha256:631128d4c9aba15e56903fb5f66fb1eff412ce28dd387ca3a81339e52dbd3ad0", size = 25127, upload-time = "2024-12-19T22:33:57.837Z" }, +] + +[[package]] +name = "rich" +version = "14.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "markdown-it-py", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/d9/f7a0c4b3a2bf2556cd5d99b05372c29980249ef71e8e32669ba77428c82c/ruff-0.14.8.tar.gz", hash = "sha256:774ed0dd87d6ce925e3b8496feb3a00ac564bea52b9feb551ecd17e0a23d1eed", size = 5765385, upload-time = "2025-12-04T15:06:17.669Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/b8/9537b52010134b1d2b72870cc3f92d5fb759394094741b09ceccae183fbe/ruff-0.14.8-py3-none-linux_armv6l.whl", hash = "sha256:ec071e9c82eca417f6111fd39f7043acb53cd3fde9b1f95bbed745962e345afb", size = 13441540, upload-time = "2025-12-04T15:06:14.896Z" }, + { url = "https://files.pythonhosted.org/packages/24/00/99031684efb025829713682012b6dd37279b1f695ed1b01725f85fd94b38/ruff-0.14.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8cdb162a7159f4ca36ce980a18c43d8f036966e7f73f866ac8f493b75e0c27e9", size = 13669384, upload-time = "2025-12-04T15:06:51.809Z" }, + { url = "https://files.pythonhosted.org/packages/72/64/3eb5949169fc19c50c04f28ece2c189d3b6edd57e5b533649dae6ca484fe/ruff-0.14.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e2fcbefe91f9fad0916850edf0854530c15bd1926b6b779de47e9ab619ea38f", size = 12806917, upload-time = "2025-12-04T15:06:08.925Z" }, + { url = "https://files.pythonhosted.org/packages/c4/08/5250babb0b1b11910f470370ec0cbc67470231f7cdc033cee57d4976f941/ruff-0.14.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d70721066a296f45786ec31916dc287b44040f553da21564de0ab4d45a869b", size = 13256112, upload-time = "2025-12-04T15:06:23.498Z" }, + { url = "https://files.pythonhosted.org/packages/78/4c/6c588e97a8e8c2d4b522c31a579e1df2b4d003eddfbe23d1f262b1a431ff/ruff-0.14.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c87e09b3cd9d126fc67a9ecd3b5b1d3ded2b9c7fce3f16e315346b9d05cfb52", size = 13227559, upload-time = "2025-12-04T15:06:33.432Z" }, + { url = "https://files.pythonhosted.org/packages/23/ce/5f78cea13eda8eceac71b5f6fa6e9223df9b87bb2c1891c166d1f0dce9f1/ruff-0.14.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d62cb310c4fbcb9ee4ac023fe17f984ae1e12b8a4a02e3d21489f9a2a5f730c", size = 13896379, upload-time = "2025-12-04T15:06:02.687Z" }, + { url = "https://files.pythonhosted.org/packages/cf/79/13de4517c4dadce9218a20035b21212a4c180e009507731f0d3b3f5df85a/ruff-0.14.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1af35c2d62633d4da0521178e8a2641c636d2a7153da0bac1b30cfd4ccd91344", size = 15372786, upload-time = "2025-12-04T15:06:29.828Z" }, + { url = "https://files.pythonhosted.org/packages/00/06/33df72b3bb42be8a1c3815fd4fae83fa2945fc725a25d87ba3e42d1cc108/ruff-0.14.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25add4575ffecc53d60eed3f24b1e934493631b48ebbc6ebaf9d8517924aca4b", size = 14990029, upload-time = "2025-12-04T15:06:36.812Z" }, + { url = "https://files.pythonhosted.org/packages/64/61/0f34927bd90925880394de0e081ce1afab66d7b3525336f5771dcf0cb46c/ruff-0.14.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c943d847b7f02f7db4201a0600ea7d244d8a404fbb639b439e987edcf2baf9a", size = 14407037, upload-time = "2025-12-04T15:06:39.979Z" }, + { url = "https://files.pythonhosted.org/packages/96/bc/058fe0aefc0fbf0d19614cb6d1a3e2c048f7dc77ca64957f33b12cfdc5ef/ruff-0.14.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6e8bf7b4f627548daa1b69283dac5a296bfe9ce856703b03130732e20ddfe2", size = 14102390, upload-time = "2025-12-04T15:06:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/af/a4/e4f77b02b804546f4c17e8b37a524c27012dd6ff05855d2243b49a7d3cb9/ruff-0.14.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7aaf2974f378e6b01d1e257c6948207aec6a9b5ba53fab23d0182efb887a0e4a", size = 14230793, upload-time = "2025-12-04T15:06:20.497Z" }, + { url = "https://files.pythonhosted.org/packages/3f/52/bb8c02373f79552e8d087cedaffad76b8892033d2876c2498a2582f09dcf/ruff-0.14.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e5758ca513c43ad8a4ef13f0f081f80f08008f410790f3611a21a92421ab045b", size = 13160039, upload-time = "2025-12-04T15:06:49.06Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ad/b69d6962e477842e25c0b11622548df746290cc6d76f9e0f4ed7456c2c31/ruff-0.14.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f74f7ba163b6e85a8d81a590363bf71618847e5078d90827749bfda1d88c9cdf", size = 13205158, upload-time = "2025-12-04T15:06:54.574Z" }, + { url = "https://files.pythonhosted.org/packages/06/63/54f23da1315c0b3dfc1bc03fbc34e10378918a20c0b0f086418734e57e74/ruff-0.14.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eed28f6fafcc9591994c42254f5a5c5ca40e69a30721d2ab18bb0bb3baac3ab6", size = 13469550, upload-time = "2025-12-04T15:05:59.209Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/a4d7b1961e4903bc37fffb7ddcfaa7beb250f67d97cfd1ee1d5cddb1ec90/ruff-0.14.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:21d48fa744c9d1cb8d71eb0a740c4dd02751a5de9db9a730a8ef75ca34cf138e", size = 14211332, upload-time = "2025-12-04T15:06:06.027Z" }, + { url = "https://files.pythonhosted.org/packages/5d/93/2a5063341fa17054e5c86582136e9895db773e3c2ffb770dde50a09f35f0/ruff-0.14.8-py3-none-win32.whl", hash = "sha256:15f04cb45c051159baebb0f0037f404f1dc2f15a927418f29730f411a79bc4e7", size = 13151890, upload-time = "2025-12-04T15:06:11.668Z" }, + { url = "https://files.pythonhosted.org/packages/02/1c/65c61a0859c0add13a3e1cbb6024b42de587456a43006ca2d4fd3d1618fe/ruff-0.14.8-py3-none-win_amd64.whl", hash = "sha256:9eeb0b24242b5bbff3011409a739929f497f3fb5fe3b5698aba5e77e8c833097", size = 14537826, upload-time = "2025-12-04T15:06:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/6d/63/8b41cea3afd7f58eb64ac9251668ee0073789a3bc9ac6f816c8c6fef986d/ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99", size = 13634522, upload-time = "2025-12-04T15:06:43.212Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +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 = "time-machine" +version = "2.19.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "python-dateutil", marker = "python_full_version < '3.10' or (extra == 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/1b5fdd165f61b67f445fac2a7feb0c655118edef429cd09ff5a8067f7f1d/time_machine-2.19.0.tar.gz", hash = "sha256:7c5065a8b3f2bbb449422c66ef71d114d3f909c276a6469642ecfffb6a0fcd29", size = 14576, upload-time = "2025-08-19T17:22:08.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/8f/19125611ebbcb3a14da14cd982b9eb4573e2733db60c9f1fbf6a39534f40/time_machine-2.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5169018ef47206997b46086ce01881cd3a4666fd2998c9d76a87858ca3e49e9", size = 19659, upload-time = "2025-08-19T17:20:30.062Z" }, + { url = "https://files.pythonhosted.org/packages/74/da/9b0a928321e7822a3ff96dbd1eae089883848e30e9e1b149b85fb96ba56b/time_machine-2.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85bb7ed440fccf6f6d0c8f7d68d849e7c3d1f771d5e0b2cdf871fa6561da569f", size = 15157, upload-time = "2025-08-19T17:20:31.931Z" }, + { url = "https://files.pythonhosted.org/packages/36/ff/d7e943422038f5f2161fe2c2d791e64a45be691ef946020b20f3a6efc4d4/time_machine-2.19.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a3b12028af1cdc09ccd595be2168b7b26f206c1e190090b048598fbe278beb8e", size = 32860, upload-time = "2025-08-19T17:20:33.241Z" }, + { url = "https://files.pythonhosted.org/packages/fc/80/2b0f1070ed9808ee7da7a6da62a4a0b776957cb4d861578348f86446e778/time_machine-2.19.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c261f073086cf081d1443cbf7684148c662659d3d139d06b772bfe3fe7cc71a6", size = 34510, upload-time = "2025-08-19T17:20:34.221Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b4/48038691c8d89924b36c83335a73adeeb68c884f5a1da08a5b17b8a956f3/time_machine-2.19.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:011954d951230a9f1079f22b39ed1a3a9abb50ee297dfb8c557c46351659d94d", size = 36204, upload-time = "2025-08-19T17:20:35.163Z" }, + { url = "https://files.pythonhosted.org/packages/37/2e/60e8adb541df195e83cb74b720b2cfb1f22ed99c5a7f8abf2a9ab3442cb5/time_machine-2.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b0f83308b29c7872006803f2e77318874eb84d0654f2afe0e48e3822e7a2e39b", size = 34936, upload-time = "2025-08-19T17:20:36.61Z" }, + { url = "https://files.pythonhosted.org/packages/5e/72/e8cee59c6cd99dd3b25b8001a0253e779a286aa8f44d5b40777cbd66210b/time_machine-2.19.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:39733ef844e2984620ec9382a42d00cccc4757d75a5dd572be8c2572e86e50b9", size = 32932, upload-time = "2025-08-19T17:20:37.901Z" }, + { url = "https://files.pythonhosted.org/packages/2c/eb/83f300d93c1504965d944e03679f1c943a923bce2d0fdfadef0e2e22cc13/time_machine-2.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f8db99f6334432e9ffbf00c215caf2ae9773f17cec08304d77e9e90febc3507b", size = 34010, upload-time = "2025-08-19T17:20:39.202Z" }, + { url = "https://files.pythonhosted.org/packages/e1/77/f35f2500e04daac5033a22fbfd17e68467822b8406ee77966bf222ccaa26/time_machine-2.19.0-cp310-cp310-win32.whl", hash = "sha256:72bf66cd19e27ffd26516b9cbe676d50c2e0b026153289765dfe0cf406708128", size = 17121, upload-time = "2025-08-19T17:20:40.108Z" }, + { url = "https://files.pythonhosted.org/packages/db/df/32d3e0404be1760a64a44caab2af34b07e952bfe00a23134fea9ddba3e8a/time_machine-2.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:46f1c945934ce3d6b4f388b8e581fce7f87ec891ea90d7128e19520e434f96f0", size = 17957, upload-time = "2025-08-19T17:20:41.079Z" }, + { url = "https://files.pythonhosted.org/packages/66/df/598a71a1afb4b509a4587273b76590b16d9110a3e9106f01eedc68d02bb2/time_machine-2.19.0-cp310-cp310-win_arm64.whl", hash = "sha256:fb4897c7a5120a4fd03f0670f332d83b7e55645886cd8864a71944c4c2e5b35b", size = 16821, upload-time = "2025-08-19T17:20:41.967Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ed/4815ebcc9b6c14273f692b9be38a9b09eae52a7e532407cc61a51912b121/time_machine-2.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5ee91664880434d98e41585c3446dac7180ec408c786347451ddfca110d19296", size = 19342, upload-time = "2025-08-19T17:20:43.207Z" }, + { url = "https://files.pythonhosted.org/packages/ee/08/154cce8b11b60d8238b0b751b8901d369999f4e8f7c3a5f917caa5d95b0b/time_machine-2.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed3732b83a893d1c7b8cabde762968b4dc5680ee0d305b3ecca9bb516f4e3862", size = 14978, upload-time = "2025-08-19T17:20:44.134Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b7/b689d8c8eeca7af375cfcd64973e49e83aa817cc00f80f98548d42c0eb50/time_machine-2.19.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6ba0303e9cc9f7f947e344f501e26bedfb68fab521e3c2729d370f4f332d2d55", size = 30964, upload-time = "2025-08-19T17:20:45.366Z" }, + { url = "https://files.pythonhosted.org/packages/80/91/38bf9c79674e95ce32e23c267055f281dff651eec77ed32a677db3dc011a/time_machine-2.19.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2851825b524a988ee459c37c1c26bdfaa7eff78194efb2b562ea497a6f375b0a", size = 32606, upload-time = "2025-08-19T17:20:46.693Z" }, + { url = "https://files.pythonhosted.org/packages/19/4a/e9222d85d4de68975a5e799f539a9d32f3a134a9101fca0a61fa6aa33d68/time_machine-2.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68d32b09ecfd7fef59255c091e8e7c24dd117f882c4880b5c7ab8c5c32a98f89", size = 34405, upload-time = "2025-08-19T17:20:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/14/e2/09480d608d42d6876f9ff74593cfc9197a7eb2c31381a74fb2b145575b65/time_machine-2.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60c46ab527bf2fa144b530f639cc9e12803524c9e1f111dc8c8f493bb6586eeb", size = 33181, upload-time = "2025-08-19T17:20:48.937Z" }, + { url = "https://files.pythonhosted.org/packages/84/64/f9359e000fad32d9066305c48abc527241d608bcdf77c19d67d66e268455/time_machine-2.19.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:56f26ab9f0201c453d18fe76bb7d1cf05fe58c1b9d9cb0c7d243d05132e01292", size = 31036, upload-time = "2025-08-19T17:20:50.276Z" }, + { url = "https://files.pythonhosted.org/packages/71/0d/fab2aacec71e3e482bd7fce0589381f9414a4a97f8766bddad04ad047b7b/time_machine-2.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6c806cf3c1185baa1d807b7f51bed0db7a6506832c961d5d1b4c94c775749bc0", size = 32145, upload-time = "2025-08-19T17:20:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/44/fb/faeba2405fb27553f7b28db441a500e2064ffdb2dcba001ee315fdd2c121/time_machine-2.19.0-cp311-cp311-win32.whl", hash = "sha256:b30039dfd89855c12138095bee39c540b4633cbc3684580d684ef67a99a91587", size = 17004, upload-time = "2025-08-19T17:20:52.38Z" }, + { url = "https://files.pythonhosted.org/packages/2f/84/87e483d660ca669426192969280366635c845c3154a9fe750be546ed3afc/time_machine-2.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:13ed8b34430f1de79905877f5600adffa626793ab4546a70a99fb72c6a3350d8", size = 17822, upload-time = "2025-08-19T17:20:53.348Z" }, + { url = "https://files.pythonhosted.org/packages/41/f4/ebf7bbf5047854a528adaf54a5e8780bc5f7f0104c298ab44566a3053bf8/time_machine-2.19.0-cp311-cp311-win_arm64.whl", hash = "sha256:cc29a50a0257d8750b08056b66d7225daab47606832dea1a69e8b017323bf511", size = 16680, upload-time = "2025-08-19T17:20:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/9b/aa/7e00614d339e4d687f6e96e312a1566022528427d237ec639df66c4547bc/time_machine-2.19.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c85cf437dc3c07429456d8d6670ac90ecbd8241dcd0fbf03e8db2800576f91ff", size = 19308, upload-time = "2025-08-19T17:20:55.25Z" }, + { url = "https://files.pythonhosted.org/packages/ab/3c/bde3c757394f5bca2fbc1528d4117960a26c38f9b160bf471b38d2378d8f/time_machine-2.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d9238897e8ef54acdf59f5dff16f59ca0720e7c02d820c56b4397c11db5d3eb9", size = 15019, upload-time = "2025-08-19T17:20:56.204Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e0/8ca916dd918018352d377f1f5226ee071cfbeb7dbbde2b03d14a411ac2b1/time_machine-2.19.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e312c7d5d6bfffb96c6a7b39ff29e3046de100d7efaa3c01552654cfbd08f14c", size = 33079, upload-time = "2025-08-19T17:20:57.166Z" }, + { url = "https://files.pythonhosted.org/packages/48/69/184a0209f02dd0cb5e01e8d13cd4c97a5f389c4e3d09b95160dd676ad1e7/time_machine-2.19.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:714c40b2c90d1c57cc403382d5a9cf16e504cb525bfe9650095317da3c3d62b5", size = 34925, upload-time = "2025-08-19T17:20:58.117Z" }, + { url = "https://files.pythonhosted.org/packages/43/42/4bbf4309e8e57cea1086eb99052d97ff6ddecc1ab6a3b07aa4512f8bf963/time_machine-2.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2eaa1c675d500dc3ccae19e9fb1feff84458a68c132bbea47a80cc3dd2df7072", size = 36384, upload-time = "2025-08-19T17:20:59.108Z" }, + { url = "https://files.pythonhosted.org/packages/b1/af/9f510dc1719157348c1a2e87423aed406589070b54b503cb237d9bf3a4fe/time_machine-2.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e77a414e9597988af53b2b2e67242c9d2f409769df0d264b6d06fda8ca3360d4", size = 34881, upload-time = "2025-08-19T17:21:00.116Z" }, + { url = "https://files.pythonhosted.org/packages/ca/28/61764a635c70cc76c76ba582dfdc1a84834cddaeb96789023af5214426b2/time_machine-2.19.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cd93996970e11c382b04d4937c3cd0b0167adeef14725ece35aae88d8a01733c", size = 32931, upload-time = "2025-08-19T17:21:01.095Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e0/f028d93b266e6ade8aca5851f76ebbc605b2905cdc29981a2943b43e1a6c/time_machine-2.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8e20a6d8d6e23174bd7e931e134d9610b136db460b249d07e84ecdad029ec352", size = 34241, upload-time = "2025-08-19T17:21:02.052Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a6/36d1950ed1d3f613158024cf1dcc73db1d9ef0b9117cf51ef2e37dc06499/time_machine-2.19.0-cp312-cp312-win32.whl", hash = "sha256:95afc9bc65228b27be80c2756799c20b8eb97c4ef382a9b762b6d7888bc84099", size = 17021, upload-time = "2025-08-19T17:21:03.374Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0d/e2dce93355abda3cac69e77fe96566757e98b8fe7fdcbddce89c9ced3f5f/time_machine-2.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:e84909af950e2448f4e2562ea5759c946248c99ab380d2b47d79b62bd76fa236", size = 17857, upload-time = "2025-08-19T17:21:04.331Z" }, + { url = "https://files.pythonhosted.org/packages/eb/28/50ae6fb83b7feeeca7a461c0dc156cf7ef5e6ef594a600d06634fde6a2cb/time_machine-2.19.0-cp312-cp312-win_arm64.whl", hash = "sha256:0390a1ea9fa7e9d772a39b7c61b34fdcca80eb9ffac339cc0441c6c714c81470", size = 16677, upload-time = "2025-08-19T17:21:05.39Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b8/24ebce67aa531bae2cbe164bb3f4abc6467dc31f3aead35e77f5a075ea3e/time_machine-2.19.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5e172866753e6041d3b29f3037dc47c20525176a494a71bbd0998dfdc4f11f2f", size = 19373, upload-time = "2025-08-19T17:21:06.701Z" }, + { url = "https://files.pythonhosted.org/packages/53/a5/c9a5240fd2f845d3ff9fa26f8c8eaa29f7239af9d65007e61d212250f15b/time_machine-2.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f70f68379bd6f542ae6775cce9a4fa3dcc20bf7959c42eaef871c14469e18097", size = 15056, upload-time = "2025-08-19T17:21:07.667Z" }, + { url = "https://files.pythonhosted.org/packages/b9/92/66cce5d2fb2a5e68459aca85fd18a7e2d216f725988940cd83f96630f2f1/time_machine-2.19.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e69e0b0f694728a00e72891ef8dd00c7542952cb1c87237db594b6b27d504a96", size = 33172, upload-time = "2025-08-19T17:21:08.619Z" }, + { url = "https://files.pythonhosted.org/packages/ae/20/b499e9ab4364cd466016c33dcdf4f56629ca4c20b865bd4196d229f31d92/time_machine-2.19.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3ae0a8b869574301ec5637e32c270c7384cca5cd6e230f07af9d29271a7fa293", size = 35042, upload-time = "2025-08-19T17:21:09.622Z" }, + { url = "https://files.pythonhosted.org/packages/41/32/b252d3d32791eb16c07d553c820dbc33d9c7fa771de3d1c602190bded2b7/time_machine-2.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:554e4317de90e2f7605ff80d153c8bb56b38c0d0c0279feb17e799521e987b8c", size = 36535, upload-time = "2025-08-19T17:21:10.571Z" }, + { url = "https://files.pythonhosted.org/packages/98/cf/4d0470062b9742e1b040ab81bad04d1a5d1de09806507bb6188989cfa1a7/time_machine-2.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6567a5ec5538ed550539ac29be11b3cb36af1f9894e2a72940cba0292cc7c3c9", size = 34945, upload-time = "2025-08-19T17:21:11.538Z" }, + { url = "https://files.pythonhosted.org/packages/24/71/2f741b29d98b1c18f6777a32236497c3d3264b6077e431cea4695684c8a1/time_machine-2.19.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82e9ffe8dfff07b0d810a2ad015a82cd78c6a237f6c7cf185fa7f747a3256f8a", size = 33014, upload-time = "2025-08-19T17:21:12.858Z" }, + { url = "https://files.pythonhosted.org/packages/e8/83/ca8dba6106562843fd99f672e5aaf95badbc10f4f13f7cfe8d8640a7019d/time_machine-2.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7e1c4e578cdd69b3531d8dd3fbcb92a0cd879dadb912ee37af99c3a9e3c0d285", size = 34350, upload-time = "2025-08-19T17:21:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/21/7f/34fe540450e18d0a993240100e4b86e8d03d831b92af8bb6ddb2662dc6fc/time_machine-2.19.0-cp313-cp313-win32.whl", hash = "sha256:72dbd4cbc3d96dec9dd281ddfbb513982102776b63e4e039f83afb244802a9e5", size = 17047, upload-time = "2025-08-19T17:21:14.874Z" }, + { url = "https://files.pythonhosted.org/packages/bf/5d/c8be73df82c7ebe7cd133279670e89b8b110af3ce1412c551caa9d08e625/time_machine-2.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:e17e3e089ac95f9a145ce07ff615e3c85674f7de36f2d92aaf588493a23ffb4b", size = 17868, upload-time = "2025-08-19T17:21:15.819Z" }, + { url = "https://files.pythonhosted.org/packages/92/13/2dfd3b8fb285308f61cd7aa9bfa96f46ddf916e3549a0f0afd094c556599/time_machine-2.19.0-cp313-cp313-win_arm64.whl", hash = "sha256:149072aff8e3690e14f4916103d898ea0d5d9c95531b6aa0995251c299533f7b", size = 16710, upload-time = "2025-08-19T17:21:16.748Z" }, + { url = "https://files.pythonhosted.org/packages/05/c1/deebb361727d2c5790f9d4d874be1b19afd41f4375581df465e6718b46a2/time_machine-2.19.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f3589fee1ed0ab6ee424a55b0ea1ec694c4ba64cc26895bcd7d99f3d1bc6a28a", size = 20053, upload-time = "2025-08-19T17:21:17.704Z" }, + { url = "https://files.pythonhosted.org/packages/45/e8/fe3376951e6118d8ec1d1f94066a169b791424fe4a26c7dfc069b153ee08/time_machine-2.19.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7887e85275c4975fe54df03dcdd5f38bd36be973adc68a8c77e17441c3b443d6", size = 15423, upload-time = "2025-08-19T17:21:18.668Z" }, + { url = "https://files.pythonhosted.org/packages/9c/c7/f88d95cd1a87c650cf3749b4d64afdaf580297aa18ad7f4b44ec9d252dfc/time_machine-2.19.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ce0be294c209928563fcce1c587963e60ec803436cf1e181acd5bc1e425d554b", size = 39630, upload-time = "2025-08-19T17:21:19.645Z" }, + { url = "https://files.pythonhosted.org/packages/cc/5d/65a5c48a65357e56ec6f032972e4abd1c02d4fca4b0717a3aaefd19014d4/time_machine-2.19.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a62fd1ab380012c86f4c042010418ed45eb31604f4bf4453e17c9fa60bc56a29", size = 41242, upload-time = "2025-08-19T17:21:20.979Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f9/fe5209e1615fde0a8cad6c4e857157b150333ed1fe31a7632b08cfe0ebdd/time_machine-2.19.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b25ec853a4530a5800731257f93206b12cbdee85ede964ebf8011b66086a7914", size = 44278, upload-time = "2025-08-19T17:21:21.984Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3a/a5e5fe9c5d614cde0a9387ff35e8dfd12c5ef6384e4c1a21b04e6e0b905d/time_machine-2.19.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a430e4d0e0556f021a9c78e9b9f68e5e8910bdace4aa34ed4d1a73e239ed9384", size = 42321, upload-time = "2025-08-19T17:21:23.755Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c5/56eca774e9162bc1ce59111d2bd69140dc8908c9478c92ec7bd15d547600/time_machine-2.19.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2415b7495ec4364c8067071e964fbadfe746dd4cdb43983f2f0bd6ebed13315c", size = 39270, upload-time = "2025-08-19T17:21:26.009Z" }, + { url = "https://files.pythonhosted.org/packages/9b/69/5dd0c420667578169a12acc8c8fd7452e8cfb181e41c9b4ac7e88fa36686/time_machine-2.19.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbfc6b90c10f288594e1bf89a728a98cc0030791fd73541bbdc6b090aff83143", size = 40193, upload-time = "2025-08-19T17:21:27.054Z" }, + { url = "https://files.pythonhosted.org/packages/75/a7/de974d421bd55c9355583427c2a38fb0237bb5fd6614af492ba89dacb2f9/time_machine-2.19.0-cp313-cp313t-win32.whl", hash = "sha256:16f5d81f650c0a4d117ab08036dc30b5f8b262e11a4a0becc458e7f1c011b228", size = 17542, upload-time = "2025-08-19T17:21:28.674Z" }, + { url = "https://files.pythonhosted.org/packages/76/0a/aa0d05becd5d06ae8d3f16d657dc8cc9400c8d79aef80299de196467ff12/time_machine-2.19.0-cp313-cp313t-win_amd64.whl", hash = "sha256:645699616ec14e147094f601e6ab9553ff6cea37fad9c42720a6d7ed04bcd5dc", size = 18703, upload-time = "2025-08-19T17:21:29.663Z" }, + { url = "https://files.pythonhosted.org/packages/1f/c0/f785a4c7c73aa176510f7c48b84b49c26be84af0d534deb222e0327f750e/time_machine-2.19.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b32daa965d13237536ea3afaa5ad61ade2b2d9314bc3a20196a0d2e1d7b57c6a", size = 17020, upload-time = "2025-08-19T17:21:30.653Z" }, + { url = "https://files.pythonhosted.org/packages/ed/97/c5fb51def06c0b2b6735332ad118ab35b4d9b85368792e5b638e99b1b686/time_machine-2.19.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:31cb43c8fd2d961f31bed0ff4e0026964d2b35e5de9e0fabbfecf756906d3612", size = 19360, upload-time = "2025-08-19T17:21:31.94Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4e/2d795f7d6b7f5205ffe737a05bb1cf19d8038233b797062b2ef412b8512b/time_machine-2.19.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:bdf481a75afc6bff3e520db594501975b652f7def21cd1de6aa971d35ba644e6", size = 15033, upload-time = "2025-08-19T17:21:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/dd/32/9bad501e360b4e758c58fae616ca5f8c7ad974b343f2463a15b2bf77a366/time_machine-2.19.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:00bee4bb950ac6a08d62af78e4da0cf2b4fc2abf0de2320d0431bf610db06e7c", size = 33379, upload-time = "2025-08-19T17:21:33.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/45/eda0ca4d793dfd162478d6163759b1c6ce7f6e61daa7fd7d62b31f21f87f/time_machine-2.19.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9f02199490906582302ce09edd32394fb393271674c75d7aa76c7a3245f16003", size = 35123, upload-time = "2025-08-19T17:21:34.945Z" }, + { url = "https://files.pythonhosted.org/packages/f0/5a/97e16325442ae5731fcaac794f0a1ef9980eff8a5491e58201d7eb814a34/time_machine-2.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e35726c7ba625f844c13b1fc0d4f81f394eefaee1d3a094a9093251521f2ef15", size = 36588, upload-time = "2025-08-19T17:21:35.975Z" }, + { url = "https://files.pythonhosted.org/packages/e8/9d/bf0b2ccc930cc4a316f26f1c78d3f313cd0fa13bb7480369b730a8f129db/time_machine-2.19.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:304315023999cd401ff02698870932b893369e1cfeb2248d09f6490507a92e97", size = 35013, upload-time = "2025-08-19T17:21:37.017Z" }, + { url = "https://files.pythonhosted.org/packages/f0/5a/39ac6a3078174f9715d88364871348b249631f12e76de1b862433b3f8862/time_machine-2.19.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9765d4f003f263ea8bfd90d2d15447ca4b3dfa181922cf6cf808923b02ac180a", size = 33303, upload-time = "2025-08-19T17:21:38.352Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ac/d8646baf9f95f2e792a6d7a7b35e92fca253c4a992afff801beafae0e5c2/time_machine-2.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7837ef3fd5911eb9b480909bb93d922737b6bdecea99dfcedb0a03807de9b2d3", size = 34440, upload-time = "2025-08-19T17:21:39.382Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8b/8b6568c5ae966d80ead03ab537be3c6acf2af06fb501c2d466a3162c6295/time_machine-2.19.0-cp314-cp314-win32.whl", hash = "sha256:4bb5bd43b1bdfac3007b920b51d8e761f024ed465cfeec63ac4296922a4ec428", size = 17162, upload-time = "2025-08-19T17:21:40.381Z" }, + { url = "https://files.pythonhosted.org/packages/46/a5/211c1ab4566eba5308b2dc001b6349e3a032e3f6afa67ca2f27ea6b27af5/time_machine-2.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:f583bbd0aa8ab4a7c45a684bf636d9e042d466e30bcbae1d13e7541e2cbe7207", size = 18040, upload-time = "2025-08-19T17:21:41.363Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fc/4c2fb705f6371cb83824da45a8b967514a922fc092a0ef53979334d97a70/time_machine-2.19.0-cp314-cp314-win_arm64.whl", hash = "sha256:f379c6f8a6575a8284592179cf528ce89373f060301323edcc44f1fa1d37be12", size = 16752, upload-time = "2025-08-19T17:21:42.336Z" }, + { url = "https://files.pythonhosted.org/packages/79/ab/6437d18f31c666b5116c97572a282ac2590a82a0a9867746a6647eaf4613/time_machine-2.19.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a3b8981f9c663b0906b05ab4d0ca211fae4b63b47c6ec26de5374fe56c836162", size = 20057, upload-time = "2025-08-19T17:21:43.35Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a2/e03639ec2ba7200328bbcad8a2b2b1d5fccca9cceb9481b164a1cabdcb33/time_machine-2.19.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8e9c6363893e7f52c226afbebb23e825259222d100e67dfd24c8a6d35f1a1907", size = 15430, upload-time = "2025-08-19T17:21:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ff/39e63a48e840f3e36ce24846ee51dd99c6dba635659b1750a2993771e88e/time_machine-2.19.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:206fcd6c9a6f00cac83db446ad1effc530a8cec244d2780af62db3a2d0a9871b", size = 39622, upload-time = "2025-08-19T17:21:45.821Z" }, + { url = "https://files.pythonhosted.org/packages/9a/2e/ee5ac79c4954768705801e54817c7d58e07e25a0bb227e775f501f3e2122/time_machine-2.19.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf33016a1403c123373ffaeff25e26e69d63bf2c63b6163932efed94160db7ef", size = 41235, upload-time = "2025-08-19T17:21:46.783Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3e/9af5f39525e779185c77285b8bbae15340eeeaa0afb33d458bc8b47d459b/time_machine-2.19.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9247c4bb9bbd3ff584ef4efbdec8efd9f37aa08bcfc4728bde1e489c2cb445bd", size = 44276, upload-time = "2025-08-19T17:21:47.759Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/572c7443cc27140bbeae3947279bbd4a120f9e8622253a20637f260b7813/time_machine-2.19.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:77f9bb0b86758d1f2d9352642c874946ad5815df53ef4ca22eb9d532179fe50d", size = 42330, upload-time = "2025-08-19T17:21:48.881Z" }, + { url = "https://files.pythonhosted.org/packages/cf/24/1a81c2e08ee7dae13ec8ceed27a29afa980c3d63852e42f1e023bf0faa03/time_machine-2.19.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0b529e262df3b9c449f427385f4d98250828c879168c2e00eec844439f40b370", size = 39281, upload-time = "2025-08-19T17:21:49.907Z" }, + { url = "https://files.pythonhosted.org/packages/d2/60/6f0d6e5108978ca1a2a4ffb4d1c7e176d9199bb109fd44efe2680c60b52a/time_machine-2.19.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9199246e31cdc810e5d89cb71d09144c4d745960fdb0824da4994d152aca3303", size = 40201, upload-time = "2025-08-19T17:21:50.953Z" }, + { url = "https://files.pythonhosted.org/packages/73/b9/3ea4951e8293b0643feb98c0b9a176fa822154f1810835db3f282968ab10/time_machine-2.19.0-cp314-cp314t-win32.whl", hash = "sha256:0fe81bae55b7aefc2c2a34eb552aa82e6c61a86b3353a3c70df79b9698cb02ca", size = 17743, upload-time = "2025-08-19T17:21:51.948Z" }, + { url = "https://files.pythonhosted.org/packages/e4/8b/cd802884ca8a98e2b6cdc2397d57dd12ff8a7d1481e06fc3fad3d4e7e5ff/time_machine-2.19.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7253791b8d7e7399fbeed7a8193cb01bc004242864306288797056badbdaf80b", size = 18956, upload-time = "2025-08-19T17:21:52.997Z" }, + { url = "https://files.pythonhosted.org/packages/c6/49/cabb1593896082fd55e34768029b8b0ca23c9be8b2dc127e0fc14796d33e/time_machine-2.19.0-cp314-cp314t-win_arm64.whl", hash = "sha256:536bd1ac31ab06a1522e7bf287602188f502dc19d122b1502c4f60b1e8efac79", size = 17068, upload-time = "2025-08-19T17:21:54.064Z" }, + { url = "https://files.pythonhosted.org/packages/d6/05/0608376c3167afe6cf7cdfd2b05c142ea4c42616eee9ba06d1799965806a/time_machine-2.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8bb00b30ec9fe56d01e9812df1ffe39f331437cef9bfaebcc81c83f7f8f8ee2", size = 19659, upload-time = "2025-08-19T17:21:55.426Z" }, + { url = "https://files.pythonhosted.org/packages/11/c4/72eb8c7b36830cf36c51d7bc2f1ac313d68881c3a58040fb6b42c4523d20/time_machine-2.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d821c60efc08a97cc11e5482798e6fd5eba5c0f22a02db246b50895dbdc0de41", size = 15153, upload-time = "2025-08-19T17:21:56.505Z" }, + { url = "https://files.pythonhosted.org/packages/89/1a/0782e1f5c8ab8809ebd992709e1bb69d67600191baa023af7a5d32023a3c/time_machine-2.19.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb051aec7b3b6e96a200d911c225901e6133ff3da11e470e24111a53bbc13637", size = 32555, upload-time = "2025-08-19T17:21:57.74Z" }, + { url = "https://files.pythonhosted.org/packages/94/b0/8ef58e2f6321851d5900ca3d18044938832c2ed42a2ac7570ca6aa29768a/time_machine-2.19.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe59909d95a2ef5e01ce3354fdea3908404c2932c2069f00f66dff6f27e9363e", size = 34185, upload-time = "2025-08-19T17:21:59.361Z" }, + { url = "https://files.pythonhosted.org/packages/82/74/ce0c9867f788c1fb22c417ec1aae47a24117e53d51f6ff97d7c6ca5392f6/time_machine-2.19.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29e84b8682645b16eb6f9e8ec11c35324ad091841a11cf4fc3fc7f6119094c89", size = 35917, upload-time = "2025-08-19T17:22:00.421Z" }, + { url = "https://files.pythonhosted.org/packages/d2/70/6f97a8f552dbaa66feb10170b5726dab74bc531673d1ed9d6f271547e54c/time_machine-2.19.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a11f1c0e0d06023dc01614c964e256138913551d3ae6dca5148f79081156336", size = 34584, upload-time = "2025-08-19T17:22:01.447Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/cf139088ce537c15d7f03cf56ec317d3a5cfb520e30aa711ea0248d0ae8a/time_machine-2.19.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:57a235a6307c54df50e69f1906e2f199e47da91bde4b886ee05aff57fe4b6bf6", size = 32608, upload-time = "2025-08-19T17:22:02.548Z" }, + { url = "https://files.pythonhosted.org/packages/b1/17/0ec41ef7a30c6753fb226a28b74162b264b35724905ced4098f2f5076ded/time_machine-2.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:426aba552f7af9604adad9ef570c859af7c1081d878db78089fac159cd911b0a", size = 33686, upload-time = "2025-08-19T17:22:03.606Z" }, + { url = "https://files.pythonhosted.org/packages/b0/19/586f15159083ec84f178d494c60758c46603b00c9641b04deb63f1950128/time_machine-2.19.0-cp39-cp39-win32.whl", hash = "sha256:67772c7197a3a712d1b970ed545c6e98db73524bd90e245fd3c8fa7ad7630768", size = 17133, upload-time = "2025-08-19T17:22:04.989Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c2/bfe4b906a9fe0bf2d011534314212ed752d6b8f392c9c82f6ac63dccc5ab/time_machine-2.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:011d7859089263204dc5fdf83dce7388f986fe833c9381d6106b4edfda2ebd3e", size = 17972, upload-time = "2025-08-19T17:22:06.026Z" }, + { url = "https://files.pythonhosted.org/packages/5d/73/182343eba05aa5787732aaa68f3b3feb5e40ddf86b928ae941be45646393/time_machine-2.19.0-cp39-cp39-win_arm64.whl", hash = "sha256:e1af66550fa4685434f00002808a525f176f1f92746646c0019bb86fbff48b27", size = 16820, upload-time = "2025-08-19T17:22:07.227Z" }, +] + +[[package]] +name = "time-machine" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and python_full_version < '3.14' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra == 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and extra == 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", + "python_full_version >= '3.10' and extra != 'group-12-dedalus-labs-pydantic-v1' and extra != 'group-12-dedalus-labs-pydantic-v2'", +] +sdist = { url = "https://files.pythonhosted.org/packages/17/bd/a1bb03eb39ce35c966f0dde6559df7348ca0580f7cd3a956fdd7ed0b5080/time_machine-3.1.0.tar.gz", hash = "sha256:90831c2cf9e18e4199abb85fafa0c0ca0c6c15d0894a03ef68d5005a796c4f27", size = 14436, upload-time = "2025-11-21T13:56:33.802Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/c9/0aaa082d6b5c489c22d9db025cbf17016062e953f70a7678d76b520f274f/time_machine-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e867178cc87490c578534832c29f048cc954b32a01681237e52ccda704baece5", size = 19046, upload-time = "2025-11-21T13:54:53.273Z" }, + { url = "https://files.pythonhosted.org/packages/30/0d/e825251028c68822a63478c7e44a0dca640daedb15f685a9a3973edf8ae8/time_machine-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d4d4b944e0197c7726844f452fcb3dc17991165e9b4fab779e505b095bb7363", size = 15027, upload-time = "2025-11-21T13:54:54.474Z" }, + { url = "https://files.pythonhosted.org/packages/01/ee/d1e041f85787b835ed70bc7c31000a03d197f831490304f2527e9b840507/time_machine-3.1.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9fc81014e6fc989b833e8cae3684b78b1e1f7c0d867c2fbfea785385a806ea6b", size = 32548, upload-time = "2025-11-21T13:54:55.868Z" }, + { url = "https://files.pythonhosted.org/packages/50/c0/12fc339c054e0db1da79c476bb1c3cc0b8796789e90eb8687af96bbdde9b/time_machine-3.1.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aebd2a55f860b1ef28a2c1efe81454a6fa110ec1a6517c011760132f3cbf35fa", size = 34150, upload-time = "2025-11-21T13:54:56.955Z" }, + { url = "https://files.pythonhosted.org/packages/84/ab/40a2d31d4f742e41fc7f3703da672bbc25b505df3e7ab5df6c11a39e435e/time_machine-3.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02e8018629e12e8064fee4f8fbbb8ae353f5907051965b5721ef189aeb7f833c", size = 35694, upload-time = "2025-11-21T13:54:58.053Z" }, + { url = "https://files.pythonhosted.org/packages/6f/1f/f31b604cb72af2c89311e8152bfe4e64a890785daeb19939bb841ed4cb77/time_machine-3.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2fbbcf9de9a1d3e94e8b6c41ac7e9da46948fbdf489dbc083ea6c28ed045a43a", size = 34500, upload-time = "2025-11-21T13:54:59.428Z" }, + { url = "https://files.pythonhosted.org/packages/4f/1d/dca59c5d54dd0777b342fa708ffb24e0c595b8c47106300bc154dbaa8d98/time_machine-3.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:358f0feead47ee74c7747cb5b7f73582cbde1c033c20f9350e58ab4389aa59ff", size = 32605, upload-time = "2025-11-21T13:55:00.877Z" }, + { url = "https://files.pythonhosted.org/packages/f0/af/033b7b29c9364f05e99b4f35152574edc21b2f038589dafd60a60945a017/time_machine-3.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f6d3f9e531127306610433fe62368b9e14621e336da2042d062565d655d97a02", size = 33699, upload-time = "2025-11-21T13:55:02.287Z" }, + { url = "https://files.pythonhosted.org/packages/4e/92/782c75e9f958e2cb33ba30f9a7adeb819811626eb3193e5ade4343ef197d/time_machine-3.1.0-cp310-cp310-win32.whl", hash = "sha256:bd4b4279938472ea18e5580330c10f8d49b8aec34e0df71be46e3be3b0f03f1d", size = 17054, upload-time = "2025-11-21T13:55:04.036Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e6/cbdb32a72d2e122646cd3c62aed47dcddb366196798caa39043985d4e11d/time_machine-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:10a0d18b65af008e1cb60e0cc57594e67d3bbfee07204879f0e5864223dfd899", size = 17898, upload-time = "2025-11-21T13:55:05.069Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d2/3663703fc694b07241673391c267629f881d4c025c392d7df09161031ac7/time_machine-3.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:53702d6e594fc395b66517bc75c3d2ad8bfbff1f693d09bb67f8178cdfd21cd5", size = 16630, upload-time = "2025-11-21T13:55:06.178Z" }, + { url = "https://files.pythonhosted.org/packages/67/2b/9f5cea745e6c704cbbd1b6c36e0c73ca3204160e9c79234d66f140b326f6/time_machine-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3b205f91d3907f6bd1747fe5484a3ed931e121e51fec32e4d6a8ee6eb41c37c3", size = 18741, upload-time = "2025-11-21T13:55:07.288Z" }, + { url = "https://files.pythonhosted.org/packages/76/17/7b5d94a119883b56b446980387e8ab83f37037db01696cf236cbc85807dc/time_machine-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7004f2fd396b6d40cbf7c21df82f838445d899a3ed2ecc5b1fb67eea7e3d2fa8", size = 14865, upload-time = "2025-11-21T13:55:08.304Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/a6ff6587d520ac20bf7414faf8f53cf094dd9fe450acf3b0c85e0b332c8a/time_machine-3.1.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:68c35b3450c27cf0087c90ae872ab41ec8097b76b5fb6b07321fc5873e78f152", size = 30643, upload-time = "2025-11-21T13:55:09.8Z" }, + { url = "https://files.pythonhosted.org/packages/45/50/dcf4272d7f9a4690d9edd983b5690efa8df3cc7671ade9bcf3439adac278/time_machine-3.1.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2e28aed9da0182c10cb0344aa06fac585f394768c7d088bee781ad2779ea6fe0", size = 32226, upload-time = "2025-11-21T13:55:10.927Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7c/b719ae736568b3f2c9bf8d3bc65bada96b04c9241c628fcb5ab0724a6928/time_machine-3.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d7faced22388578dbed3b4be0603eb00f709857cd57b7b9738cd81fbaf326a9", size = 33883, upload-time = "2025-11-21T13:55:12.32Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5c/f433fe44eadecfe21e4f2bf128d240c15d295592c877490a475960644281/time_machine-3.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2e8a78dd7afc3a081e208e0b8d1a8105cff95d96e4f79da746afdf05fb822e7a", size = 32769, upload-time = "2025-11-21T13:55:13.541Z" }, + { url = "https://files.pythonhosted.org/packages/09/0f/fbc0e1437801315d4bdd8c21f9ef0c51f005a327ab0289ca034658fe78a1/time_machine-3.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8b9dce925dae420d6b66dec4cae00785260dbededec5c89eaedbfba99a2be55b", size = 30765, upload-time = "2025-11-21T13:55:14.706Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c0/87d23d5817d95ed07fe272bb7a5de91177bb18274718d318c73a4aa0a4c2/time_machine-3.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:17f0d30a05073cdee68162779870eadf5e7f091bc94ae96d90d8fddbb8020714", size = 31885, upload-time = "2025-11-21T13:55:16.56Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d7/572e38dadab9efe6ec4fff6e063f488866121dc384873d5b04fc5855ca83/time_machine-3.1.0-cp311-cp311-win32.whl", hash = "sha256:9e836e4fa8cb58de80de863335f4566f896b4dcd69d8a400d705857ca8301872", size = 16935, upload-time = "2025-11-21T13:55:17.612Z" }, + { url = "https://files.pythonhosted.org/packages/00/a6/edc968e1429a14d28676adb596f42570aa42def63014ccd3ccaf8d279d43/time_machine-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:1d652f85cbd2fd41e4262c27515023cc216589ca0b4aebab458972cce8119cc1", size = 17779, upload-time = "2025-11-21T13:55:18.668Z" }, + { url = "https://files.pythonhosted.org/packages/a0/97/2025eea7792f1be50777d85a2e2974d4416698c0002c419a61fcc6222de8/time_machine-3.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:4be520b8ed752e788f57f72617f12b5bf5103e3db2b062e69b82e7f7f2977c4f", size = 16496, upload-time = "2025-11-21T13:55:19.753Z" }, + { url = "https://files.pythonhosted.org/packages/39/3d/412015d3e2f682548b7222367aa8d7b91d323145234d216847bc56c2d720/time_machine-3.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d3d6606ae81a29c925452f8b56115936aeda34c0a25d40af53cf1b29c66235ef", size = 18851, upload-time = "2025-11-21T13:55:21Z" }, + { url = "https://files.pythonhosted.org/packages/33/98/48f60552570d6d66773dcfbc1d7b8fb305d3e9ae0694dd249f1ae0bc3b77/time_machine-3.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0265df307778104c44d9114b55246a5b99da03f1dcb791305f9af21b0389ef7b", size = 14955, upload-time = "2025-11-21T13:55:22.408Z" }, + { url = "https://files.pythonhosted.org/packages/4d/7a/6fd1c47d3a1d87919d38f85c12db8f838298acb4ca3d6908f3288bcea0fd/time_machine-3.1.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:098da08900bdf6c6bd44b36ec06afd5b92c7a7140cd48c8686a79b6c6fef9da7", size = 32622, upload-time = "2025-11-21T13:55:23.541Z" }, + { url = "https://files.pythonhosted.org/packages/64/74/01641bd3d5f8c4f22710b7070d1dbeaeb501e8549e37419fc8b995bead32/time_machine-3.1.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:092f2e7a2526a02fcbd9c0a4165e0790350a13da4b01b6306b82e9580d83ae69", size = 34172, upload-time = "2025-11-21T13:55:24.638Z" }, + { url = "https://files.pythonhosted.org/packages/35/df/91f39b8bfe42c67dd3e66d8d2baa2c1b10126cc6e217fb3c7b1e777804c5/time_machine-3.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64b6cada6dd2433cdaeda53dd940bdd579e40a8c92c5379527694570bb58b97", size = 35554, upload-time = "2025-11-21T13:55:25.841Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6c/236434de77561003429e90300327b5ac6a6eeaa6d6c967282d28d1983232/time_machine-3.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2776e3300ef499541f9081b4a03ff1b3e7681e51a594572e1bf191773504bd21", size = 34261, upload-time = "2025-11-21T13:55:27.715Z" }, + { url = "https://files.pythonhosted.org/packages/97/76/32eea75715aefbd7ccfeea70285bb5400ecebd8dc3524b9c3491115e2504/time_machine-3.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:86e10914592a95e35edeb081975b6527919300bd1b65c04ee7f765db7bf2c1ad", size = 32485, upload-time = "2025-11-21T13:55:29.55Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1b/91c529de2d2c6d7097692b4ae620cbe30bf24a4609d737b5f41d91a77bb0/time_machine-3.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1362a6672252fb0d79da492dcf75eb7369b37fe595946ee3c2848df2fcc22e7", size = 33674, upload-time = "2025-11-21T13:55:30.636Z" }, + { url = "https://files.pythonhosted.org/packages/d6/af/345dfab6543e79151867daabbc4f4788ee10e408b8bd1361d066d0fea932/time_machine-3.1.0-cp312-cp312-win32.whl", hash = "sha256:50773648c69960e6e8497077875427aeb484d6a57a06399502cc125e349fca19", size = 16962, upload-time = "2025-11-21T13:55:31.73Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9d/79a4ebed58d78cad85a5fc5c4ed4558d4d018d8a2bb7858ea02704b49be7/time_machine-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:20e0974e58a40a626d353d6132b7595de3fcb8deb72da4a762071b315cc95f6f", size = 17723, upload-time = "2025-11-21T13:55:32.76Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a2/dd405133427dc47afd95618c3519854147408ed05deb209ba1b6b704689b/time_machine-3.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:da9bced71b5966312f13c423b5b124649f1b08a9400d95018eb6d23311b384b9", size = 16520, upload-time = "2025-11-21T13:55:33.972Z" }, + { url = "https://files.pythonhosted.org/packages/c7/27/8af0187b4f7c574d7c4d7e86dbaece47ac92666fda8717c787849bc48560/time_machine-3.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:99d91fc0d4d91be1e1ea92389e1e93b0f43bf60433409616cb43de69be6505a8", size = 18911, upload-time = "2025-11-21T13:55:35.056Z" }, + { url = "https://files.pythonhosted.org/packages/92/35/a948a07659d471be160c8500c2e82ca0576a067d52d86ebe7ef24ea8e141/time_machine-3.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff18d291b7e681af3dc2f069a182a367baee510ab9800120a487a01d2ec929a9", size = 14983, upload-time = "2025-11-21T13:55:36.414Z" }, + { url = "https://files.pythonhosted.org/packages/4a/8c/0cc16dd1d058580c00ffa685401756bd6170efe4434d418b724e96e3a0ac/time_machine-3.1.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fedc78cab733dfe1afeec09bd841aea314249b316eb02f17ad2e617c400fa4d", size = 32709, upload-time = "2025-11-21T13:55:37.628Z" }, + { url = "https://files.pythonhosted.org/packages/20/34/f2f162c67854be20c34ed9c49474b6abd6427108b98c3452533e60ba2526/time_machine-3.1.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:62bb78376c4a13a0463b71bc9ffd81d9e3e6ed9efdbe500716d9c51ae5a2a60c", size = 34299, upload-time = "2025-11-21T13:55:38.999Z" }, + { url = "https://files.pythonhosted.org/packages/8b/2c/314fe33e24bbc46837643d5add7a9843c7cbd4b66f355a94e98c700ddcac/time_machine-3.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc750d300bd8794a0581360632e20dd5896d21b5c1c6b74c7c01c72bebd65df2", size = 35699, upload-time = "2025-11-21T13:55:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ac/28/c2f5d93024ef712897352e3fb801425325adfb3b2c33d3ba7838c8ea5941/time_machine-3.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d23623c3223318cb4b43ad07e10b682821c59b6ab99cce3d9db0f80bc2206ead", size = 34359, upload-time = "2025-11-21T13:55:41.672Z" }, + { url = "https://files.pythonhosted.org/packages/25/c6/67a6abd6ab75a6c16275cd8b5bf13053f33fac1de83a5b8e569685d37005/time_machine-3.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:48a4ad44202dcafd302310739e086374c8e928d6604bb5812b1f5e755dbcd5e1", size = 32604, upload-time = "2025-11-21T13:55:42.878Z" }, + { url = "https://files.pythonhosted.org/packages/13/c7/1277ebfbcfaea02bbf01a69beac821a6543e1829a47bda52a020b3509ba2/time_machine-3.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b6f8572c1e674d5ae43882ee9f39a03cea86c31cf9904ff3767905d41fc5e5a", size = 33779, upload-time = "2025-11-21T13:55:44.057Z" }, + { url = "https://files.pythonhosted.org/packages/6b/39/773a7456222b391e2f0bc6d049e00d99ea78b1e226b8b36c261e1235f36d/time_machine-3.1.0-cp313-cp313-win32.whl", hash = "sha256:8743edd11d3e2cb2d0244d4e83d96873fd96a375ba75364399f2f64fd95c7ec4", size = 16984, upload-time = "2025-11-21T13:55:45.144Z" }, + { url = "https://files.pythonhosted.org/packages/e9/95/94b9a839586eae1e3afcd575d1dabf81929e44e3886ad6d94deb5e2d5bda/time_machine-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:3a99c68a1cc962f76709c2b67efdcf4c97c9ad4a950f694cccb413ab378f9d94", size = 17727, upload-time = "2025-11-21T13:55:46.524Z" }, + { url = "https://files.pythonhosted.org/packages/1f/fd/f1fb569e8c7547c983b4e3259ee40684b0c4fdc882f36864d5eb05d71f72/time_machine-3.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:7be2af8b69892615d897b2c6b546093e45dba7d9cde6af64c17f1e5da4f38dbc", size = 16558, upload-time = "2025-11-21T13:55:47.922Z" }, + { url = "https://files.pythonhosted.org/packages/e9/15/ccb1178e3a0988c320075285fe7b5ab26e51b71b2e5e14eee7158bd04dd6/time_machine-3.1.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:56938d4a24435014d3d9141415aee81699cf1a5419462a24357c7e3181c67f06", size = 19593, upload-time = "2025-11-21T13:55:50.114Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9e/495e1ae27227cc3fc20f5d9e9011c14a3bda515f0c98630b0d0e2c444c4a/time_machine-3.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ecdd9b98305cecfbff29ad9b663f38cbcf728ff023dc1db63cc94bd439890da5", size = 15252, upload-time = "2025-11-21T13:55:51.213Z" }, + { url = "https://files.pythonhosted.org/packages/d6/03/c905659d81c6b071cd8f2a6a6a23b1e25cd2a498167125b95e543fea7cec/time_machine-3.1.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e5f0dc8ba33fcd624d11dc4029fa3fd1712f96660ddc629ce61097c71d8f6400", size = 38810, upload-time = "2025-11-21T13:55:52.379Z" }, + { url = "https://files.pythonhosted.org/packages/42/26/393277a6f07472cdb56ee2d8e34f0bdc203f64c8857180b73c4ba9cf0d91/time_machine-3.1.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:812aad79acf4b3471d997d29a5e7010f0a914740b1fe5b6cefb81b462cb28824", size = 40358, upload-time = "2025-11-21T13:55:54.077Z" }, + { url = "https://files.pythonhosted.org/packages/86/70/0cc738ba7fdaf8d29acd128a124be00c781b33e3ea84f34211f5a2cff4c2/time_machine-3.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b0218aa05865a16c000320cfdac921d0e02992ef51e711325bc366bacdc4aeb", size = 43118, upload-time = "2025-11-21T13:55:55.616Z" }, + { url = "https://files.pythonhosted.org/packages/dc/5a/6c42a046abfcb8996ef3239bbc1cfd7c0051dea166a0f9f01923d1eb1848/time_machine-3.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3544ab394f4149e8884996f1c5047d52dbb08bb2396109c530eee6ecffd6f4c4", size = 41321, upload-time = "2025-11-21T13:55:56.869Z" }, + { url = "https://files.pythonhosted.org/packages/36/3e/1123a93add930d4933ca4f1c3441f1832eba6b9e1b41b9ca3a5d3f9203c7/time_machine-3.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:acf41d44e526cae2f62c9c6ac95daef42bdbd7d95bdb3bb60e071b4b61110723", size = 38547, upload-time = "2025-11-21T13:55:58.065Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c8/c98855aa75a6bc49c352e00396e545353db3e5d7c65a6eefca76366d9aac/time_machine-3.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bb7031a367209c223c41ab625172b38362e0ce07f13f1f1e7d75d5194fcdd0d7", size = 39421, upload-time = "2025-11-21T13:55:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/aa/94/f1520be4f125489a9d327848048688c2c13c5705770b98caac63e35cc204/time_machine-3.1.0-cp313-cp313t-win32.whl", hash = "sha256:ecf49c418f854f42171b4f0859906a26ff56d73303dee2e83beb307747e11db1", size = 17436, upload-time = "2025-11-21T13:56:00.395Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/79e13c341b20e8ceb1629fb2e1ae36063c9dee42f3886be44a54867ad0dc/time_machine-3.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee93cf4c11452bf8211bf12a926d6f5179c241558f6af30c2de2669bf26ba1c1", size = 18505, upload-time = "2025-11-21T13:56:01.54Z" }, + { url = "https://files.pythonhosted.org/packages/72/42/0cdb0b67d44ebfa47f4dbecb65d25522312ee772f59c4d63a0df0c895f34/time_machine-3.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9c317eebaa0578a370a29f40152db0ac00bd34b387b54c95bf01fd123bca178d", size = 16852, upload-time = "2025-11-21T13:56:02.977Z" }, + { url = "https://files.pythonhosted.org/packages/f0/14/2f9b4c6ae63662827c48d81c445fedeba4733248a56640747c8e5be55870/time_machine-3.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2b07055e5327e04d725557a07a69523d14d2d897877d90781b9c27c70bd8997c", size = 18899, upload-time = "2025-11-21T13:56:04.186Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c2/09a91825ea60413316ece41e448d275a9a4b719bc92b35b6166013dc01bb/time_machine-3.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:b71948998e3f709bda9b600b0d250bb4ad677b28fac32475b6093aa5b9e8969f", size = 15027, upload-time = "2025-11-21T13:56:05.299Z" }, + { url = "https://files.pythonhosted.org/packages/f5/65/b737258b39b98406a3ed681cdc025fa788441221c5d24a59897a4752e413/time_machine-3.1.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:343dfb0663ccb1e5d5dc6dfb651b7b7233985c73b3a3f6af0fe58c9cf5b0f4ab", size = 32798, upload-time = "2025-11-21T13:56:06.521Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e0/063edd2188a5c7e8f4b1a184dc9e87de955dcfd5cd8f706131739ff0685c/time_machine-3.1.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3df9b834ec2ee8536a398c15c70f5d54dfe3bbb34344f6549ba29acf80916948", size = 34385, upload-time = "2025-11-21T13:56:07.719Z" }, + { url = "https://files.pythonhosted.org/packages/84/e8/ead05dc304f973b01443829367be3c504f3ff86c394a3fec932c4d720f3f/time_machine-3.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e1ee0725a016f69fa8f0f37d793ba6d2d3870b32e164650a6922caf065f2ce2", size = 35781, upload-time = "2025-11-21T13:56:08.931Z" }, + { url = "https://files.pythonhosted.org/packages/1b/5e/23303d6b13482436d6c37015d17142821adf8e47c1104c0a4c5fc0bdb173/time_machine-3.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ed552f135efabca895e678044ce1dbb693e6a399003606e9d6a413b2eaf48a51", size = 34447, upload-time = "2025-11-21T13:56:10.468Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/5ace5c8e2dc0b6899c3e18ebf4301211a50e1addfcbecbf61a100a76ac03/time_machine-3.1.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c93451242de81fe2a96f699dad97aa463250688b10d2e8a72e98208df9bd62b1", size = 32814, upload-time = "2025-11-21T13:56:12.072Z" }, + { url = "https://files.pythonhosted.org/packages/40/cb/b0212b98e422fbb08f7328aabe0c6f59e853146eb61337df8f497dd4a2ad/time_machine-3.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:010c2dd1f084eae4687021f7b7fd798abc7a8472f2e783919aafe7b8fe624c8b", size = 33864, upload-time = "2025-11-21T13:56:13.28Z" }, + { url = "https://files.pythonhosted.org/packages/28/53/08ad68c1971257ee0c9b9ec38b1f2ef88ae7565e0c7eb272f9ca3ff40152/time_machine-3.1.0-cp314-cp314-win32.whl", hash = "sha256:9b8e24de4ba47401dcec53733d98db9678a708f6bafb77a64e46636304eca64c", size = 17127, upload-time = "2025-11-21T13:56:14.414Z" }, + { url = "https://files.pythonhosted.org/packages/0c/36/95d5b7fff7e1506f9f4a481df3b17ebae0f3ab4a36669e6a93890df1da5f/time_machine-3.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:6873c903c8de85884655afc49c8465136ea5d6c7500ad2bea31601cf6a48939f", size = 18006, upload-time = "2025-11-21T13:56:15.575Z" }, + { url = "https://files.pythonhosted.org/packages/d2/c8/b30211e36117b4436368927e46dcf1f785626069b11a12cc3ea150337136/time_machine-3.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:9a74b2da34e7e6aaa2db011556f40f8ea26e89a3a1683ffad43ceca1789b8af0", size = 16633, upload-time = "2025-11-21T13:56:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3d/eeb00cd285102e39cc4eeeb4e78cc1fcff8a89691bdc6a708d4c40fe38cc/time_machine-3.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0c688897189b50f0820e2916579c7e88aefef2a86cd17aa05b5b7a6676dbd97e", size = 19578, upload-time = "2025-11-21T13:56:17.853Z" }, + { url = "https://files.pythonhosted.org/packages/3e/91/30710e1883a4c39b1367ef469d6fd18c791bec4ee8783a19af9ac82bc632/time_machine-3.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1715d221c4c49bd2a0bc73868d5543133ab15e02e0d9726d73d802ccf978e1c0", size = 15299, upload-time = "2025-11-21T13:56:18.955Z" }, + { url = "https://files.pythonhosted.org/packages/d4/7f/2311774df6d41dba3934494b6589195a726fec0753a4e8e8eba28e509327/time_machine-3.1.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8cecc83df46ab1095f93ff42dceaddb313e12efd9135cd153d0021a98b570390", size = 38801, upload-time = "2025-11-21T13:56:20.357Z" }, + { url = "https://files.pythonhosted.org/packages/1b/74/5af7e7af3787333c927d860476b505ec8770412e2bb1ba4e2d00a3aa644a/time_machine-3.1.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:be63019454e0e30138bfe414f3dd5f626d32b8a265ea99bdc4b107867b68128a", size = 40357, upload-time = "2025-11-21T13:56:21.971Z" }, + { url = "https://files.pythonhosted.org/packages/10/1a/ebcecff1e57f52788989f0734a57eab5e045c9784cfd998040b8ba280f5b/time_machine-3.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac1245d6a4a4ac40e308362e4236c1aad6ead836c97576c7e29167752a5283d0", size = 43101, upload-time = "2025-11-21T13:56:23.555Z" }, + { url = "https://files.pythonhosted.org/packages/99/b3/63883e2d8555358469da098dd1568ec8f6c9b6d7317796cfbf8bc5c59ab2/time_machine-3.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9fde90d14b13396ecaa5ce2366f788f7c01b7bf4ac4246b798c622bc6369b861", size = 41305, upload-time = "2025-11-21T13:56:24.831Z" }, + { url = "https://files.pythonhosted.org/packages/04/a3/d7851676cf7a5d5451b73f271b6b7229688f403488a8dd111b5fe5fde7cf/time_machine-3.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:15d83cc338d02dc6e674e24ce40a8c311d75191c86014b0be455a4267f27f00e", size = 38534, upload-time = "2025-11-21T13:56:26.032Z" }, + { url = "https://files.pythonhosted.org/packages/13/dc/ced9245bc633f0c4790a57b3c6089a586f0a208b50f8ec7d001bf8254d49/time_machine-3.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cbbd90494a9bc6626e3180594246cb9557418e32f2cb2c40edf8526a182f5e31", size = 39430, upload-time = "2025-11-21T13:56:28.83Z" }, + { url = "https://files.pythonhosted.org/packages/67/24/bb20ff76ed4e8e09ab65910bf21a315dc7562c8be92250363b25f3ab1dd1/time_machine-3.1.0-cp314-cp314t-win32.whl", hash = "sha256:6c00758d155601d710fa036c8d24d5ad3fb28531933cf70343006cf2be93092a", size = 17674, upload-time = "2025-11-21T13:56:29.969Z" }, + { url = "https://files.pythonhosted.org/packages/2e/64/42573a6da9298efd68a831d4e9eabc8c9c0cac9305bc19bb24a4066bbba0/time_machine-3.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e00239b54b05255862e0965e6ae67728e467727ca7dc23d9a6c5a51c7b5b01c8", size = 18792, upload-time = "2025-11-21T13:56:31.123Z" }, + { url = "https://files.pythonhosted.org/packages/35/10/09ad4e5ccc27224ed8377a6f3d191034242c404d0c1ad5f119d79fb18363/time_machine-3.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:8b47da89595dc3c7f0b52f1e3f3f8da8325037f3746e66d74bebac9f42f2a989", size = 16944, upload-time = "2025-11-21T13:56:32.254Z" }, +] + +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "extra == 'group-12-dedalus-labs-pydantic-v2' or extra != 'group-12-dedalus-labs-pydantic-v1'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/43/a2204825342f37c337f5edb6637040fa14e365b2fcc2346960201d457579/yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e", size = 140517, upload-time = "2025-10-06T14:08:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/44/6f/674f3e6f02266428c56f704cd2501c22f78e8b2eeb23f153117cc86fb28a/yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f", size = 93495, upload-time = "2025-10-06T14:08:46.2Z" }, + { url = "https://files.pythonhosted.org/packages/b8/12/5b274d8a0f30c07b91b2f02cba69152600b47830fcfb465c108880fcee9c/yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf", size = 94400, upload-time = "2025-10-06T14:08:47.855Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7f/df1b6949b1fa1aa9ff6de6e2631876ad4b73c4437822026e85d8acb56bb1/yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a", size = 347545, upload-time = "2025-10-06T14:08:49.683Z" }, + { url = "https://files.pythonhosted.org/packages/84/09/f92ed93bd6cd77872ab6c3462df45ca45cd058d8f1d0c9b4f54c1704429f/yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c", size = 319598, upload-time = "2025-10-06T14:08:51.215Z" }, + { url = "https://files.pythonhosted.org/packages/c3/97/ac3f3feae7d522cf7ccec3d340bb0b2b61c56cb9767923df62a135092c6b/yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147", size = 363893, upload-time = "2025-10-06T14:08:53.144Z" }, + { url = "https://files.pythonhosted.org/packages/06/49/f3219097403b9c84a4d079b1d7bda62dd9b86d0d6e4428c02d46ab2c77fc/yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb", size = 371240, upload-time = "2025-10-06T14:08:55.036Z" }, + { url = "https://files.pythonhosted.org/packages/35/9f/06b765d45c0e44e8ecf0fe15c9eacbbde342bb5b7561c46944f107bfb6c3/yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6", size = 346965, upload-time = "2025-10-06T14:08:56.722Z" }, + { url = "https://files.pythonhosted.org/packages/c5/69/599e7cea8d0fcb1694323b0db0dda317fa3162f7b90166faddecf532166f/yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0", size = 342026, upload-time = "2025-10-06T14:08:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/95/6f/9dfd12c8bc90fea9eab39832ee32ea48f8e53d1256252a77b710c065c89f/yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda", size = 335637, upload-time = "2025-10-06T14:09:00.506Z" }, + { url = "https://files.pythonhosted.org/packages/57/2e/34c5b4eb9b07e16e873db5b182c71e5f06f9b5af388cdaa97736d79dd9a6/yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc", size = 359082, upload-time = "2025-10-06T14:09:01.936Z" }, + { url = "https://files.pythonhosted.org/packages/31/71/fa7e10fb772d273aa1f096ecb8ab8594117822f683bab7d2c5a89914c92a/yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737", size = 357811, upload-time = "2025-10-06T14:09:03.445Z" }, + { url = "https://files.pythonhosted.org/packages/26/da/11374c04e8e1184a6a03cf9c8f5688d3e5cec83ed6f31ad3481b3207f709/yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467", size = 351223, upload-time = "2025-10-06T14:09:05.401Z" }, + { url = "https://files.pythonhosted.org/packages/82/8f/e2d01f161b0c034a30410e375e191a5d27608c1f8693bab1a08b089ca096/yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea", size = 82118, upload-time = "2025-10-06T14:09:11.148Z" }, + { url = "https://files.pythonhosted.org/packages/62/46/94c76196642dbeae634c7a61ba3da88cd77bed875bf6e4a8bed037505aa6/yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca", size = 86852, upload-time = "2025-10-06T14:09:12.958Z" }, + { url = "https://files.pythonhosted.org/packages/af/af/7df4f179d3b1a6dcb9a4bd2ffbc67642746fcafdb62580e66876ce83fff4/yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b", size = 82012, upload-time = "2025-10-06T14:09:14.664Z" }, + { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, + { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, + { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, + { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, + { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, + { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, + { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, + { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, + { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, + { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, + { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, + { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, + { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, + { url = "https://files.pythonhosted.org/packages/94/fd/6480106702a79bcceda5fd9c63cb19a04a6506bd5ce7fd8d9b63742f0021/yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748", size = 141301, upload-time = "2025-10-06T14:12:19.01Z" }, + { url = "https://files.pythonhosted.org/packages/42/e1/6d95d21b17a93e793e4ec420a925fe1f6a9342338ca7a563ed21129c0990/yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859", size = 93864, upload-time = "2025-10-06T14:12:21.05Z" }, + { url = "https://files.pythonhosted.org/packages/32/58/b8055273c203968e89808413ea4c984988b6649baabf10f4522e67c22d2f/yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9", size = 94706, upload-time = "2025-10-06T14:12:23.287Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/d7bfbc28a88c2895ecd0da6a874def0c147de78afc52c773c28e1aa233a3/yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054", size = 347100, upload-time = "2025-10-06T14:12:28.527Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e8/37a1e7b99721c0564b1fc7b0a4d1f595ef6fb8060d82ca61775b644185f7/yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b", size = 318902, upload-time = "2025-10-06T14:12:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ef/34724449d7ef2db4f22df644f2dac0b8a275d20f585e526937b3ae47b02d/yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60", size = 363302, upload-time = "2025-10-06T14:12:32.295Z" }, + { url = "https://files.pythonhosted.org/packages/8a/04/88a39a5dad39889f192cce8d66cc4c58dbeca983e83f9b6bf23822a7ed91/yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890", size = 370816, upload-time = "2025-10-06T14:12:34.01Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1f/5e895e547129413f56c76be2c3ce4b96c797d2d0ff3e16a817d9269b12e6/yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba", size = 346465, upload-time = "2025-10-06T14:12:35.977Z" }, + { url = "https://files.pythonhosted.org/packages/11/13/a750e9fd6f9cc9ed3a52a70fe58ffe505322f0efe0d48e1fd9ffe53281f5/yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca", size = 341506, upload-time = "2025-10-06T14:12:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/3c/67/bb6024de76e7186611ebe626aec5b71a2d2ecf9453e795f2dbd80614784c/yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba", size = 335030, upload-time = "2025-10-06T14:12:39.775Z" }, + { url = "https://files.pythonhosted.org/packages/a2/be/50b38447fd94a7992996a62b8b463d0579323fcfc08c61bdba949eef8a5d/yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b", size = 358560, upload-time = "2025-10-06T14:12:41.547Z" }, + { url = "https://files.pythonhosted.org/packages/e2/89/c020b6f547578c4e3dbb6335bf918f26e2f34ad0d1e515d72fd33ac0c635/yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e", size = 357290, upload-time = "2025-10-06T14:12:43.861Z" }, + { url = "https://files.pythonhosted.org/packages/8c/52/c49a619ee35a402fa3a7019a4fa8d26878fec0d1243f6968bbf516789578/yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8", size = 350700, upload-time = "2025-10-06T14:12:46.868Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c9/f5042d87777bf6968435f04a2bbb15466b2f142e6e47fa4f34d1a3f32f0c/yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b", size = 82323, upload-time = "2025-10-06T14:12:48.633Z" }, + { url = "https://files.pythonhosted.org/packages/fd/58/d00f7cad9eba20c4eefac2682f34661d1d1b3a942fc0092eb60e78cfb733/yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed", size = 87145, upload-time = "2025-10-06T14:12:50.241Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a3/70904f365080780d38b919edd42d224b8c4ce224a86950d2eaa2a24366ad/yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2", size = 82173, upload-time = "2025-10-06T14:12:51.869Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +]