Compare commits

..
Author SHA1 Message Date
Saad FarooqandRobin bc7b8634ed feat(cli): match LangSmith login success screen
Co-authored-by: Robin <open-swe@users.noreply.github.com>
2026-11-06 17:24:11 +00:00
Hari Dhanushkodiandopen-swe[bot] 6e144fb893 feat(cli): log in to LangSmith without an API key
Reuse the `langsmith login` OAuth profile (~/.langsmith/config.json) for the
deploy commands, refreshing the 5-minute access token per request, and add
`langgraph login` (device flow). With no credentials, an interactive deploy
starts the login flow instead of prompting for an API key.

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-10-30 20:16:51 +00:00
64 changed files with 1216 additions and 2146 deletions

No files matched your search

+15 -10
View File
@@ -115,16 +115,21 @@ jobs:
echo "LANGSMITH_API_KEY=${{ secrets.LANGSMITH_API_KEY }}" >> .env
timeout --kill-after=30 300 python ../../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-h
echo "Finished starting up langgraph-test-h"
pins=$(grep -oE '"[a-z0-9-]+==[^"]+"' pyproject.toml | tr -d '"')
[ -n "$pins" ] || { echo "no == pins found in pyproject.toml"; exit 1; }
for pin in $pins; do
pkg=${pin%%==*}
installed=$(docker run --rm --entrypoint "" langgraph-test-h python -c "from importlib.metadata import version; print(version('$pkg'))")
if [ "$installed" != "${pin#*==}" ]; then
echo "$pkg $installed != pinned ${pin#*==}"
exit 1
fi
done
LANGGRAPH_VERSION=$(docker run --rm --entrypoint "" langgraph-test-h python -c "import sys; from importlib.metadata import version; v = version('langgraph'); print(v);")
if [ "$LANGGRAPH_VERSION" != "1.2.6" ]; then
echo "LANGGRAPH_VERSION != 1.2.6; $LANGGRAPH_VERSION"
exit 1
fi
LANGCHAIN_OPENAI_VERSION=$(docker run --rm --entrypoint "" langgraph-test-h python -c "import sys; from importlib.metadata import version; v = version('langchain-openai'); print(v);")
if [ "$LANGCHAIN_OPENAI_VERSION" != "1.1.14" ]; then
echo "LANGCHAIN_OPENAI_VERSION != 1.1.14; $LANGCHAIN_OPENAI_VERSION"
exit 1
fi
LANGCHAIN_ANTHROPIC_VERSION=$(docker run --rm --entrypoint "" langgraph-test-h python -c "import sys; from importlib.metadata import version; v = version('langchain-anthropic'); print(v);")
if [ "$LANGCHAIN_ANTHROPIC_VERSION" != "1.4.6" ]; then
echo "LANGCHAIN_ANTHROPIC_VERSION != 1.4.6; $LANGCHAIN_ANTHROPIC_VERSION"
exit 1
fi
- name: Build and test prerelease reqs fail service
if: ${{ (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') &&matrix.example.name == 'A' }}
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
working-directory: libs/sdk-py
- name: Login to Docker Hub
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
cache-suffix: test-${{ inputs.working-directory }}
working-directory: ${{ inputs.working-directory }}
- name: Login to Docker Hub
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
cache-suffix: "test-langgraph"
working-directory: libs/langgraph
- name: Login to Docker Hub
uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
+3 -3
View File
@@ -48,7 +48,7 @@ jobs:
working-directory: ${{ inputs.working-directory }}
- name: Upload build
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-dist
path: ${{ inputs.working-directory }}/dist/
@@ -76,13 +76,13 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: test-dist
path: ${{ inputs.working-directory }}/dist/
- name: Publish to test PyPI
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
with:
packages-dir: ${{ inputs.working-directory }}/dist/
verbose: true
+1 -1
View File
@@ -114,7 +114,7 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
- name: Run check_sdk_methods script
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Python
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.11'
+5 -5
View File
@@ -51,7 +51,7 @@ jobs:
working-directory: ${{ inputs.working-directory }}
- name: Upload build
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
path: ${{ inputs.working-directory }}/dist/
@@ -167,7 +167,7 @@ jobs:
enable-cache: false
working-directory: ${{ inputs.working-directory }}
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: ${{ inputs.working-directory }}/dist/
@@ -259,13 +259,13 @@ jobs:
enable-cache: false
working-directory: ${{ inputs.working-directory }}
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: ${{ inputs.working-directory }}/dist/
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
with:
packages-dir: ${{ inputs.working-directory }}/dist/
verbose: true
@@ -301,7 +301,7 @@ jobs:
enable-cache: false
working-directory: ${{ inputs.working-directory }}
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: ${{ inputs.working-directory }}/dist/
+2 -2
View File
@@ -55,7 +55,7 @@ jobs:
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.ORG_MEMBERSHIP_APP_ID }}
private-key: ${{ secrets.ORG_MEMBERSHIP_APP_PRIVATE_KEY }}
@@ -190,7 +190,7 @@ jobs:
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.ORG_MEMBERSHIP_APP_ID }}
private-key: ${{ secrets.ORG_MEMBERSHIP_APP_PRIVATE_KEY }}
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.ORG_MEMBERSHIP_APP_ID }}
private-key: ${{ secrets.ORG_MEMBERSHIP_APP_PRIVATE_KEY }}
+45 -45
View File
@@ -1,5 +1,5 @@
version = 1
revision = 5
revision = 3
requires-python = ">=3.10"
[[package]]
@@ -686,7 +686,7 @@ wheels = [
[[package]]
name = "pytest"
version = "9.2.0"
version = "9.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
@@ -697,23 +697,23 @@ dependencies = [
{ name = "pygments" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/79/42/bb4641652e58dedeac2ec51abc1f8cae4fee4ec63899a0999016229a0f2a/pytest-9.2.0.tar.gz", hash = "sha256:97c4f3b1f3c7414aa36465e09b8220f1d03627993bd51b1282e193753c80557b", size = 1797555, upload-time = "2026-10-27T21:30:20.728Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/51/39/1f5d8c8877a0fa327db959c19341d4be61660de1ab6b7391f9eb805bcd67/pytest-9.2.0-py3-none-any.whl", hash = "sha256:07acdcf88ec174f907dc01af70bf52d49b014bbab03faeeade31c96b18c2e588", size = 411776, upload-time = "2026-10-27T21:30:19.131Z" },
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
]
[[package]]
name = "pytest-asyncio"
version = "1.4.1"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" },
{ name = "pytest" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f8/9a/887535e477847f5159377ab35303923644840b13c0ca03c0a467f84d4680/pytest_asyncio-1.4.1.tar.gz", hash = "sha256:e8266eb094e5ee9ffdb2068c9836f33e209ea77a970eb846933d416b5602669b", size = 145237, upload-time = "2026-10-25T21:05:37.135Z" }
sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/46/d7/cd9e2708afdbf059658adaf08bdb0792ca52489871051d6ce7d498b51277/pytest_asyncio-1.4.1-py3-none-any.whl", hash = "sha256:507afa78ec77c6661164ef1a2368bc7820383fd1a14b2a7f813de8b5a92949cc", size = 17061, upload-time = "2026-10-25T21:05:35.473Z" },
{ url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" },
]
[[package]]
@@ -809,27 +809,27 @@ wheels = [
[[package]]
name = "ruff"
version = "0.17.3"
version = "0.16.9"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/59/73/81f140c4edd9cc323de5478e6bbd3bbcfae9a82692df1a258de8cec6a6d2/ruff-0.17.3.tar.gz", hash = "sha256:a57049c8addb3662073859bb55a7ad35aa54f8a7c9ed268911d1333c5aadaaa9", size = 5339444, upload-time = "2026-10-27T00:34:05.914Z" }
sdist = { url = "https://files.pythonhosted.org/packages/96/bf/c935ca98e73fe8ce65b87ef08a280c0c1e85295d569228c15e87d8fdfaf1/ruff-0.16.9.tar.gz", hash = "sha256:12b625c6cfba78d285d9f48eda5f053374f1e53cb10ef17342a383750db99161", size = 4948764, upload-time = "2026-09-24T20:37:49.416Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9e/8c/083365310c4e79c842f25bf5a842facbd9b0c0ec336f96dffa5074fe723d/ruff-0.17.3-py3-none-linux_armv6l.whl", hash = "sha256:ab86b84cd9d05e56d329742c0cabdcbe2d1d56c98f4a04aab9b5b66196febfb4", size = 10216522, upload-time = "2026-10-27T00:33:29.822Z" },
{ url = "https://files.pythonhosted.org/packages/72/26/4b2edf31cfebe9605d8f6ed8f1d6f376d3a53d4c9a123e62661c64c440f0/ruff-0.17.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e407b4a6852853a9a2cdce96963e94ad9775578bb44e7561a0f2829f3cc04b11", size = 10217786, upload-time = "2026-10-27T00:33:33.949Z" },
{ url = "https://files.pythonhosted.org/packages/a2/7e/537aba4b7f54397a374d2f6a6fc93e9c8015e18553f31a4a7b7dbced48b5/ruff-0.17.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b75cc113b3aca388933d8296b667b1ac401a80317ea3074c9b0a66541199da29", size = 9440362, upload-time = "2026-10-27T00:33:35.784Z" },
{ url = "https://files.pythonhosted.org/packages/3d/66/283e39734f66c8a5ebaa93c22eb7b7515779a0b1d5a5c0ad20e05c0edb41/ruff-0.17.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab5fa7ed5ce18554ea69d850ad162cbaa0c85fc47c08d83a569f806681c9a6ad", size = 10094281, upload-time = "2026-10-27T00:33:38.216Z" },
{ url = "https://files.pythonhosted.org/packages/21/4c/1ffc768497bd025a32c72152771e40c0b99bddc5cc6e4f50c7949a395346/ruff-0.17.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c673c170e27834d1262e3a9c1e97bdc9b9c3b180c63d89173ce69747232474c1", size = 10022543, upload-time = "2026-10-27T00:33:40.181Z" },
{ url = "https://files.pythonhosted.org/packages/73/c3/f90db547301080fcb82c92abf914c8bd57b89911d9b65a6c4cc6b52d0635/ruff-0.17.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a754c0341bc9ca151b7b335988b3263921c08859f84ca219b70cee825571b6f", size = 10683921, upload-time = "2026-10-27T00:33:42.182Z" },
{ url = "https://files.pythonhosted.org/packages/e5/98/92d4a8a26c6c96cf1a5b34dd59036d8a71e52d650aab065c58867e964e63/ruff-0.17.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4590b9d86c0b1543ebb09094254e191cd43fe8edaf330baa035aab03da251532", size = 11373408, upload-time = "2026-10-27T00:33:44.19Z" },
{ url = "https://files.pythonhosted.org/packages/43/02/1f24d2e3064c9753926deeb901afa57777b37ee46799b09ddd7aee6e5145/ruff-0.17.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbde18beccd98c29a227c63c99022e3443b99258e08326df6425dfe0443705ef", size = 10977469, upload-time = "2026-10-27T00:33:46.179Z" },
{ url = "https://files.pythonhosted.org/packages/f0/20/fecedea2209f574ab92033b344e01a7f0c2dc76c6d6d19a14c9f8141173f/ruff-0.17.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fa4b0f931cdbcb8a52044f370a3eb429fb42eb88807b0e800fcaa98dd42b24e", size = 10577491, upload-time = "2026-10-27T00:33:48.233Z" },
{ url = "https://files.pythonhosted.org/packages/33/36/b93fdf0bc6f84f0cd487aadfb0354deb40013669ea35ac3d26855de1e959/ruff-0.17.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:39405f341deec934b4289634324ad695f72df2970f9121582872eaf5b722f0f5", size = 10608748, upload-time = "2026-10-27T00:33:50.284Z" },
{ url = "https://files.pythonhosted.org/packages/5d/b7/7d3f5f14145daead1efb0b9a8289ed1daa100f419d60774375951723ce00/ruff-0.17.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68074094f55ca3d29ab19491f640282f7a84061c4c7977aa5094b968ca457e9a", size = 10075057, upload-time = "2026-10-27T00:33:52.074Z" },
{ url = "https://files.pythonhosted.org/packages/b0/ed/93ff7a5909ee56e2bd4b982deae9e0efb2724417874a90c9e41ad66c6dd7/ruff-0.17.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:27bb571ac78fef178fe5118d2827df95c2aae74a48fff2b15f6acd03af5ff5bb", size = 10022431, upload-time = "2026-10-27T00:33:54.061Z" },
{ url = "https://files.pythonhosted.org/packages/9b/07/a83b9eeec8c1c3c0e4eda15f06c3a91001bfd2687ea2f07b750719bf9712/ruff-0.17.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:029c37cbd3de48ca37aa12117bead2d065f15da17d66bf84e0d4274013705db2", size = 10394903, upload-time = "2026-10-27T00:33:56.158Z" },
{ url = "https://files.pythonhosted.org/packages/af/b2/05c39857c2526fcbebe46ad93a8dc97424656dfef14d3ef933abe1ffd331/ruff-0.17.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:692fdb46d0fd9c71e380947dc5fc72f2c3f881073bc1f739e4579376231affbe", size = 10799780, upload-time = "2026-10-27T00:33:58.204Z" },
{ url = "https://files.pythonhosted.org/packages/ec/d9/47171a16739eb7a353936b31c20cf54340531e68c9af7d72fd7a784e235d/ruff-0.17.3-py3-none-win32.whl", hash = "sha256:46539cb031b01fd29ae0a02142483c46640b233ee12bb1c1b690bffa1555d5ca", size = 9908481, upload-time = "2026-10-27T00:34:00.087Z" },
{ url = "https://files.pythonhosted.org/packages/d0/a0/f08c1631bfd81faa6a90eaf70c37c786c11bed2046302b56bb506c25e1a0/ruff-0.17.3-py3-none-win_amd64.whl", hash = "sha256:56b8ffb3f3b3cbccb8b76b71b38261b7ed982ef5d20f5b447b1b61a7f4949b24", size = 10341333, upload-time = "2026-10-27T00:34:02.099Z" },
{ url = "https://files.pythonhosted.org/packages/99/d5/4ad4ed31413c301430918e70d3d38ae6f097af0766cb9ab8c9d502b2a4d6/ruff-0.17.3-py3-none-win_arm64.whl", hash = "sha256:81fce74bcd6be0e9da7c67c7553fd0f1f12964a2052cd728ef917b937a5ca1d9", size = 9854332, upload-time = "2026-10-27T00:34:03.847Z" },
{ url = "https://files.pythonhosted.org/packages/0d/26/df51322b52ee1ada7eff2d071ea09d11d5c2d1dcc9f02594f5785c4d1635/ruff-0.16.9-py3-none-linux_armv6l.whl", hash = "sha256:95e6f022090368ab3b824c36276839c53b2adf1a3f4c09fefc33dfc400f6da96", size = 10082922, upload-time = "2026-09-24T20:37:13.045Z" },
{ url = "https://files.pythonhosted.org/packages/a5/27/7bf51f5a7aa375e9f339280a303aab44ca75dc1525f1cdc5991761685b0f/ruff-0.16.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a5f27be168556594a86d2f415db0cf43f5291917849318f873c7e2791f7a8c67", size = 10236360, upload-time = "2026-09-24T20:37:16.049Z" },
{ url = "https://files.pythonhosted.org/packages/b6/63/09659283f92f02dff45809da194a70da2f688d87c55d8875c4fae3536072/ruff-0.16.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1632eb1d6197f33bd00b1acbc5b71009e89a8895c158e2d2b03a834fac964ab6", size = 9892940, upload-time = "2026-09-24T20:37:17.957Z" },
{ url = "https://files.pythonhosted.org/packages/24/58/98de1b72ec172f5f8f1731236fe21585b3998bf7dfe9fcc44ae9ba626012/ruff-0.16.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3f951b14d865d5952c89d40a5ca07e87abe24fa5453299878411e127748fb1c", size = 10032114, upload-time = "2026-09-24T20:37:19.942Z" },
{ url = "https://files.pythonhosted.org/packages/c8/7d/f1e17c54ab59d4bad1dce8ee3e22a7a1d0ef4745240decacdcf3832b5bb2/ruff-0.16.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:447fc07e1573afff7cb02803462b12b6c8ece7cf10e2cd78565fa6d7a1c0bf8d", size = 9910227, upload-time = "2026-09-24T20:37:21.872Z" },
{ url = "https://files.pythonhosted.org/packages/34/19/436f647a65075bbd3bab2668b3bdaa5120559b294694018cdcefabbbf30b/ruff-0.16.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a3e039a6a40ed976c491722b60e0ae4a4aa1a86057f540ee7a37a5d19ae9120", size = 10547484, upload-time = "2026-09-24T20:37:24.229Z" },
{ url = "https://files.pythonhosted.org/packages/03/59/38430a6bc2f6d8095447ac39625cf8b6e9344a47e6c26225c8ba1bff3ffb/ruff-0.16.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4684dded7db60aa57cb118fa158630f5feade4af5782903b6053484bdf9bd129", size = 11412367, upload-time = "2026-09-24T20:37:26.307Z" },
{ url = "https://files.pythonhosted.org/packages/c8/bd/bbb6d7fc7f208c8b8c50dd5dc8206e4cfdb1e7a8fb852606a3adf370c880/ruff-0.16.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d29c934357e45642fda2f34c0b1f4025b4a6c01e15e4bf0016879d60078a142c", size = 10869787, upload-time = "2026-09-24T20:37:28.35Z" },
{ url = "https://files.pythonhosted.org/packages/bc/b8/9c543074918061abbefc3bd139bee22de35abedb00dde0f2d27288838962/ruff-0.16.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a21713e629d3e5bdb2f5c2def1cc7f04f47fa8e1a7eb0571b4a28e1da64bc728", size = 10406494, upload-time = "2026-09-24T20:37:30.624Z" },
{ url = "https://files.pythonhosted.org/packages/35/7a/5a8851bd146e7ccf8fd4b003f6c75c11f8fbdb0e60673b45097986b6bf41/ruff-0.16.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7baa24ef5fc8e77aa93879e1d3f43754a01ae488e869f1ae30cf431afd4d2452", size = 10590083, upload-time = "2026-09-24T20:37:32.439Z" },
{ url = "https://files.pythonhosted.org/packages/87/f0/4c3467188f23f806960b46fa76575a7cd0514c9ba90562650b71efc96980/ruff-0.16.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a41aac6230aadfaa133bdfa1614488531ffa3e0837567ae04c0da2058a9c0f9e", size = 10119151, upload-time = "2026-09-24T20:37:34.581Z" },
{ url = "https://files.pythonhosted.org/packages/15/34/5a4def5adea572ce6aea0bb64f21f928ee317b80e0db747d7979b01d7261/ruff-0.16.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c2529fb5896d49115b0e9aa8f887490b34bbe76baf879ec2264ac59406869ce7", size = 9911544, upload-time = "2026-09-24T20:37:36.796Z" },
{ url = "https://files.pythonhosted.org/packages/62/5d/d15ebea7499eef9373318c0ee6ca127832927c6529731f6d48e18dce7ca9/ruff-0.16.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:41e3870277694177429b56406d65dfbdb2c2802c52b715edaf6a0b829c69d4ee", size = 10269884, upload-time = "2026-09-24T20:37:38.857Z" },
{ url = "https://files.pythonhosted.org/packages/d1/56/c5d3cd119ded7a3c7aba0e961b69cb3df701c91662c98ad694467d060ce1/ruff-0.16.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8adbe4e58af167f767d7b2ba5e83c42e878350796cf78c2f5e14ab9903a92588", size = 10749366, upload-time = "2026-09-24T20:37:41.042Z" },
{ url = "https://files.pythonhosted.org/packages/ac/fe/734ec7527029ac757ecf821f143c9f3fcf69149c21f53a044a899b430f5a/ruff-0.16.9-py3-none-win32.whl", hash = "sha256:0e1dbc2073624dee6618d41d0098690a7244654af746704b64759e12b6b6b385", size = 10152355, upload-time = "2026-09-24T20:37:43.025Z" },
{ url = "https://files.pythonhosted.org/packages/14/21/26e4643629b3ebb44f0a06f9c9a53058d63d989415f63a9a3c28e2ee7f22/ruff-0.16.9-py3-none-win_amd64.whl", hash = "sha256:6bd40fec8cd4c8a3d4dd589bd8ad4e6320c13c29234159bfd959a40d529d597b", size = 10592965, upload-time = "2026-09-24T20:37:44.944Z" },
{ url = "https://files.pythonhosted.org/packages/51/60/5fb1a39dbb5ae314d5f59bc7348a63c1d5c20f3cd83914c4b5cb0be31d2d/ruff-0.16.9-py3-none-win_arm64.whl", hash = "sha256:ed1a252039200f57a59eebc063b54beabea67bfbaaca0eeaa7f54b5fbcda2284", size = 10458649, upload-time = "2026-09-24T20:37:46.882Z" },
]
[[package]]
@@ -897,27 +897,27 @@ wheels = [
[[package]]
name = "ty"
version = "0.0.94"
version = "0.0.84"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8a/c7/1257c8c70e46274664db1eaf2b8aba99a1277470daaa39c65e7decd67c8f/ty-0.0.94.tar.gz", hash = "sha256:933d2feca6057742d04af00c090621c139e7be2c059aa38b040a70532fd5104b", size = 8106769, upload-time = "2026-10-28T18:06:09.045Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e1/f6/34f8869c45ea87fe6a931ffa824b1fe4428167173543d92988d631add355/ty-0.0.84.tar.gz", hash = "sha256:0cefdb0cd5d399418dbe83c349ba605047b7dff815ae6f460c80e8522b40be67", size = 7409537, upload-time = "2026-09-24T13:16:24.367Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/27/bd/5145d91cf196cb20fc3b005515b21266a419ea7336d3d7891a1769439259/ty-0.0.94-py3-none-linux_armv6l.whl", hash = "sha256:c6463c045880ad17df911a53d367eb2554b17538ca54911bea6ccb20ec95ee11", size = 14528574, upload-time = "2026-10-28T18:05:19.423Z" },
{ url = "https://files.pythonhosted.org/packages/29/0e/2b563132429b5fc6044095061fde4584548cb55abf0d6d9f2829c41ab226/ty-0.0.94-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ec3a3892f4290306c674586149668097cf44f13e8b381ba388662f5a440b459b", size = 14067146, upload-time = "2026-10-28T18:05:23.014Z" },
{ url = "https://files.pythonhosted.org/packages/3d/e0/3a458362ea33ca9756cd6b914f905b3dec007d329b21d83e859ecee65a63/ty-0.0.94-py3-none-macosx_11_0_arm64.whl", hash = "sha256:74ddc6eeecd3251cf8980c9abb960d7eeab649dac5cdb7504910d99c479f84ab", size = 13148266, upload-time = "2026-10-28T18:05:25.769Z" },
{ url = "https://files.pythonhosted.org/packages/cd/38/5380bdaad2745a4e191deacce96403af74d1992ed8782d5766c301c32b50/ty-0.0.94-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31bdc3aace142400d7e22b4d32676c063ec42f0437d77940012c3dc60587ecdd", size = 13973583, upload-time = "2026-10-28T18:05:28.731Z" },
{ url = "https://files.pythonhosted.org/packages/1d/bc/7e1dc4f2594010aff6d1e2e34d0ff9003736210983be5720a04d69c27e03/ty-0.0.94-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fecacfe9c41ed1109aa503ed172d84b1b0fc283ee49aaf078d298dbe1c62e75f", size = 14269235, upload-time = "2026-10-28T18:05:31.697Z" },
{ url = "https://files.pythonhosted.org/packages/7b/61/43041f1c53164d6be21ace55047feffeaa8a9709782b01b06c951412b900/ty-0.0.94-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a49762bbc97a732847da8350ad0227c99ce2675fa32e3f58aa9f9ff4a81e4c14", size = 15191720, upload-time = "2026-10-28T18:05:34.455Z" },
{ url = "https://files.pythonhosted.org/packages/22/72/f425d48b13cbf12b92e4f837a5866e9788e9ef4d5a071e2b12f54ee94da4/ty-0.0.94-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76a7b3aea839dfafb4bc068af918e0292b06ac26ea2a43f7ab3dd65548c0ed50", size = 15596604, upload-time = "2026-10-28T18:05:37.377Z" },
{ url = "https://files.pythonhosted.org/packages/13/ea/bd2da666ad4af596886833b095098fa9ff93685285f48bebc4d3865126e0/ty-0.0.94-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f032ce5a9c0c7033a5a8e748f952ece2e22afe2b148d211d05dca2cba8b28000", size = 15572711, upload-time = "2026-10-28T18:05:40.499Z" },
{ url = "https://files.pythonhosted.org/packages/44/f8/4f07eac57e9b60f49cc222d5048d9a61ac7f0902964ec8ac8d4ea8bc521d/ty-0.0.94-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc5e780c2107c1b83a21a394076da328e05a293ce06298df40ecab1ddc31d371", size = 16309102, upload-time = "2026-10-28T18:05:43.539Z" },
{ url = "https://files.pythonhosted.org/packages/22/39/c2ac7e14eb8ca59bd3c71b8c3055294524df84c476ba88545f59c11ceaf9/ty-0.0.94-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5bc969cdabf85aa66987c9b4818072e3af17ce5f2b7bc93550ab896c904c6c40", size = 15073645, upload-time = "2026-10-28T18:05:46.65Z" },
{ url = "https://files.pythonhosted.org/packages/d1/7d/dd4935abf50a97ccc1f46cf6c921a7acdab30f102c3c56aa4e8d6cbc3fa9/ty-0.0.94-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:27bbaff0d7bbcb3e92ed7cab07d6b26dc7ed7fa28ff69910324e5cc347c7b256", size = 13936242, upload-time = "2026-10-28T18:05:49.726Z" },
{ url = "https://files.pythonhosted.org/packages/d6/f0/b65fa6ccde2d629e25031bf2cc2d21ac7b50ee6e0e3d29a5f471c4686c54/ty-0.0.94-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d0960eb71220300655d67db91073fd180f385186b1d186e76faa2fd3ae5bd10f", size = 14280351, upload-time = "2026-10-28T18:05:52.521Z" },
{ url = "https://files.pythonhosted.org/packages/d6/d2/5c901ad5e4f5444852b002b1f4dd3a0b470039305c688facc0483bfccd1a/ty-0.0.94-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab04c14bca92ab92106ceaa19a962a80998389700cf7938576ba3936a304355d", size = 14611163, upload-time = "2026-10-28T18:05:55.208Z" },
{ url = "https://files.pythonhosted.org/packages/6d/99/37d8633e9b5d1dcf1b06f12466825ff0c4028664994e573c352546ee9cbd/ty-0.0.94-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a92ebefa092e6bd58cb5719626858df744575473e2de0cd1c44abd44b9b84b57", size = 14938978, upload-time = "2026-10-28T18:05:58.17Z" },
{ url = "https://files.pythonhosted.org/packages/51/13/804b1f993bf84a819385f8b440fbc4b894499e2c20d865cd74d69327e7b8/ty-0.0.94-py3-none-win32.whl", hash = "sha256:3212dcf30b7567f2bb65a83cdfc4818a5385f82e7afddd83548b0d84e759057e", size = 13448009, upload-time = "2026-10-28T18:06:01.111Z" },
{ url = "https://files.pythonhosted.org/packages/a1/23/81850750cda0ea781ba9d6e08cea1a1039361cb3474c7dd03765bbc4b51f/ty-0.0.94-py3-none-win_amd64.whl", hash = "sha256:daf5bafe8e7d21b8a99a56fc8518f2342a2dbcaa370a5c25cfaedc684088d158", size = 14010576, upload-time = "2026-10-28T18:06:03.773Z" },
{ url = "https://files.pythonhosted.org/packages/d7/d4/ac3a759556493230823abd31e149c95401f96b9cac36567bd403a8454ea3/ty-0.0.94-py3-none-win_arm64.whl", hash = "sha256:ce344353ccdc542cfcfd6abe1481cf23c005f483411ea79652b78ce0beae36f2", size = 13547398, upload-time = "2026-10-28T18:06:06.379Z" },
{ url = "https://files.pythonhosted.org/packages/aa/0c/600648778e1c79133ce6b6a5c4c2531ae16b90c9b30f5041b38b574f61b8/ty-0.0.84-py3-none-linux_armv6l.whl", hash = "sha256:868438e2b9abfc835dd5b5b07ed693db95d8348ac40e3228252511d63ae87695", size = 13935333, upload-time = "2026-09-24T13:15:31.167Z" },
{ url = "https://files.pythonhosted.org/packages/c5/3b/27f06f49945c36170765fad82076898039ae3873338e9865f876dd62f12c/ty-0.0.84-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b00f39b521f0b559cde0f6abf8dd47213b513b04f33d58efeb9b98486a16bb17", size = 13511328, upload-time = "2026-09-24T13:15:34.446Z" },
{ url = "https://files.pythonhosted.org/packages/fc/b6/b12dd130e7f30141f0645614f454c925991cd389b1cc6c8284fd6bf32326/ty-0.0.84-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8f78c2915567f2ee62ce0c24d86091bc76f81577677755e242d09951db588b9a", size = 13445445, upload-time = "2026-09-24T13:15:37.476Z" },
{ url = "https://files.pythonhosted.org/packages/44/96/f012de4b3c1d9a4773326621380d768b4d48c47e620dc012775c5e01c7d7/ty-0.0.84-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61155cad0921e890d86d1d911a40b3a0e924ef4bdc3b4f3865a6c89c7e3012dc", size = 13471235, upload-time = "2026-09-24T13:15:40.64Z" },
{ url = "https://files.pythonhosted.org/packages/05/32/dca630f5f5353fa7bf67eebccf1549df99b16656d21071d0df6677c826a5/ty-0.0.84-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:417c3bf14947ed16b0b92976ab333c27915590354e96d4f581709068d3811355", size = 13680704, upload-time = "2026-09-24T13:15:43.817Z" },
{ url = "https://files.pythonhosted.org/packages/44/8d/33702c8f62f05ad45b33644fb914d483de6a0a7bc8e51884e9b8773e6d69/ty-0.0.84-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e06ef117759f654f5379efe9ae79bda8a186016c016f1023d05c34f8e15c1546", size = 14518923, upload-time = "2026-09-24T13:15:46.802Z" },
{ url = "https://files.pythonhosted.org/packages/c8/a1/da3246e6ef2ae0e842bca4058b25c54bee55ab058ffff4ee54e72517ec01/ty-0.0.84-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a05d1d53db8b64410ab38c6e32c5898510584869d1d5dd9c0605db81c9aef7df", size = 15027731, upload-time = "2026-09-24T13:15:49.755Z" },
{ url = "https://files.pythonhosted.org/packages/20/17/b0099098a560aeb4f9fde6c912f9a81622c3e44e6555e1fbe322b368ec31/ty-0.0.84-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b92400494ab855cfb1204ac1101e3f53b2d09a8e21d171758b2cf9acdab1809e", size = 14748260, upload-time = "2026-09-24T13:15:52.713Z" },
{ url = "https://files.pythonhosted.org/packages/60/d5/32cd67aedb271f006e716ad093806c96c5d7e2af86be8255469dbd3d76c8/ty-0.0.84-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af17eb391ae3027fed9be1780bea555c2b8a25ba68e184c627b4981b2c9eaab7", size = 14205858, upload-time = "2026-09-24T13:15:55.677Z" },
{ url = "https://files.pythonhosted.org/packages/43/c4/638ac62ca2e8eca1b92e3e16273085744fada728b993500819b5c258ca15/ty-0.0.84-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:44cf06af0e7ec78ea6d8ff33a3450fc19627756a4fba681f7410d4e3ef63047a", size = 14573196, upload-time = "2026-09-24T13:15:59.165Z" },
{ url = "https://files.pythonhosted.org/packages/a6/f6/9ee81c5d8921e5fd5f10889ae563c95cd3272b31df2ada528919ca1cef6b/ty-0.0.84-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c6e56b443ff9ba462e34bfbc1e582004c2719ed35f8f93a433744b0ba0afb2d9", size = 13480760, upload-time = "2026-09-24T13:16:02.313Z" },
{ url = "https://files.pythonhosted.org/packages/f6/83/fbe5d1177667ae16d84c3242846b143809c9cc09568b333e5a9b4d5908f6/ty-0.0.84-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b6562fcc48820c1030e2a4509ef2c14ef93043ca4860280074314ac3a825b95", size = 13696346, upload-time = "2026-09-24T13:16:05.391Z" },
{ url = "https://files.pythonhosted.org/packages/86/9a/1b4ac9ddc34cc0ab4624bc219825d66af52f1ad87f4778ad08ad03fa06d5/ty-0.0.84-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dee7421451efcbd70f03e95a8f2c8d9e5ec3d35edcd27b4095ee00ee062abfc4", size = 13959410, upload-time = "2026-09-24T13:16:08.661Z" },
{ url = "https://files.pythonhosted.org/packages/c9/04/70e289437a67dfd686056ab4fd91880261a8233033a6a95d29393741fb97/ty-0.0.84-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:31e96307cf621babe969307dd657783d672e2d8bdbd06493710da635bf63fa07", size = 14350294, upload-time = "2026-09-24T13:16:11.865Z" },
{ url = "https://files.pythonhosted.org/packages/9c/f4/ae7dbd118425c850981d23efb850f0d9d7f6b8d2b852bde2767434f668b4/ty-0.0.84-py3-none-win32.whl", hash = "sha256:4bdc85f91cbaae35825f2b17bb4ac5de0563367875cb0f42b2bf07a1bf7ff2ba", size = 13176439, upload-time = "2026-09-24T13:16:15.049Z" },
{ url = "https://files.pythonhosted.org/packages/29/ec/994d87252f065dda5a500bc8afc726d18f6582d81d6f4ff1feb8a267c800/ty-0.0.84-py3-none-win_amd64.whl", hash = "sha256:71cc9aa7a7c89d4e12c6a814e9c0c3310ad5bee44efedbd6f5a42d91e9ac0f0d", size = 13962879, upload-time = "2026-09-24T13:16:18.191Z" },
{ url = "https://files.pythonhosted.org/packages/6d/1c/e6e91852b72566b1ab9aca504712857dfb89324c12092a5bd61d42d34427/ty-0.0.84-py3-none-win_arm64.whl", hash = "sha256:6c9d3718c4a0d318bc9c92af9b2aa85c3294e7dff54cf1d69ad79568f56516da", size = 13656453, upload-time = "2026-09-24T13:16:21.51Z" },
]
[[package]]
+2 -2
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph-checkpoint-sqlite"
version = "3.1.3"
version = "3.1.2"
description = "Library with a SQLite implementation of LangGraph checkpoint saver."
authors = []
requires-python = ">=3.10"
@@ -12,7 +12,7 @@ readme = "README.md"
license = "MIT"
license-files = ['LICENSE']
dependencies = [
"langgraph-checkpoint>=4.3.1,<5.0.0",
"langgraph-checkpoint>=4.3.0,<5.0.0",
"aiosqlite>=0.20",
"sqlite-vec>=0.1.6",
]
+1 -1
View File
@@ -360,7 +360,7 @@ test = [
[[package]]
name = "langgraph-checkpoint-sqlite"
version = "3.1.3"
version = "3.1.2"
source = { editable = "." }
dependencies = [
{ name = "aiosqlite" },
+90 -205
View File
@@ -1,5 +1,5 @@
version = 1
revision = 5
revision = 3
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.15'",
@@ -178,15 +178,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" },
]
[[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"
@@ -221,40 +212,6 @@ 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 = "httpcore2"
version = "2.14.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.11'",
]
dependencies = [
{ name = "h11" },
{ name = "truststore" },
]
sdist = { url = "https://files.pythonhosted.org/packages/30/ff/b53af1b49bb2cb5fa97c92215614b173ca5c1a99ded80e2d52f15a94fa6d/httpcore2-2.14.0.tar.gz", hash = "sha256:4f57a1a8249fe6af589d935cfd456f1d13bbdcb0f0fc72af9142368fc94811d7", size = 69096, upload-time = "2026-10-18T16:29:21.898Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/02/06/a8d6a8f90d11b76d9086242b6bb0ee4b503473bfc8be34c2d5b067275c22/httpcore2-2.14.0-py3-none-any.whl", hash = "sha256:4c4f64f4b544afeb778c42c0ffb5941b29e671157e90ed3f6dc787c4bca90cd1", size = 84291, upload-time = "2026-10-18T16:29:19.743Z" },
]
[[package]]
name = "httpcore2"
version = "2.16.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.15'",
"python_full_version >= '3.12' and python_full_version < '3.15'",
"python_full_version == '3.11.*'",
]
dependencies = [
{ name = "h11" },
{ name = "truststore" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/61/afa9d6b7452f9eca73f73d45324eb0bb52f098fd5e6049bf6b8263be9b4e/httpcore2-2.16.0.tar.gz", hash = "sha256:c72cd0413563b17e1cd8354677dff887466f8ec2551679759e93126534428643", size = 69886, upload-time = "2026-10-21T07:09:45.31Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/af/c2/ce417c397afa5181f0e3c6ff01a4d01639414e1adef08730d97571297151/httpcore2-2.16.0-py3-none-any.whl", hash = "sha256:5c4358319f2602d984ad93182c9fee5160f22e9da7babdda870a5e7c4e2dc34f", size = 85070, upload-time = "2026-10-21T07:09:42.802Z" },
]
[[package]]
name = "httpx"
version = "0.28.1"
@@ -270,63 +227,13 @@ 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 = "httpx2"
version = "2.14.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.11'",
]
dependencies = [
{ name = "anyio", marker = "sys_platform != 'emscripten'" },
{ name = "httpcore2", version = "2.14.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'emscripten'" },
{ name = "idna" },
{ name = "truststore", marker = "sys_platform != 'emscripten'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e8/67/58353f1848b4ba568e1ec22ea1e0d8e039f0bca5fde7e2d1f07739fd83e7/httpx2-2.14.0.tar.gz", hash = "sha256:47ad61a3c40282cd8d0dc96311531f00f5bfc71a066c6fe3b36d8f63fb9e4f6e", size = 102036, upload-time = "2026-10-18T16:29:22.627Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/69/5a/469616aa8c2cbb0bb9dd67a3c18d07c01ab604cc0b236ee1c47d260c7f6e/httpx2-2.14.0-py3-none-any.whl", hash = "sha256:9d20c13a10bae1773a0be3b0f1e5beeb4bdd549bc3a2ba18efa32897c65403e4", size = 96901, upload-time = "2026-10-18T16:29:20.819Z" },
]
[[package]]
name = "httpx2"
version = "2.16.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.15'",
"python_full_version >= '3.12' and python_full_version < '3.15'",
"python_full_version == '3.11.*'",
]
dependencies = [
{ name = "anyio", marker = "sys_platform != 'emscripten'" },
{ name = "httpcore2", version = "2.16.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'emscripten'" },
{ name = "httpx2-jsfetch", marker = "python_full_version >= '3.12' and sys_platform == 'emscripten'" },
{ name = "idna" },
{ name = "truststore", marker = "sys_platform != 'emscripten'" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/43/73/5ca2cedf5e4e50423d1c258877865a6b62793633b561c58499f8aeb33b7e/httpx2-2.16.0.tar.gz", hash = "sha256:0d54c98128e8f2ff7fbbde0fca8c359e5f032edeaaaac443cdb7f31f685bcd7f", size = 103116, upload-time = "2026-10-21T07:09:46.348Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7b/3d/2e4bc4fd1ef9770871669adb348d612a6e4786b303a23f9935b81c48dd60/httpx2-2.16.0-py3-none-any.whl", hash = "sha256:fd2c5d7909e8d86fe92d53020c310ca0e4e431b838c207c5754b9bbf0308d49a", size = 97880, upload-time = "2026-10-21T07:09:44.01Z" },
]
[[package]]
name = "httpx2-jsfetch"
version = "1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" },
]
[[package]]
name = "idna"
version = "3.20"
version = "3.15"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" }
sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" },
{ url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" },
]
[[package]]
@@ -361,7 +268,7 @@ wheels = [
[[package]]
name = "langchain-core"
version = "1.7.1"
version = "1.6.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -375,21 +282,21 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "uuid-utils" },
]
sdist = { url = "https://files.pythonhosted.org/packages/96/ec/55c77c3d53c65fbf58d41bd78567ed34b5c0e9dcacd00e4568a9067314ab/langchain_core-1.7.1.tar.gz", hash = "sha256:5dc2a357e1aa40e84c7f29755182121b3acf04410e55fa3723915f645033fede", size = 1026919, upload-time = "2026-10-28T14:59:47.314Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ed/ed/ceecd3bfcfc77a94cf30a9f1506e907fdea447458825e01914a72d2d1a6f/langchain_core-1.6.5.tar.gz", hash = "sha256:bdb059fef65473fd444558dbb18f50901657402c43ceed9dc0fdc7b76a393c6b", size = 1007041, upload-time = "2026-09-24T18:11:06.487Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f3/87/50fa7f000065502ce76ec6b701a13adec62c32171785d5bebb0345219a0f/langchain_core-1.7.1-py3-none-any.whl", hash = "sha256:2d668788945e824832e19bca05788a25dbb864197d63ec48b583057cf8d975fa", size = 575541, upload-time = "2026-10-28T14:59:45.887Z" },
{ url = "https://files.pythonhosted.org/packages/59/68/447ac7e734990b7ed9dcfc3b6dee9a4cd4d4169e296429fa71a9a47c1c7b/langchain_core-1.6.5-py3-none-any.whl", hash = "sha256:54c7b0e9314b9084fb04405bb33dc5d32986d512d92b7b8863899cd5f6267556", size = 572136, upload-time = "2026-09-24T18:11:04.837Z" },
]
[[package]]
name = "langchain-protocol"
version = "0.0.20"
version = "0.0.18"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/cb/8b/d908d082c3cf5368ddd1816ed114de79c873f979ba7f98b95fab0e841643/langchain_protocol-0.0.20.tar.gz", hash = "sha256:0f5bd002474265648e91161771a9e0438fe9480bd55a416e51af0f60070637ec", size = 6280, upload-time = "2026-10-20T11:13:50.84Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d2/59/b5959aea96faa9146e2e49a7a22882b3528c62efafe9a6a95beab30c2305/langchain_protocol-0.0.18.tar.gz", hash = "sha256:ec3e11782f1ed0c9db38e5a9ed01b0e7a0d3fba406faa8aef6594b73c56a63e6", size = 6150, upload-time = "2026-06-18T17:08:26.959Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/02/4b/f1c99a0b27c5ff8b4b131c0d6e62012c17cc424089efe213767db927afd2/langchain_protocol-0.0.20-py3-none-any.whl", hash = "sha256:3c935e5e46b0f6f9cb52f45ba7cfa53d1b9fa80c82c4a277d47f03e07f7385b1", size = 7347, upload-time = "2026-10-20T11:13:49.566Z" },
{ url = "https://files.pythonhosted.org/packages/99/2e/d82db9eec13ad0f72e7aaad5c4bc730ab111934fdc83c85523206eb9b0a0/langchain_protocol-0.0.18-py3-none-any.whl", hash = "sha256:70b53a86fbf9cedc863555effe44da192ab02d556ddbf2cf95b8873adcf41b5a", size = 7221, upload-time = "2026-06-18T17:08:25.996Z" },
]
[[package]]
@@ -477,28 +384,23 @@ test = [
[[package]]
name = "langsmith"
version = "0.14.16"
version = "0.8.18"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "distro" },
{ name = "httpx2", version = "2.14.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "httpx2", version = "2.16.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "httpx" },
{ name = "orjson", marker = "platform_python_implementation != 'PyPy'" },
{ name = "packaging" },
{ name = "pydantic" },
{ name = "requests" },
{ name = "requests-toolbelt" },
{ name = "sniffio" },
{ name = "typing-extensions" },
{ name = "uuid-utils" },
{ name = "websockets" },
{ name = "xxhash" },
{ name = "zstandard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f5/45/576803a72da96d9c0866b3815f08f447ed16c14acb52174129daca6847d6/langsmith-0.14.16.tar.gz", hash = "sha256:4e5816cbe1541f4d04d71f652eb8f4137889861bebcf7137bcba3553ae9e335e", size = 5096529, upload-time = "2026-10-29T17:53:27.747Z" }
sdist = { url = "https://files.pythonhosted.org/packages/9a/d9/a6681aa9847bbbc5ec21abe20a5e233b94e5edcfe39624db607ac7e8ccb4/langsmith-0.8.18.tar.gz", hash = "sha256:32dde9c0e67e053e0fb738921fc8ced768af7b8fa83d7a0e3fd63597cf8776dd", size = 4526988, upload-time = "2026-06-19T13:12:17.123Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/11/3a/9a072c02a22089ddf20f0f33ea95bee5a5951b31839f118b04f27b94be6e/langsmith-0.14.16-py3-none-any.whl", hash = "sha256:b9ea1a57622b0788b181d78605f974fb206c5f6f75793076ffec23cef4d904c8", size = 933680, upload-time = "2026-10-29T17:53:25.465Z" },
{ url = "https://files.pythonhosted.org/packages/03/70/0e0cc80a3b064c8d6c8d697c3125ed86e39d5a7393ec6dc8b07cb1cf13c4/langsmith-0.8.18-py3-none-any.whl", hash = "sha256:3940183349993faef48e6c7d08e4822ee9cefd906b362d0e3c2d650314d2f282", size = 508108, upload-time = "2026-06-19T13:12:15.348Z" },
]
[[package]]
@@ -904,36 +806,37 @@ wheels = [
[[package]]
name = "pycryptodome"
version = "4.2.0"
version = "3.23.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/33/c7/f9374c8939560ab22db1dd9dbe215b62d20f32634aeb7f259dc03c807b44/pycryptodome-4.2.0.tar.gz", hash = "sha256:21d7fdba239d6ef863d3d08388801c683453a3bb4897c2b27f27847aad89bdf7", size = 1750332, upload-time = "2026-10-24T10:24:42.016Z" }
sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/05/61028b65913438a091ed3a2ec55bb50843e674b6ae14bccf196bbed652a2/pycryptodome-4.2.0-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:4b58c4fd5615a788e0df6793d578e565b19d75a348c0ba02d469b0c38bbf0332", size = 1684620, upload-time = "2026-10-24T10:23:50.473Z" },
{ url = "https://files.pythonhosted.org/packages/cd/73/0930fd56dd5eb856ecc33ec48845def52a1d25a9cd6b810a4c3ef3633cf4/pycryptodome-4.2.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:42ab554efcc14928f1db42deac9c84989fd49e289a16aacbbee709fe2eb838cc", size = 1520423, upload-time = "2026-10-24T10:23:52.505Z" },
{ url = "https://files.pythonhosted.org/packages/19/ba/4b931de42881053a6d818b9ee70f6dde3dfe1cfcf78527d45db3f99d8b47/pycryptodome-4.2.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b4ed1a34d2d667ec165beb15313a759bcd8e704e2743f9942491c5a88a2d6ca5", size = 2296340, upload-time = "2026-10-24T10:23:54.294Z" },
{ url = "https://files.pythonhosted.org/packages/d4/63/68fc2a01d621c2ff389ea261e731e8f1abe6aba89992681bbcdb19fcca43/pycryptodome-4.2.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1482a7d9ceed7beadbb0853f83d4d44406a9f9fa8ee596d637618f095869e14", size = 2987589, upload-time = "2026-10-24T10:23:56.547Z" },
{ url = "https://files.pythonhosted.org/packages/f5/63/69e211459f4625c89bb47a7ba4527a6cc3276c9bbd79ae871a2d0b05c021/pycryptodome-4.2.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:dceadb6f478b0a687d129f1b58220e0587fb17a31b2502ae7a1a4234bc304ee1", size = 2322482, upload-time = "2026-10-24T10:23:58.538Z" },
{ url = "https://files.pythonhosted.org/packages/4a/65/21a4ed3ae5af153c57861d7f987c81fca5bf02d49b76f34c545f8eb6b95f/pycryptodome-4.2.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:8217736d5ac9b7fef49d1d2b292d5af4e8300298ef4926742aaec49b2bf6505b", size = 3548352, upload-time = "2026-10-24T10:24:01.185Z" },
{ url = "https://files.pythonhosted.org/packages/4a/a4/9a7e4e8896cfd8131978d31e39c930fb114f6a63751f39d2069b965dad45/pycryptodome-4.2.0-cp315-cp315t-win32.whl", hash = "sha256:b717e36cf14cffe463ff4e7d423a0c9fd6f83b40d207388e0f40dd0a3d4018bc", size = 1306386, upload-time = "2026-10-24T10:24:03.047Z" },
{ url = "https://files.pythonhosted.org/packages/af/6c/e1a78019b7db83e2cfc1c98cea4a7a0afbb6b233514b9dfee2bad7443bd9/pycryptodome-4.2.0-cp315-cp315t-win_amd64.whl", hash = "sha256:585f0a792cdc7fb381ca0d1fac4e20f03fd99bb78cf2733eaab59f62c77ccf88", size = 1688319, upload-time = "2026-10-24T10:24:04.963Z" },
{ url = "https://files.pythonhosted.org/packages/c6/96/7cb051ba325141b33bc7e6ef6152499cf74b74606c5bb4c8c72411e8afdc/pycryptodome-4.2.0-cp315-cp315t-win_arm64.whl", hash = "sha256:831ae8ce070e990d4fcb913c49c7fb0181ea7ab2691fc58dcb9d41fdb5384d85", size = 1330914, upload-time = "2026-10-24T10:24:06.934Z" },
{ url = "https://files.pythonhosted.org/packages/41/4c/954a9a1451f7bcc89d66db39bf6499ef5701e8ff35b6c898c659a7ec8546/pycryptodome-4.2.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:3e430168f767ecec60c1f850df4abbd0b9f94786b9cc8041a00e976f6d5190c6", size = 1680663, upload-time = "2026-10-24T10:24:08.982Z" },
{ url = "https://files.pythonhosted.org/packages/6e/84/23f8013dce07cbc8c8b9ed878972294230a1edc49a6b64d271c77c878c29/pycryptodome-4.2.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aea22120791b4534a27e39fc01d91265a249ba72dbd682a8a5c1adee475d5483", size = 1513323, upload-time = "2026-10-24T10:24:11.076Z" },
{ url = "https://files.pythonhosted.org/packages/1a/82/693940a8e4a20c3db91050480429daf39c818be3c7ec59f992ded4567ce6/pycryptodome-4.2.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f6f30a457bd83a3074747d11bcb95375397a2e9b9a6b9179ab001e0cdd8ed0c5", size = 2292885, upload-time = "2026-10-24T10:24:13.486Z" },
{ url = "https://files.pythonhosted.org/packages/18/88/33df6b1176b63aed427745696d88ed38f7877104cf0f6004ee9ad600f897/pycryptodome-4.2.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:76777686de3a354c41710727988c2284448d5db9bd82e5ddb9a9b6710d17d9df", size = 2976488, upload-time = "2026-10-24T10:24:16.139Z" },
{ url = "https://files.pythonhosted.org/packages/b1/c6/c1c137d6e3284409ca5b2cfd7db3621167c316f9c314a96274af69a66fbb/pycryptodome-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c5bc41a418451616cd0ec3d765ba8539416d7429d7cd633ffb603a5a22355e70", size = 2313324, upload-time = "2026-10-24T10:24:18.302Z" },
{ url = "https://files.pythonhosted.org/packages/ef/26/b7752cffc8200040835ad24dc17c9de6376ac27612c8bf943766cb1b3c36/pycryptodome-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2c5b1e9058687daca3aaaff23eeb1c1853c8251986f0de7a950235e8bc0cc8b7", size = 3537773, upload-time = "2026-10-24T10:24:20.482Z" },
{ url = "https://files.pythonhosted.org/packages/df/1b/34bec1416507cda23b5dfa33c0bca936fd4ac8ed4d3269803477b897649d/pycryptodome-4.2.0-cp39-abi3-win32.whl", hash = "sha256:bb7659f87d7aa69105e1be12764f9cf1908287eb424b242186f5ac3c192ed612", size = 1292974, upload-time = "2026-10-24T10:24:22.422Z" },
{ url = "https://files.pythonhosted.org/packages/03/5b/a4a31e6e1aabd5a26da77c7960e7fcce4fca9132e51868f1bba10bf1e932/pycryptodome-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:3282c9fd744a16594e51e1a9fbfe4d658b6b0aaa1f050926e6d591ac7494f78b", size = 1658684, upload-time = "2026-10-24T10:24:24.009Z" },
{ url = "https://files.pythonhosted.org/packages/2b/81/bddb0637fa85a03d00eea2f3514c5aa8d7b9c4921ef5e0c125b4ce662a8c/pycryptodome-4.2.0-cp39-abi3-win_arm64.whl", hash = "sha256:34ec11258967efec61d92a83a63a050ab7a7c31dc3806412f6334b8aef47e2d9", size = 1305591, upload-time = "2026-10-24T10:24:25.993Z" },
{ url = "https://files.pythonhosted.org/packages/df/a1/5460bbb750121f71abacaaf25f0cb1df67be7fe13c70cf6d7c2b8ad54fc0/pycryptodome-4.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7ea7dbf6b8ce0f0d2c0e17c1226c74d07c01499db7704797bdbb6cb9ec511ccb", size = 1472158, upload-time = "2026-10-24T10:24:27.92Z" },
{ url = "https://files.pythonhosted.org/packages/e2/65/27ac8fb15f0ffb70b72a0413291637de46d29b075804221703273c3ca6d1/pycryptodome-4.2.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0106af8684e1d73c93bc39a2f1cb4ae2f2636d1f80e6291caddcc37b4efc5dc1", size = 1247107, upload-time = "2026-10-24T10:24:29.672Z" },
{ url = "https://files.pythonhosted.org/packages/f0/7d/6b3ed6cd8626007f6a6b13616b08150bc0bb936a0b84c310e59558ed5136/pycryptodome-4.2.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4deb46e0d0a540b1f047cb6f430249692da15c21edd75f5e6278314089fb859", size = 1426092, upload-time = "2026-10-24T10:24:31.627Z" },
{ url = "https://files.pythonhosted.org/packages/73/45/5adf4dbb1eab1da6291d25f777d1d34f745c7f2993bf2d4d3b0d9abb87c6/pycryptodome-4.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e094873fe87ae4d546ef393cec7ea1e1cf8e0684f649612b19352afa67eb8bdb", size = 1662257, upload-time = "2026-10-24T10:24:33.373Z" },
{ url = "https://files.pythonhosted.org/packages/57/88/fef93a9ecbda783a312b15a8823fa1a9a4ec3474685e756584af595457aa/pycryptodome-4.2.0-pp311-pypy311_pp80-macosx_10_15_x86_64.whl", hash = "sha256:30f418cd1f4e776bde6c5df1fcd90ddfe74dfc8f679286a82f17ec1d4018cce0", size = 1467319, upload-time = "2026-10-24T10:24:35.108Z" },
{ url = "https://files.pythonhosted.org/packages/78/65/cb53934c9f8d5c41dfcfaddb0d98103f4dd6670af1ffdbc2582bcdd99382/pycryptodome-4.2.0-pp311-pypy311_pp80-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20cb4a489f7a5150a882901884b84ee0df77a47b9a6eb6e2dd8e98997b7b826f", size = 1247105, upload-time = "2026-10-24T10:24:36.859Z" },
{ url = "https://files.pythonhosted.org/packages/11/38/5bba7fd24beb6bc7a82fdd155f2df39f51c8ad16143fc99d3a1ccc7c76b4/pycryptodome-4.2.0-pp311-pypy311_pp80-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f104dc1a38aa1172a166ebe3716f2666f785bfa015b7ca13bb808c12c902e455", size = 1426092, upload-time = "2026-10-24T10:24:38.584Z" },
{ url = "https://files.pythonhosted.org/packages/38/cf/e70a8cb1ab184b59b2d02dfd403965a0c6419571d8520463944bcb0ca8a0/pycryptodome-4.2.0-pp311-pypy311_pp80-win_amd64.whl", hash = "sha256:a7c6857077255f5f4e6c34e6325802ba1bb59960133c0a5e584d9371dbdf3dea", size = 1662214, upload-time = "2026-10-24T10:24:40.139Z" },
{ url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" },
{ url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" },
{ url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" },
{ url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" },
{ url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" },
{ url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" },
{ url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" },
{ url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" },
{ url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" },
{ url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" },
{ url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" },
{ url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" },
{ url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" },
{ url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" },
{ url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" },
{ url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" },
{ url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" },
{ url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" },
{ url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" },
{ url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" },
{ url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" },
{ url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" },
{ url = "https://files.pythonhosted.org/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379", size = 1623886, upload-time = "2025-05-17T17:21:20.614Z" },
{ url = "https://files.pythonhosted.org/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4", size = 1672151, upload-time = "2025-05-17T17:21:22.666Z" },
{ url = "https://files.pythonhosted.org/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630", size = 1664461, upload-time = "2025-05-17T17:21:25.225Z" },
{ url = "https://files.pythonhosted.org/packages/d6/92/608fbdad566ebe499297a86aae5f2a5263818ceeecd16733006f1600403c/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353", size = 1702440, upload-time = "2025-05-17T17:21:27.991Z" },
{ url = "https://files.pythonhosted.org/packages/d1/92/2eadd1341abd2989cce2e2740b4423608ee2014acb8110438244ee97d7ff/pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5", size = 1803005, upload-time = "2025-05-17T17:21:31.37Z" },
]
[[package]]
@@ -1080,7 +983,7 @@ wheels = [
[[package]]
name = "pytest"
version = "9.2.0"
version = "9.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
@@ -1091,35 +994,35 @@ dependencies = [
{ name = "pygments" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/79/42/bb4641652e58dedeac2ec51abc1f8cae4fee4ec63899a0999016229a0f2a/pytest-9.2.0.tar.gz", hash = "sha256:97c4f3b1f3c7414aa36465e09b8220f1d03627993bd51b1282e193753c80557b", size = 1797555, upload-time = "2026-10-27T21:30:20.728Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/51/39/1f5d8c8877a0fa327db959c19341d4be61660de1ab6b7391f9eb805bcd67/pytest-9.2.0-py3-none-any.whl", hash = "sha256:07acdcf88ec174f907dc01af70bf52d49b014bbab03faeeade31c96b18c2e588", size = 411776, upload-time = "2026-10-27T21:30:19.131Z" },
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
]
[[package]]
name = "pytest-asyncio"
version = "1.4.1"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" },
{ name = "pytest" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f8/9a/887535e477847f5159377ab35303923644840b13c0ca03c0a467f84d4680/pytest_asyncio-1.4.1.tar.gz", hash = "sha256:e8266eb094e5ee9ffdb2068c9836f33e209ea77a970eb846933d416b5602669b", size = 145237, upload-time = "2026-10-25T21:05:37.135Z" }
sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/46/d7/cd9e2708afdbf059658adaf08bdb0792ca52489871051d6ce7d498b51277/pytest_asyncio-1.4.1-py3-none-any.whl", hash = "sha256:507afa78ec77c6661164ef1a2368bc7820383fd1a14b2a7f813de8b5a92949cc", size = 17061, upload-time = "2026-10-25T21:05:35.473Z" },
{ url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" },
]
[[package]]
name = "pytest-mock"
version = "3.17.0"
version = "3.16.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/97/c2/28b9f5b955ff2bcd4727a6f768e2ca9b586cd7c37b95ea5f7b9b873ac7b3/pytest_mock-3.17.0.tar.gz", hash = "sha256:e36576e0985e01027e999891e7a966c67822d40c89ef1b4c8b6e22737e3590b4", size = 43064, upload-time = "2026-10-17T16:21:42.435Z" }
sdist = { url = "https://files.pythonhosted.org/packages/7a/7f/6ed29931d5c8cd396e7c0a55412e6cc88020373365c8685985dea53d26d7/pytest_mock-3.16.0.tar.gz", hash = "sha256:5a8395528b8f498205f3718f575228d0edaed7425fff638f87d1a6c3e0383636", size = 35362, upload-time = "2026-09-27T14:57:55.46Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e8/ea/47fbdc8f37410956fd445720e927589b1e2a47772c5f28109f0ce2fa26d9/pytest_mock-3.17.0-py3-none-any.whl", hash = "sha256:1b82aa4b37137248bd687055e109723debeb07b489de1565297dc9589c5f2d6b", size = 11248, upload-time = "2026-10-17T16:21:41.265Z" },
{ url = "https://files.pythonhosted.org/packages/db/5b/b83a9bf1a3b4ec222f9fa083147ff6816245223da0ab92370e7e056f113f/pytest_mock-3.16.0-py3-none-any.whl", hash = "sha256:007cfeb257801d88d9c0b2a7b5a15a15e73b71968dfd72e7bf8c4a2f8393aec8", size = 10016, upload-time = "2026-09-27T14:57:54.283Z" },
]
[[package]]
@@ -1261,27 +1164,27 @@ wheels = [
[[package]]
name = "ruff"
version = "0.17.3"
version = "0.16.9"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/59/73/81f140c4edd9cc323de5478e6bbd3bbcfae9a82692df1a258de8cec6a6d2/ruff-0.17.3.tar.gz", hash = "sha256:a57049c8addb3662073859bb55a7ad35aa54f8a7c9ed268911d1333c5aadaaa9", size = 5339444, upload-time = "2026-10-27T00:34:05.914Z" }
sdist = { url = "https://files.pythonhosted.org/packages/96/bf/c935ca98e73fe8ce65b87ef08a280c0c1e85295d569228c15e87d8fdfaf1/ruff-0.16.9.tar.gz", hash = "sha256:12b625c6cfba78d285d9f48eda5f053374f1e53cb10ef17342a383750db99161", size = 4948764, upload-time = "2026-09-24T20:37:49.416Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9e/8c/083365310c4e79c842f25bf5a842facbd9b0c0ec336f96dffa5074fe723d/ruff-0.17.3-py3-none-linux_armv6l.whl", hash = "sha256:ab86b84cd9d05e56d329742c0cabdcbe2d1d56c98f4a04aab9b5b66196febfb4", size = 10216522, upload-time = "2026-10-27T00:33:29.822Z" },
{ url = "https://files.pythonhosted.org/packages/72/26/4b2edf31cfebe9605d8f6ed8f1d6f376d3a53d4c9a123e62661c64c440f0/ruff-0.17.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e407b4a6852853a9a2cdce96963e94ad9775578bb44e7561a0f2829f3cc04b11", size = 10217786, upload-time = "2026-10-27T00:33:33.949Z" },
{ url = "https://files.pythonhosted.org/packages/a2/7e/537aba4b7f54397a374d2f6a6fc93e9c8015e18553f31a4a7b7dbced48b5/ruff-0.17.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b75cc113b3aca388933d8296b667b1ac401a80317ea3074c9b0a66541199da29", size = 9440362, upload-time = "2026-10-27T00:33:35.784Z" },
{ url = "https://files.pythonhosted.org/packages/3d/66/283e39734f66c8a5ebaa93c22eb7b7515779a0b1d5a5c0ad20e05c0edb41/ruff-0.17.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab5fa7ed5ce18554ea69d850ad162cbaa0c85fc47c08d83a569f806681c9a6ad", size = 10094281, upload-time = "2026-10-27T00:33:38.216Z" },
{ url = "https://files.pythonhosted.org/packages/21/4c/1ffc768497bd025a32c72152771e40c0b99bddc5cc6e4f50c7949a395346/ruff-0.17.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c673c170e27834d1262e3a9c1e97bdc9b9c3b180c63d89173ce69747232474c1", size = 10022543, upload-time = "2026-10-27T00:33:40.181Z" },
{ url = "https://files.pythonhosted.org/packages/73/c3/f90db547301080fcb82c92abf914c8bd57b89911d9b65a6c4cc6b52d0635/ruff-0.17.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a754c0341bc9ca151b7b335988b3263921c08859f84ca219b70cee825571b6f", size = 10683921, upload-time = "2026-10-27T00:33:42.182Z" },
{ url = "https://files.pythonhosted.org/packages/e5/98/92d4a8a26c6c96cf1a5b34dd59036d8a71e52d650aab065c58867e964e63/ruff-0.17.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4590b9d86c0b1543ebb09094254e191cd43fe8edaf330baa035aab03da251532", size = 11373408, upload-time = "2026-10-27T00:33:44.19Z" },
{ url = "https://files.pythonhosted.org/packages/43/02/1f24d2e3064c9753926deeb901afa57777b37ee46799b09ddd7aee6e5145/ruff-0.17.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbde18beccd98c29a227c63c99022e3443b99258e08326df6425dfe0443705ef", size = 10977469, upload-time = "2026-10-27T00:33:46.179Z" },
{ url = "https://files.pythonhosted.org/packages/f0/20/fecedea2209f574ab92033b344e01a7f0c2dc76c6d6d19a14c9f8141173f/ruff-0.17.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fa4b0f931cdbcb8a52044f370a3eb429fb42eb88807b0e800fcaa98dd42b24e", size = 10577491, upload-time = "2026-10-27T00:33:48.233Z" },
{ url = "https://files.pythonhosted.org/packages/33/36/b93fdf0bc6f84f0cd487aadfb0354deb40013669ea35ac3d26855de1e959/ruff-0.17.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:39405f341deec934b4289634324ad695f72df2970f9121582872eaf5b722f0f5", size = 10608748, upload-time = "2026-10-27T00:33:50.284Z" },
{ url = "https://files.pythonhosted.org/packages/5d/b7/7d3f5f14145daead1efb0b9a8289ed1daa100f419d60774375951723ce00/ruff-0.17.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68074094f55ca3d29ab19491f640282f7a84061c4c7977aa5094b968ca457e9a", size = 10075057, upload-time = "2026-10-27T00:33:52.074Z" },
{ url = "https://files.pythonhosted.org/packages/b0/ed/93ff7a5909ee56e2bd4b982deae9e0efb2724417874a90c9e41ad66c6dd7/ruff-0.17.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:27bb571ac78fef178fe5118d2827df95c2aae74a48fff2b15f6acd03af5ff5bb", size = 10022431, upload-time = "2026-10-27T00:33:54.061Z" },
{ url = "https://files.pythonhosted.org/packages/9b/07/a83b9eeec8c1c3c0e4eda15f06c3a91001bfd2687ea2f07b750719bf9712/ruff-0.17.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:029c37cbd3de48ca37aa12117bead2d065f15da17d66bf84e0d4274013705db2", size = 10394903, upload-time = "2026-10-27T00:33:56.158Z" },
{ url = "https://files.pythonhosted.org/packages/af/b2/05c39857c2526fcbebe46ad93a8dc97424656dfef14d3ef933abe1ffd331/ruff-0.17.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:692fdb46d0fd9c71e380947dc5fc72f2c3f881073bc1f739e4579376231affbe", size = 10799780, upload-time = "2026-10-27T00:33:58.204Z" },
{ url = "https://files.pythonhosted.org/packages/ec/d9/47171a16739eb7a353936b31c20cf54340531e68c9af7d72fd7a784e235d/ruff-0.17.3-py3-none-win32.whl", hash = "sha256:46539cb031b01fd29ae0a02142483c46640b233ee12bb1c1b690bffa1555d5ca", size = 9908481, upload-time = "2026-10-27T00:34:00.087Z" },
{ url = "https://files.pythonhosted.org/packages/d0/a0/f08c1631bfd81faa6a90eaf70c37c786c11bed2046302b56bb506c25e1a0/ruff-0.17.3-py3-none-win_amd64.whl", hash = "sha256:56b8ffb3f3b3cbccb8b76b71b38261b7ed982ef5d20f5b447b1b61a7f4949b24", size = 10341333, upload-time = "2026-10-27T00:34:02.099Z" },
{ url = "https://files.pythonhosted.org/packages/99/d5/4ad4ed31413c301430918e70d3d38ae6f097af0766cb9ab8c9d502b2a4d6/ruff-0.17.3-py3-none-win_arm64.whl", hash = "sha256:81fce74bcd6be0e9da7c67c7553fd0f1f12964a2052cd728ef917b937a5ca1d9", size = 9854332, upload-time = "2026-10-27T00:34:03.847Z" },
{ url = "https://files.pythonhosted.org/packages/0d/26/df51322b52ee1ada7eff2d071ea09d11d5c2d1dcc9f02594f5785c4d1635/ruff-0.16.9-py3-none-linux_armv6l.whl", hash = "sha256:95e6f022090368ab3b824c36276839c53b2adf1a3f4c09fefc33dfc400f6da96", size = 10082922, upload-time = "2026-09-24T20:37:13.045Z" },
{ url = "https://files.pythonhosted.org/packages/a5/27/7bf51f5a7aa375e9f339280a303aab44ca75dc1525f1cdc5991761685b0f/ruff-0.16.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a5f27be168556594a86d2f415db0cf43f5291917849318f873c7e2791f7a8c67", size = 10236360, upload-time = "2026-09-24T20:37:16.049Z" },
{ url = "https://files.pythonhosted.org/packages/b6/63/09659283f92f02dff45809da194a70da2f688d87c55d8875c4fae3536072/ruff-0.16.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1632eb1d6197f33bd00b1acbc5b71009e89a8895c158e2d2b03a834fac964ab6", size = 9892940, upload-time = "2026-09-24T20:37:17.957Z" },
{ url = "https://files.pythonhosted.org/packages/24/58/98de1b72ec172f5f8f1731236fe21585b3998bf7dfe9fcc44ae9ba626012/ruff-0.16.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3f951b14d865d5952c89d40a5ca07e87abe24fa5453299878411e127748fb1c", size = 10032114, upload-time = "2026-09-24T20:37:19.942Z" },
{ url = "https://files.pythonhosted.org/packages/c8/7d/f1e17c54ab59d4bad1dce8ee3e22a7a1d0ef4745240decacdcf3832b5bb2/ruff-0.16.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:447fc07e1573afff7cb02803462b12b6c8ece7cf10e2cd78565fa6d7a1c0bf8d", size = 9910227, upload-time = "2026-09-24T20:37:21.872Z" },
{ url = "https://files.pythonhosted.org/packages/34/19/436f647a65075bbd3bab2668b3bdaa5120559b294694018cdcefabbbf30b/ruff-0.16.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a3e039a6a40ed976c491722b60e0ae4a4aa1a86057f540ee7a37a5d19ae9120", size = 10547484, upload-time = "2026-09-24T20:37:24.229Z" },
{ url = "https://files.pythonhosted.org/packages/03/59/38430a6bc2f6d8095447ac39625cf8b6e9344a47e6c26225c8ba1bff3ffb/ruff-0.16.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4684dded7db60aa57cb118fa158630f5feade4af5782903b6053484bdf9bd129", size = 11412367, upload-time = "2026-09-24T20:37:26.307Z" },
{ url = "https://files.pythonhosted.org/packages/c8/bd/bbb6d7fc7f208c8b8c50dd5dc8206e4cfdb1e7a8fb852606a3adf370c880/ruff-0.16.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d29c934357e45642fda2f34c0b1f4025b4a6c01e15e4bf0016879d60078a142c", size = 10869787, upload-time = "2026-09-24T20:37:28.35Z" },
{ url = "https://files.pythonhosted.org/packages/bc/b8/9c543074918061abbefc3bd139bee22de35abedb00dde0f2d27288838962/ruff-0.16.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a21713e629d3e5bdb2f5c2def1cc7f04f47fa8e1a7eb0571b4a28e1da64bc728", size = 10406494, upload-time = "2026-09-24T20:37:30.624Z" },
{ url = "https://files.pythonhosted.org/packages/35/7a/5a8851bd146e7ccf8fd4b003f6c75c11f8fbdb0e60673b45097986b6bf41/ruff-0.16.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7baa24ef5fc8e77aa93879e1d3f43754a01ae488e869f1ae30cf431afd4d2452", size = 10590083, upload-time = "2026-09-24T20:37:32.439Z" },
{ url = "https://files.pythonhosted.org/packages/87/f0/4c3467188f23f806960b46fa76575a7cd0514c9ba90562650b71efc96980/ruff-0.16.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a41aac6230aadfaa133bdfa1614488531ffa3e0837567ae04c0da2058a9c0f9e", size = 10119151, upload-time = "2026-09-24T20:37:34.581Z" },
{ url = "https://files.pythonhosted.org/packages/15/34/5a4def5adea572ce6aea0bb64f21f928ee317b80e0db747d7979b01d7261/ruff-0.16.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c2529fb5896d49115b0e9aa8f887490b34bbe76baf879ec2264ac59406869ce7", size = 9911544, upload-time = "2026-09-24T20:37:36.796Z" },
{ url = "https://files.pythonhosted.org/packages/62/5d/d15ebea7499eef9373318c0ee6ca127832927c6529731f6d48e18dce7ca9/ruff-0.16.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:41e3870277694177429b56406d65dfbdb2c2802c52b715edaf6a0b829c69d4ee", size = 10269884, upload-time = "2026-09-24T20:37:38.857Z" },
{ url = "https://files.pythonhosted.org/packages/d1/56/c5d3cd119ded7a3c7aba0e961b69cb3df701c91662c98ad694467d060ce1/ruff-0.16.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8adbe4e58af167f767d7b2ba5e83c42e878350796cf78c2f5e14ab9903a92588", size = 10749366, upload-time = "2026-09-24T20:37:41.042Z" },
{ url = "https://files.pythonhosted.org/packages/ac/fe/734ec7527029ac757ecf821f143c9f3fcf69149c21f53a044a899b430f5a/ruff-0.16.9-py3-none-win32.whl", hash = "sha256:0e1dbc2073624dee6618d41d0098690a7244654af746704b64759e12b6b6b385", size = 10152355, upload-time = "2026-09-24T20:37:43.025Z" },
{ url = "https://files.pythonhosted.org/packages/14/21/26e4643629b3ebb44f0a06f9c9a53058d63d989415f63a9a3c28e2ee7f22/ruff-0.16.9-py3-none-win_amd64.whl", hash = "sha256:6bd40fec8cd4c8a3d4dd589bd8ad4e6320c13c29234159bfd959a40d529d597b", size = 10592965, upload-time = "2026-09-24T20:37:44.944Z" },
{ url = "https://files.pythonhosted.org/packages/51/60/5fb1a39dbb5ae314d5f59bc7348a63c1d5c20f3cd83914c4b5cb0be31d2d/ruff-0.16.9-py3-none-win_arm64.whl", hash = "sha256:ed1a252039200f57a59eebc063b54beabea67bfbaaca0eeaa7f54b5fbcda2284", size = 10458649, upload-time = "2026-09-24T20:37:46.882Z" },
]
[[package]]
@@ -1293,15 +1196,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
[[package]]
name = "tenacity"
version = "9.1.2"
@@ -1365,38 +1259,29 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" },
]
[[package]]
name = "truststore"
version = "0.10.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/9f/c5201d42a484c061e528825fc8e2d565f5abd50a4ced6fb7d29c4ec99b2b/truststore-0.10.5.tar.gz", hash = "sha256:30d36967ccaded5cbb38d602c433f53600036c79d502f4533a49b60a03bbefcd", size = 28091, upload-time = "2026-10-12T22:27:31.808Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/51/e9/3a7820be2bb0fe53b6bc9c3be26d3d1158004e4c3ab953aa6840b955b1e9/truststore-0.10.5-py3-none-any.whl", hash = "sha256:9aaaedaefaf06d8b206278cf8b5012bc897f485a874503501e12d776df78951c", size = 19017, upload-time = "2026-10-12T22:27:30.377Z" },
]
[[package]]
name = "ty"
version = "0.0.94"
version = "0.0.84"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8a/c7/1257c8c70e46274664db1eaf2b8aba99a1277470daaa39c65e7decd67c8f/ty-0.0.94.tar.gz", hash = "sha256:933d2feca6057742d04af00c090621c139e7be2c059aa38b040a70532fd5104b", size = 8106769, upload-time = "2026-10-28T18:06:09.045Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e1/f6/34f8869c45ea87fe6a931ffa824b1fe4428167173543d92988d631add355/ty-0.0.84.tar.gz", hash = "sha256:0cefdb0cd5d399418dbe83c349ba605047b7dff815ae6f460c80e8522b40be67", size = 7409537, upload-time = "2026-09-24T13:16:24.367Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/27/bd/5145d91cf196cb20fc3b005515b21266a419ea7336d3d7891a1769439259/ty-0.0.94-py3-none-linux_armv6l.whl", hash = "sha256:c6463c045880ad17df911a53d367eb2554b17538ca54911bea6ccb20ec95ee11", size = 14528574, upload-time = "2026-10-28T18:05:19.423Z" },
{ url = "https://files.pythonhosted.org/packages/29/0e/2b563132429b5fc6044095061fde4584548cb55abf0d6d9f2829c41ab226/ty-0.0.94-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ec3a3892f4290306c674586149668097cf44f13e8b381ba388662f5a440b459b", size = 14067146, upload-time = "2026-10-28T18:05:23.014Z" },
{ url = "https://files.pythonhosted.org/packages/3d/e0/3a458362ea33ca9756cd6b914f905b3dec007d329b21d83e859ecee65a63/ty-0.0.94-py3-none-macosx_11_0_arm64.whl", hash = "sha256:74ddc6eeecd3251cf8980c9abb960d7eeab649dac5cdb7504910d99c479f84ab", size = 13148266, upload-time = "2026-10-28T18:05:25.769Z" },
{ url = "https://files.pythonhosted.org/packages/cd/38/5380bdaad2745a4e191deacce96403af74d1992ed8782d5766c301c32b50/ty-0.0.94-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31bdc3aace142400d7e22b4d32676c063ec42f0437d77940012c3dc60587ecdd", size = 13973583, upload-time = "2026-10-28T18:05:28.731Z" },
{ url = "https://files.pythonhosted.org/packages/1d/bc/7e1dc4f2594010aff6d1e2e34d0ff9003736210983be5720a04d69c27e03/ty-0.0.94-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fecacfe9c41ed1109aa503ed172d84b1b0fc283ee49aaf078d298dbe1c62e75f", size = 14269235, upload-time = "2026-10-28T18:05:31.697Z" },
{ url = "https://files.pythonhosted.org/packages/7b/61/43041f1c53164d6be21ace55047feffeaa8a9709782b01b06c951412b900/ty-0.0.94-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a49762bbc97a732847da8350ad0227c99ce2675fa32e3f58aa9f9ff4a81e4c14", size = 15191720, upload-time = "2026-10-28T18:05:34.455Z" },
{ url = "https://files.pythonhosted.org/packages/22/72/f425d48b13cbf12b92e4f837a5866e9788e9ef4d5a071e2b12f54ee94da4/ty-0.0.94-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76a7b3aea839dfafb4bc068af918e0292b06ac26ea2a43f7ab3dd65548c0ed50", size = 15596604, upload-time = "2026-10-28T18:05:37.377Z" },
{ url = "https://files.pythonhosted.org/packages/13/ea/bd2da666ad4af596886833b095098fa9ff93685285f48bebc4d3865126e0/ty-0.0.94-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f032ce5a9c0c7033a5a8e748f952ece2e22afe2b148d211d05dca2cba8b28000", size = 15572711, upload-time = "2026-10-28T18:05:40.499Z" },
{ url = "https://files.pythonhosted.org/packages/44/f8/4f07eac57e9b60f49cc222d5048d9a61ac7f0902964ec8ac8d4ea8bc521d/ty-0.0.94-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc5e780c2107c1b83a21a394076da328e05a293ce06298df40ecab1ddc31d371", size = 16309102, upload-time = "2026-10-28T18:05:43.539Z" },
{ url = "https://files.pythonhosted.org/packages/22/39/c2ac7e14eb8ca59bd3c71b8c3055294524df84c476ba88545f59c11ceaf9/ty-0.0.94-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5bc969cdabf85aa66987c9b4818072e3af17ce5f2b7bc93550ab896c904c6c40", size = 15073645, upload-time = "2026-10-28T18:05:46.65Z" },
{ url = "https://files.pythonhosted.org/packages/d1/7d/dd4935abf50a97ccc1f46cf6c921a7acdab30f102c3c56aa4e8d6cbc3fa9/ty-0.0.94-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:27bbaff0d7bbcb3e92ed7cab07d6b26dc7ed7fa28ff69910324e5cc347c7b256", size = 13936242, upload-time = "2026-10-28T18:05:49.726Z" },
{ url = "https://files.pythonhosted.org/packages/d6/f0/b65fa6ccde2d629e25031bf2cc2d21ac7b50ee6e0e3d29a5f471c4686c54/ty-0.0.94-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d0960eb71220300655d67db91073fd180f385186b1d186e76faa2fd3ae5bd10f", size = 14280351, upload-time = "2026-10-28T18:05:52.521Z" },
{ url = "https://files.pythonhosted.org/packages/d6/d2/5c901ad5e4f5444852b002b1f4dd3a0b470039305c688facc0483bfccd1a/ty-0.0.94-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab04c14bca92ab92106ceaa19a962a80998389700cf7938576ba3936a304355d", size = 14611163, upload-time = "2026-10-28T18:05:55.208Z" },
{ url = "https://files.pythonhosted.org/packages/6d/99/37d8633e9b5d1dcf1b06f12466825ff0c4028664994e573c352546ee9cbd/ty-0.0.94-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a92ebefa092e6bd58cb5719626858df744575473e2de0cd1c44abd44b9b84b57", size = 14938978, upload-time = "2026-10-28T18:05:58.17Z" },
{ url = "https://files.pythonhosted.org/packages/51/13/804b1f993bf84a819385f8b440fbc4b894499e2c20d865cd74d69327e7b8/ty-0.0.94-py3-none-win32.whl", hash = "sha256:3212dcf30b7567f2bb65a83cdfc4818a5385f82e7afddd83548b0d84e759057e", size = 13448009, upload-time = "2026-10-28T18:06:01.111Z" },
{ url = "https://files.pythonhosted.org/packages/a1/23/81850750cda0ea781ba9d6e08cea1a1039361cb3474c7dd03765bbc4b51f/ty-0.0.94-py3-none-win_amd64.whl", hash = "sha256:daf5bafe8e7d21b8a99a56fc8518f2342a2dbcaa370a5c25cfaedc684088d158", size = 14010576, upload-time = "2026-10-28T18:06:03.773Z" },
{ url = "https://files.pythonhosted.org/packages/d7/d4/ac3a759556493230823abd31e149c95401f96b9cac36567bd403a8454ea3/ty-0.0.94-py3-none-win_arm64.whl", hash = "sha256:ce344353ccdc542cfcfd6abe1481cf23c005f483411ea79652b78ce0beae36f2", size = 13547398, upload-time = "2026-10-28T18:06:06.379Z" },
{ url = "https://files.pythonhosted.org/packages/aa/0c/600648778e1c79133ce6b6a5c4c2531ae16b90c9b30f5041b38b574f61b8/ty-0.0.84-py3-none-linux_armv6l.whl", hash = "sha256:868438e2b9abfc835dd5b5b07ed693db95d8348ac40e3228252511d63ae87695", size = 13935333, upload-time = "2026-09-24T13:15:31.167Z" },
{ url = "https://files.pythonhosted.org/packages/c5/3b/27f06f49945c36170765fad82076898039ae3873338e9865f876dd62f12c/ty-0.0.84-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b00f39b521f0b559cde0f6abf8dd47213b513b04f33d58efeb9b98486a16bb17", size = 13511328, upload-time = "2026-09-24T13:15:34.446Z" },
{ url = "https://files.pythonhosted.org/packages/fc/b6/b12dd130e7f30141f0645614f454c925991cd389b1cc6c8284fd6bf32326/ty-0.0.84-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8f78c2915567f2ee62ce0c24d86091bc76f81577677755e242d09951db588b9a", size = 13445445, upload-time = "2026-09-24T13:15:37.476Z" },
{ url = "https://files.pythonhosted.org/packages/44/96/f012de4b3c1d9a4773326621380d768b4d48c47e620dc012775c5e01c7d7/ty-0.0.84-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61155cad0921e890d86d1d911a40b3a0e924ef4bdc3b4f3865a6c89c7e3012dc", size = 13471235, upload-time = "2026-09-24T13:15:40.64Z" },
{ url = "https://files.pythonhosted.org/packages/05/32/dca630f5f5353fa7bf67eebccf1549df99b16656d21071d0df6677c826a5/ty-0.0.84-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:417c3bf14947ed16b0b92976ab333c27915590354e96d4f581709068d3811355", size = 13680704, upload-time = "2026-09-24T13:15:43.817Z" },
{ url = "https://files.pythonhosted.org/packages/44/8d/33702c8f62f05ad45b33644fb914d483de6a0a7bc8e51884e9b8773e6d69/ty-0.0.84-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e06ef117759f654f5379efe9ae79bda8a186016c016f1023d05c34f8e15c1546", size = 14518923, upload-time = "2026-09-24T13:15:46.802Z" },
{ url = "https://files.pythonhosted.org/packages/c8/a1/da3246e6ef2ae0e842bca4058b25c54bee55ab058ffff4ee54e72517ec01/ty-0.0.84-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a05d1d53db8b64410ab38c6e32c5898510584869d1d5dd9c0605db81c9aef7df", size = 15027731, upload-time = "2026-09-24T13:15:49.755Z" },
{ url = "https://files.pythonhosted.org/packages/20/17/b0099098a560aeb4f9fde6c912f9a81622c3e44e6555e1fbe322b368ec31/ty-0.0.84-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b92400494ab855cfb1204ac1101e3f53b2d09a8e21d171758b2cf9acdab1809e", size = 14748260, upload-time = "2026-09-24T13:15:52.713Z" },
{ url = "https://files.pythonhosted.org/packages/60/d5/32cd67aedb271f006e716ad093806c96c5d7e2af86be8255469dbd3d76c8/ty-0.0.84-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af17eb391ae3027fed9be1780bea555c2b8a25ba68e184c627b4981b2c9eaab7", size = 14205858, upload-time = "2026-09-24T13:15:55.677Z" },
{ url = "https://files.pythonhosted.org/packages/43/c4/638ac62ca2e8eca1b92e3e16273085744fada728b993500819b5c258ca15/ty-0.0.84-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:44cf06af0e7ec78ea6d8ff33a3450fc19627756a4fba681f7410d4e3ef63047a", size = 14573196, upload-time = "2026-09-24T13:15:59.165Z" },
{ url = "https://files.pythonhosted.org/packages/a6/f6/9ee81c5d8921e5fd5f10889ae563c95cd3272b31df2ada528919ca1cef6b/ty-0.0.84-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c6e56b443ff9ba462e34bfbc1e582004c2719ed35f8f93a433744b0ba0afb2d9", size = 13480760, upload-time = "2026-09-24T13:16:02.313Z" },
{ url = "https://files.pythonhosted.org/packages/f6/83/fbe5d1177667ae16d84c3242846b143809c9cc09568b333e5a9b4d5908f6/ty-0.0.84-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b6562fcc48820c1030e2a4509ef2c14ef93043ca4860280074314ac3a825b95", size = 13696346, upload-time = "2026-09-24T13:16:05.391Z" },
{ url = "https://files.pythonhosted.org/packages/86/9a/1b4ac9ddc34cc0ab4624bc219825d66af52f1ad87f4778ad08ad03fa06d5/ty-0.0.84-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dee7421451efcbd70f03e95a8f2c8d9e5ec3d35edcd27b4095ee00ee062abfc4", size = 13959410, upload-time = "2026-09-24T13:16:08.661Z" },
{ url = "https://files.pythonhosted.org/packages/c9/04/70e289437a67dfd686056ab4fd91880261a8233033a6a95d29393741fb97/ty-0.0.84-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:31e96307cf621babe969307dd657783d672e2d8bdbd06493710da635bf63fa07", size = 14350294, upload-time = "2026-09-24T13:16:11.865Z" },
{ url = "https://files.pythonhosted.org/packages/9c/f4/ae7dbd118425c850981d23efb850f0d9d7f6b8d2b852bde2767434f668b4/ty-0.0.84-py3-none-win32.whl", hash = "sha256:4bdc85f91cbaae35825f2b17bb4ac5de0563367875cb0f42b2bf07a1bf7ff2ba", size = 13176439, upload-time = "2026-09-24T13:16:15.049Z" },
{ url = "https://files.pythonhosted.org/packages/29/ec/994d87252f065dda5a500bc8afc726d18f6582d81d6f4ff1feb8a267c800/ty-0.0.84-py3-none-win_amd64.whl", hash = "sha256:71cc9aa7a7c89d4e12c6a814e9c0c3310ad5bee44efedbd6f5a42d91e9ac0f0d", size = 13962879, upload-time = "2026-09-24T13:16:18.191Z" },
{ url = "https://files.pythonhosted.org/packages/6d/1c/e6e91852b72566b1ab9aca504712857dfb89324c12092a5bd61d42d34427/ty-0.0.84-py3-none-win_arm64.whl", hash = "sha256:6c9d3718c4a0d318bc9c92af9b2aa85c3294e7dff54cf1d69ad79568f56516da", size = 13656453, upload-time = "2026-09-24T13:16:21.51Z" },
]
[[package]]
@@ -5,5 +5,5 @@ description = "Test for prerelease stuff"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"langgraph==1.2.19"
"langgraph==1.2.6"
]
@@ -7,7 +7,7 @@ requires-python = ">=3.10"
dependencies = [
"langchain-openai==1.1.14",
"langchain-anthropic==1.4.6",
"langgraph==1.2.19"
"langgraph==1.2.6"
]
[tool.uv]
+9 -9
View File
@@ -21,8 +21,8 @@
"test:all": "yarn test && yarn test:int && yarn lint:langgraph"
},
"dependencies": {
"@langchain/core": "^1.3.1",
"@langchain/langgraph": "^1.5.3"
"@langchain/core": "^1.2.13",
"@langchain/langgraph": "^1.4.18"
},
"resolutions": {
"@langchain/langgraph-checkpoint": "1.0.4",
@@ -34,17 +34,17 @@
"@eslint/js": "^10.0.1",
"@tsconfig/recommended": "^1.0.13",
"@types/jest": "^30.0.0",
"@typescript-eslint/eslint-plugin": "^8.72.1",
"@typescript-eslint/parser": "^8.72.1",
"dotenv": "^18.0.9",
"eslint": "^10.13.0",
"@typescript-eslint/eslint-plugin": "^8.70.1",
"@typescript-eslint/parser": "^8.70.1",
"dotenv": "^18.0.4",
"eslint": "^10.11.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-no-instanceof": "^1.0.1",
"eslint-plugin-prettier": "^5.5.7",
"eslint-plugin-prettier": "^5.5.6",
"jest": "^30.5.2",
"prettier": "^3.9.10",
"ts-jest": "^29.4.15",
"prettier": "^3.9.9",
"ts-jest": "^29.4.14",
"typescript": "^7.0.2"
}
}
+123 -126
View File
@@ -387,11 +387,6 @@
resolved "https://registry.yarnpkg.com/@cfworker/json-schema/-/json-schema-4.1.1.tgz#4a2a3947ee9fa7b7c24be981422831b8674c3be6"
integrity sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==
"@dual-bundle/import-meta-resolve@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.2.1.tgz#cd0b25b3808cd9e684cd6cd549bbf8e1dcf05ee7"
integrity sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==
"@emnapi/core@1.10.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.10.0.tgz#380ccc8f2412ea22d1d972df7f8ee23a3b9c7467"
@@ -895,50 +890,50 @@
resolved "https://registry.yarnpkg.com/@keyv/serialize/-/serialize-1.1.1.tgz#0c01dd3a3483882af7cf3878d4e71d505c81fc4a"
integrity sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==
"@langchain/core@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-1.3.1.tgz#f6cdb6d200def5c2e69d25ff4fef92612d1b7601"
integrity sha512-jt0miXWxNi+rbZLeSeZkc7BBYKnWopksoJSpB+P3zaYgzMMNwkAPWqnd1zSClfYGwLZPXQbbTtaqv8ZFXmE3FA==
"@langchain/core@^1.2.13":
version "1.2.13"
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-1.2.13.tgz#4ea4eb0308a25af350f75de2fbf34aecb819177e"
integrity sha512-ADGTxZ84n3civruUX1LlTOdua/PDGoni2U6TmKTX7hvRU2Jlepu8vLJI4Wnwj45KUKhUCrW94Il12Q2bxE3e8A==
dependencies:
"@cfworker/json-schema" "^4.0.2"
"@standard-schema/spec" "^1.1.0"
js-tiktoken "^1.0.12"
langsmith ">=0.11.0 <1.0.0"
langsmith ">=0.5.0 <1.0.0"
mustache "^4.2.0"
p-queue "^6.6.2"
zod "^3.25.76 || ^4"
"@langchain/langgraph-checkpoint@1.0.4", "@langchain/langgraph-checkpoint@^1.2.1":
"@langchain/langgraph-checkpoint@1.0.4", "@langchain/langgraph-checkpoint@^1.1.5":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-1.0.4.tgz#893aa7255b2fe810fdeb50281f6750a3b0a6fef7"
integrity sha512-1y5MgZ0gXXrtmoy56e3kaBChI3GwFPIKl27xkrHwN+VE/3iUsyr9gO3Jtp7kdKAe6diZGbcas5bdC/r0yUwTZA==
dependencies:
uuid "^14.0.0"
"@langchain/langgraph-sdk@~1.12.8":
version "1.12.9"
resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-1.12.9.tgz#b8e66e00af207f18e56eb6cd916c1ee8e7608745"
integrity sha512-A1LkLPVu00dj+9aErXPBLhmFURHtnnRO9KRFST+xAwurqQTa+LxC9ixxF7p+ZMo5P70qMFoaP8Ud2wuZmYD4xw==
"@langchain/langgraph-sdk@~1.12.0":
version "1.12.0"
resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-1.12.0.tgz#b063a7eba0a66f51cbc841a824626a47eca1eec9"
integrity sha512-F3AOZjKZRUGmE3FzY+RaVZI6WzXOkwnuF7jqgto6rDPSnO9OdVdYGvwGDi3jjRGf5xLoDtX2dpsR9z5KptU+5A==
dependencies:
"@langchain/protocol" "^0.0.20"
"@langchain/protocol" "^0.0.19"
"@types/json-schema" "^7.0.15"
p-queue "^9.0.1"
p-retry "^7.1.1"
"@langchain/langgraph@^1.5.3":
version "1.5.3"
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-1.5.3.tgz#9a921c9e099c2e3009f34987e0acb2645636bb74"
integrity sha512-cuqswNgfoF/J3/r9K5Qm7EnQc2f0mz7uWlAAs751KWhoK8sKfmuLABghF4b1LYq1NpK72XG1x8lWPyg45M8BOA==
"@langchain/langgraph@^1.4.18":
version "1.4.18"
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-1.4.18.tgz#c8cc4ea734834262fdb00ae6a45a0849620e4fd2"
integrity sha512-yrMMJ9hk2NVMD2xU2WoVrgFawAU6s/RzEl/dX9BhlyxZHazo1wHY35z4K2BIBzTUh4z3giCzrUoqRAqW2CWpWg==
dependencies:
"@langchain/langgraph-checkpoint" "^1.2.1"
"@langchain/langgraph-sdk" "~1.12.8"
"@langchain/protocol" "^0.0.20"
"@langchain/langgraph-checkpoint" "^1.1.5"
"@langchain/langgraph-sdk" "~1.12.0"
"@langchain/protocol" "^0.0.19"
"@standard-schema/spec" "1.1.0"
"@langchain/protocol@^0.0.20":
version "0.0.20"
resolved "https://registry.yarnpkg.com/@langchain/protocol/-/protocol-0.0.20.tgz#441a57a6176f634306d9214da65d35033355d5e9"
integrity sha512-sxlkDG2/HGyjDFLTJ8B/o3oglRjOv8iLCFgrV0550/Vym3V6RZ0SUjAQFe8jrLQw8ZXjERy3swJp5OODBSrUYA==
"@langchain/protocol@^0.0.19":
version "0.0.19"
resolved "https://registry.yarnpkg.com/@langchain/protocol/-/protocol-0.0.19.tgz#7fe43fed115dc34b3b4c246e8d5283fbcbfab7b0"
integrity sha512-9hKcRrH7cBX6gfutdfXPoft1OCchHe4FEpALoDJMl5Qu+n/YG5ynZmyu8+8cxORlPwHBoKTxggvXz+76M1yX1Q==
"@napi-rs/wasm-runtime@^1.1.4":
version "1.2.3"
@@ -1185,100 +1180,110 @@
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.72.1.tgz#95e29de63ce7ed793eb1c6efd9f8b802ce808262"
integrity sha512-5CokTWXpmbylfFabsbvHC0CAw3XLaGnoa+nFJX5FR592h/LoZsENUE6p/rg5xvkKrFEXz6pYmgI29AgXc2N3BQ==
"@typescript-eslint/eslint-plugin@^8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.70.1.tgz#d404bac4cb04e4dcd20086e46e13c952bd0771e0"
integrity sha512-nDNrUQ/4ruSNYbu749TRY7cfrzPtoLHEXSNBI8aaNY32LlZCajixqRf3FqcKC4p5Cam4VOHYx/t+i5+nKXvrqA==
dependencies:
"@eslint-community/regexpp" "^4.12.2"
"@typescript-eslint/scope-manager" "8.72.1"
"@typescript-eslint/type-utils" "8.72.1"
"@typescript-eslint/utils" "8.72.1"
"@typescript-eslint/visitor-keys" "8.72.1"
"@typescript-eslint/scope-manager" "8.70.1"
"@typescript-eslint/type-utils" "8.70.1"
"@typescript-eslint/utils" "8.70.1"
"@typescript-eslint/visitor-keys" "8.70.1"
ignore "^7.0.5"
natural-compare "^1.4.0"
ts-api-utils "^2.5.0"
"@typescript-eslint/parser@^8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.72.1.tgz#7370b68dadc3819b9aac68ea340bd8b43ee62379"
integrity sha512-ZGwZP4OCfyA0AQTpEAMWz+rtX01DRhQWW5hB0ucKDEIX5U7jdmPSaMx94rQfaPAQzIYz2BimOgKOJtjfpRFQ9g==
"@typescript-eslint/parser@^8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.70.1.tgz#6c99b7b954efc05dae179001c8b477d1a8948dcf"
integrity sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw==
dependencies:
"@typescript-eslint/scope-manager" "8.72.1"
"@typescript-eslint/types" "8.72.1"
"@typescript-eslint/typescript-estree" "8.72.1"
"@typescript-eslint/visitor-keys" "8.72.1"
"@typescript-eslint/scope-manager" "8.70.1"
"@typescript-eslint/types" "8.70.1"
"@typescript-eslint/typescript-estree" "8.70.1"
"@typescript-eslint/visitor-keys" "8.70.1"
debug "^4.4.3"
"@typescript-eslint/project-service@8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.72.1.tgz#1d09902a9c23f6cfab779e5acc78a45abf540add"
integrity sha512-fAuVMkwdO/huI8nyo/WKnNKf6Gag93S56Rh3Ts5mGcavwSdaN8tRg1gCeIGX8PF+nzp1BjGfO4gHhGzW1J6jTg==
"@typescript-eslint/project-service@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.70.1.tgz#d5635594cc8a573f0e15a772b1704e93a00134c1"
integrity sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.72.1"
"@typescript-eslint/types" "^8.72.1"
"@typescript-eslint/tsconfig-utils" "^8.70.1"
"@typescript-eslint/types" "^8.70.1"
debug "^4.4.3"
"@typescript-eslint/scope-manager@8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.72.1.tgz#a0fb4dfe5aa2b0149b7a2d1fd00d947992b13067"
integrity sha512-lC/t6eMCZZQAAKZEYGL6RDVSuFehpdmMT6h+nJIjIa6oylN/PwDgDVtEIbB7O9xFfZYtZAYLoLlDNWKJhtF+uQ==
"@typescript-eslint/scope-manager@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.70.1.tgz#0feeae4da2d279b960cdbfe5e0a02fbf1195e93f"
integrity sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==
dependencies:
"@typescript-eslint/types" "8.72.1"
"@typescript-eslint/visitor-keys" "8.72.1"
"@typescript-eslint/types" "8.70.1"
"@typescript-eslint/visitor-keys" "8.70.1"
"@typescript-eslint/tsconfig-utils@8.72.1", "@typescript-eslint/tsconfig-utils@^8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.1.tgz#07394c85095ee38455e76bc221ba23650af73e44"
integrity sha512-e4D5R5vpeAEPBxT2BAzsFQL3sbwyeeeo5+aczgNSRK1V734jE7pTqaGFU+fXapWEQwnZbpFaTn32+Z8DwS6OTg==
"@typescript-eslint/tsconfig-utils@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.1.tgz#e9353a6e5f3498a29b40946ca3e11f6166fef09e"
integrity sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==
"@typescript-eslint/type-utils@8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.72.1.tgz#f1f592f63bd9726de21191aae3ccf64a813914b8"
integrity sha512-iY2wlmyrXJmx4yxUGP3aYCFF3cA/6ShddOKmLU2dx7LnEykbAM8WThlxZEfd1NddqceF6eJmZUayxx9oKIYNlg==
"@typescript-eslint/tsconfig-utils@^8.70.1":
version "8.71.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.0.tgz#83a69c9a514af8f5ff099cc6c0d609c273e8f039"
integrity sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==
"@typescript-eslint/type-utils@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.70.1.tgz#6ea36f2c7dc0776c1c8e1c380b4896f00bc4c27f"
integrity sha512-7zKTnyvaVWqzLZHPFQtX1hVHqgkMC+WebPWakNCSyrQVbIP1AM0L0TlBZtACldIRb6PptI8Odk+jyZ5kP3B1VA==
dependencies:
"@typescript-eslint/types" "8.72.1"
"@typescript-eslint/typescript-estree" "8.72.1"
"@typescript-eslint/utils" "8.72.1"
"@typescript-eslint/types" "8.70.1"
"@typescript-eslint/typescript-estree" "8.70.1"
"@typescript-eslint/utils" "8.70.1"
debug "^4.4.3"
ts-api-utils "^2.5.0"
"@typescript-eslint/types@8.72.1", "@typescript-eslint/types@^8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.72.1.tgz#067d8f738c294611e33a1425ae43ac9b9c2f24e3"
integrity sha512-LmE5IhQet1BWOWEXqBrVtBPsMAOQ6Nzk35d+WLh3mk4EUF33sqlfxjio1MdYeSvKSG5skactKKuixBIKXhL4Rw==
"@typescript-eslint/types@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.70.1.tgz#2022b9d07d057174eba25b9bbd289641d262a196"
integrity sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==
"@typescript-eslint/typescript-estree@8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.1.tgz#e93b9cc3a56bf12f5fed857bb8f4963cce7924ee"
integrity sha512-DAlUoUV93hIvrtIQLwCbmZQYVZwOMhiHJrOVUUIWicFZaIYjTRqlY3I/NZevfkLmSqkNHRu2qYDcq9lrn16oOQ==
"@typescript-eslint/types@^8.70.1":
version "8.71.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.71.0.tgz#a230a121e71665060afe27d1f1ad110371f9451e"
integrity sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==
"@typescript-eslint/typescript-estree@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.1.tgz#ced48fee6f7043835d2edfed9786ae74813a3c99"
integrity sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==
dependencies:
"@typescript-eslint/project-service" "8.72.1"
"@typescript-eslint/tsconfig-utils" "8.72.1"
"@typescript-eslint/types" "8.72.1"
"@typescript-eslint/visitor-keys" "8.72.1"
"@typescript-eslint/project-service" "8.70.1"
"@typescript-eslint/tsconfig-utils" "8.70.1"
"@typescript-eslint/types" "8.70.1"
"@typescript-eslint/visitor-keys" "8.70.1"
debug "^4.4.3"
minimatch "^10.2.2"
semver "^7.7.3"
tinyglobby "^0.2.15"
ts-api-utils "^2.5.0"
"@typescript-eslint/utils@8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.72.1.tgz#f210c1bf4a25aa1306df80c702ed30bbbc9b4d3d"
integrity sha512-5v5liypurJBFHCBNqh/RjsymAD+VWcE0djuGY/BUqp5fC3Qn+luWDEksjuTtWjPn6i5zQlUtkZCRG/CYG0yCkA==
"@typescript-eslint/utils@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.70.1.tgz#6a97887cee702df513692f5cdc7f79baeef48e02"
integrity sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==
dependencies:
"@eslint-community/eslint-utils" "^4.9.1"
"@typescript-eslint/scope-manager" "8.72.1"
"@typescript-eslint/types" "8.72.1"
"@typescript-eslint/typescript-estree" "8.72.1"
"@typescript-eslint/scope-manager" "8.70.1"
"@typescript-eslint/types" "8.70.1"
"@typescript-eslint/typescript-estree" "8.70.1"
"@typescript-eslint/visitor-keys@8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.1.tgz#c7aec9883b44ef795f1b8200df81d9d44d355de7"
integrity sha512-SB8xqipSzZHUePCCLNeYxqnxq83X61/qzNhjQGbTfjxGZIeOEP7tBgWYX05Tcd6CgE02WdTJnh5b8kMZJ/+bnQ==
"@typescript-eslint/visitor-keys@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.1.tgz#b4cde0ed16982bd963a6310fcae554f59fd14124"
integrity sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==
dependencies:
"@typescript-eslint/types" "8.72.1"
"@typescript-eslint/types" "8.70.1"
eslint-visitor-keys "^5.0.0"
"@typescript/typescript-aix-ppc64@7.0.2":
@@ -2104,10 +2109,10 @@ doctrine@^2.1.0:
dependencies:
esutils "^2.0.2"
dotenv@^18.0.9:
version "18.0.9"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-18.0.9.tgz#5c565760ce7e6fb69f9477ebcd1a3b64c766f9c8"
integrity sha512-3fVOW4cB02R066PUSwn8O4TJyz9NckQl7F81blIbaq9jKBm2tSmOKky7ZHIXzGvkVPXCSn6jkpziBPZdGFs8Tw==
dotenv@^18.0.4:
version "18.0.4"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-18.0.4.tgz#419bbe784f30915bfeb10e2d407e13ca5528504f"
integrity sha512-jGt9uI5BIxpVO15kvy+iEFqNAwxYClKWnkptLjSNPnFLC003Imd0ofr3aEVsbpHdSBdSsbcApNIlz4nbyGMwdA==
dunder-proto@^1.0.0, dunder-proto@^1.0.1:
version "1.0.1"
@@ -2425,13 +2430,13 @@ eslint-plugin-no-instanceof@^1.0.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-no-instanceof/-/eslint-plugin-no-instanceof-1.0.1.tgz#5d9fc86d160df6991b654b294a62390207f1bb97"
integrity sha512-zlqQ7EsfzbRO68uI+p8FIE7zYB4njs+nNbkNjSb5QmLi2et67zQLqSeaao5U9SpnlZTTJC87nS2oyHo2ACtajw==
eslint-plugin-prettier@^5.5.7:
version "5.5.7"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.7.tgz#ebef3acc2d664333b1356b717780198abc635398"
integrity sha512-WjHyn3GrFd+ZR0rzsLiM4zJZ35PZMOE0SUIxzGmnWNG0126yNfJKJzObmmkYDwHJfGlkz3PSlzyG9ez/LJntwg==
eslint-plugin-prettier@^5.5.6:
version "5.5.6"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.6.tgz#363ebe4d769bce157ccdd8129ce3efd91dc62564"
integrity sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==
dependencies:
prettier-linter-helpers "^1.0.1"
synckit "^0.12.3"
synckit "^0.11.13"
eslint-scope@^9.1.2:
version "9.1.2"
@@ -2458,10 +2463,10 @@ eslint-visitor-keys@^5.0.0, eslint-visitor-keys@^5.0.1:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be"
integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==
eslint@^10.13.0:
version "10.13.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.13.0.tgz#a8a4742648c0fa9131f429b509de5407682d4864"
integrity sha512-UohDjD4zSzEwhf58M3O7vc/ycc8O1I7dNlhtD0WxE8Px8EHNJKtUwzBqrzeGhisRuv2ag4sAYswszCJRgKXYUg==
eslint@^10.11.0:
version "10.11.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.11.0.tgz#304d1591b7c6a327e3f64b4f550f99fda160ae20"
integrity sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==
dependencies:
"@eslint-community/eslint-utils" "^4.8.0"
"@eslint-community/regexpp" "^4.12.2"
@@ -2861,7 +2866,7 @@ graceful-fs@^4.2.11:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
handlebars@^4.7.10:
handlebars@^4.7.9:
version "4.7.10"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.10.tgz#12419ea1b41391b3ffcf003f2164dcbce090c417"
integrity sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==
@@ -3849,10 +3854,10 @@ keyv@^5.6.0:
dependencies:
"@keyv/serialize" "^1.1.1"
"langsmith@>=0.11.0 <1.0.0":
version "0.11.6"
resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.11.6.tgz#cdc0f89a14632f8b172deea5daa1ee625f7442c3"
integrity sha512-5hfJM5DEZpXFxUS3U9nY2CMyBRmFnhkJj7znUODJ8vsr/Jx38tGJl4GIhV484xAY7yQFWrFOTDr33PB95kHUKg==
"langsmith@>=0.5.0 <1.0.0":
version "0.6.3"
resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.6.3.tgz#a3d8ad58d66a47d3697e3c69b2be3f6df5233190"
integrity sha512-pXrQ4/4myQvjFFOAUmt5pWRrLEZR20gzIJD7MNdUH+5/S5nLI4ZRBo/SYKC6coaYj9pYTfQdBIzcs+3kfJ5uDA==
dependencies:
p-queue "6.6.2"
@@ -4307,10 +4312,10 @@ prettier-linter-helpers@^1.0.1:
dependencies:
fast-diff "^1.1.2"
prettier@^3.9.10:
version "3.9.10"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.9.10.tgz#ac378e432f1a7381518f2e080816bbaefa38d585"
integrity sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==
prettier@^3.9.9:
version "3.9.9"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.9.9.tgz#09b826918c91cd4cbc80e0cbd1d2a922ff04f233"
integrity sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==
pretty-format@30.2.0, pretty-format@^30.0.0:
version "30.2.0"
@@ -4754,21 +4759,13 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
synckit@^0.11.8:
synckit@^0.11.13, synckit@^0.11.8:
version "0.11.13"
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.13.tgz#062a5ea57d81befc35892f8254de5c567e97c80a"
integrity sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==
dependencies:
"@pkgr/core" "^0.3.6"
synckit@^0.12.3:
version "0.12.3"
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.12.3.tgz#6e30a3441d095ed04c311077350ce205465512d6"
integrity sha512-U+/AXT6LWsxnj2vz9HeUuPWBNgdWxjyIEjEPdbJARC9txmgPzo2VEdqnFo4hHaNyMPtycyVJrSaQYeQv/PoyyA==
dependencies:
"@dual-bundle/import-meta-resolve" "^4.2.1"
"@pkgr/core" "^0.3.6"
test-exclude@^7.0.1:
version "7.0.2"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.2.tgz#482392077630bc57d5630c13abe908bb910dfc65"
@@ -4803,14 +4800,14 @@ ts-api-utils@^2.5.0:
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz#4acd4a155e22734990a5ed1fe9e97f113bcb37c1"
integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==
ts-jest@^29.4.15:
version "29.4.15"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.15.tgz#238f2d495066a33ca973a54a36a5ef228262a9ad"
integrity sha512-s2qmEUYFRqbjz1wv0EmccNPVOb45HOFpFuhzQJLKShYVisuTbCFPIIAIV2EuK5rIxb4BejLDbH7dnBZfYjybUQ==
ts-jest@^29.4.14:
version "29.4.14"
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.14.tgz#cb36c043df0fada861c8b604a5d514f953126180"
integrity sha512-+943G6wCX5E3VpzTQhBGBFntCmdT+QAkTvUlp03edTONFBvqskuXPhpyX16xdUotO4P5HQKyppldSWjocoltLQ==
dependencies:
bs-logger "^0.2.6"
fast-json-stable-stringify "^2.1.0"
handlebars "^4.7.10"
handlebars "^4.7.9"
json5 "^2.2.3"
lodash.memoize "^4.1.2"
make-error "^1.3.6"
@@ -9,8 +9,8 @@
},
"dependencies": {
"@js-monorepo-example/shared": "*",
"@langchain/core": "^1.3.1",
"@langchain/langgraph": "^1.5.3"
"@langchain/core": "^1.2.13",
"@langchain/langgraph": "^1.4.18"
},
"devDependencies": {
"typescript": "^7.0.2"
+6 -6
View File
@@ -17,18 +17,18 @@
"lint": "eslint 'apps/**/*.ts' 'libs/**/*.ts'"
},
"devDependencies": {
"turbo": "^2.11.10",
"turbo": "^2.11.5",
"typescript": "^7.0.2",
"@tsconfig/recommended": "^1.0.13",
"@eslint/eslintrc": "^3.3.7",
"@eslint/js": "^10.0.1",
"eslint": "^10.13.0",
"eslint": "^10.11.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-no-instanceof": "^1.0.1",
"eslint-plugin-prettier": "^5.5.7",
"@typescript-eslint/eslint-plugin": "^8.72.1",
"@typescript-eslint/parser": "^8.72.1",
"prettier": "^3.9.10"
"eslint-plugin-prettier": "^5.5.6",
"@typescript-eslint/eslint-plugin": "^8.70.1",
"@typescript-eslint/parser": "^8.70.1",
"prettier": "^3.9.9"
}
}
+153 -149
View File
@@ -25,11 +25,6 @@
resolved "https://registry.yarnpkg.com/@cfworker/json-schema/-/json-schema-4.1.1.tgz#4a2a3947ee9fa7b7c24be981422831b8674c3be6"
integrity sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==
"@dual-bundle/import-meta-resolve@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.2.1.tgz#cd0b25b3808cd9e684cd6cd549bbf8e1dcf05ee7"
integrity sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==
"@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1":
version "4.9.1"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595"
@@ -139,48 +134,48 @@
resolved "https://registry.yarnpkg.com/@keyv/serialize/-/serialize-1.1.1.tgz#0c01dd3a3483882af7cf3878d4e71d505c81fc4a"
integrity sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==
"@langchain/core@^1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-1.3.1.tgz#f6cdb6d200def5c2e69d25ff4fef92612d1b7601"
integrity sha512-jt0miXWxNi+rbZLeSeZkc7BBYKnWopksoJSpB+P3zaYgzMMNwkAPWqnd1zSClfYGwLZPXQbbTtaqv8ZFXmE3FA==
"@langchain/core@^1.2.13":
version "1.2.13"
resolved "https://registry.yarnpkg.com/@langchain/core/-/core-1.2.13.tgz#4ea4eb0308a25af350f75de2fbf34aecb819177e"
integrity sha512-ADGTxZ84n3civruUX1LlTOdua/PDGoni2U6TmKTX7hvRU2Jlepu8vLJI4Wnwj45KUKhUCrW94Il12Q2bxE3e8A==
dependencies:
"@cfworker/json-schema" "^4.0.2"
"@standard-schema/spec" "^1.1.0"
js-tiktoken "^1.0.12"
langsmith ">=0.11.0 <1.0.0"
langsmith ">=0.5.0 <1.0.0"
mustache "^4.2.0"
p-queue "^6.6.2"
zod "^3.25.76 || ^4"
"@langchain/langgraph-checkpoint@^1.2.1":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-1.2.2.tgz#cbfdf0d461385769665e26f70e41f1291ed1d37e"
integrity sha512-KL3nzi7I61ll5nANCQe5RoJ5SqTFrE+si9IK6k7IIj9OrPkIKT/Dz0w8CyJF+K7OEuJfA33HvPrZqfzzW1DXDA==
"@langchain/langgraph-checkpoint@^1.1.5":
version "1.1.5"
resolved "https://registry.yarnpkg.com/@langchain/langgraph-checkpoint/-/langgraph-checkpoint-1.1.5.tgz#c793177f9afcce31a1e9922f317f88fec1254282"
integrity sha512-BwDwl5VeTOh6CVuiIPgsUgfK51vTJDMSbFcSCUfjJWsl8/DPdK/mbv+ejxJstkSk/BlSPMP4JfXWcN6jD2ea2Q==
"@langchain/langgraph-sdk@~1.12.8":
version "1.12.9"
resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-1.12.9.tgz#b8e66e00af207f18e56eb6cd916c1ee8e7608745"
integrity sha512-A1LkLPVu00dj+9aErXPBLhmFURHtnnRO9KRFST+xAwurqQTa+LxC9ixxF7p+ZMo5P70qMFoaP8Ud2wuZmYD4xw==
"@langchain/langgraph-sdk@~1.12.0":
version "1.12.0"
resolved "https://registry.yarnpkg.com/@langchain/langgraph-sdk/-/langgraph-sdk-1.12.0.tgz#b063a7eba0a66f51cbc841a824626a47eca1eec9"
integrity sha512-F3AOZjKZRUGmE3FzY+RaVZI6WzXOkwnuF7jqgto6rDPSnO9OdVdYGvwGDi3jjRGf5xLoDtX2dpsR9z5KptU+5A==
dependencies:
"@langchain/protocol" "^0.0.20"
"@langchain/protocol" "^0.0.19"
"@types/json-schema" "^7.0.15"
p-queue "^9.0.1"
p-retry "^7.1.1"
"@langchain/langgraph@^1.5.3":
version "1.5.3"
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-1.5.3.tgz#9a921c9e099c2e3009f34987e0acb2645636bb74"
integrity sha512-cuqswNgfoF/J3/r9K5Qm7EnQc2f0mz7uWlAAs751KWhoK8sKfmuLABghF4b1LYq1NpK72XG1x8lWPyg45M8BOA==
"@langchain/langgraph@^1.4.18":
version "1.4.18"
resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-1.4.18.tgz#c8cc4ea734834262fdb00ae6a45a0849620e4fd2"
integrity sha512-yrMMJ9hk2NVMD2xU2WoVrgFawAU6s/RzEl/dX9BhlyxZHazo1wHY35z4K2BIBzTUh4z3giCzrUoqRAqW2CWpWg==
dependencies:
"@langchain/langgraph-checkpoint" "^1.2.1"
"@langchain/langgraph-sdk" "~1.12.8"
"@langchain/protocol" "^0.0.20"
"@langchain/langgraph-checkpoint" "^1.1.5"
"@langchain/langgraph-sdk" "~1.12.0"
"@langchain/protocol" "^0.0.19"
"@standard-schema/spec" "1.1.0"
"@langchain/protocol@^0.0.20":
version "0.0.20"
resolved "https://registry.yarnpkg.com/@langchain/protocol/-/protocol-0.0.20.tgz#441a57a6176f634306d9214da65d35033355d5e9"
integrity sha512-sxlkDG2/HGyjDFLTJ8B/o3oglRjOv8iLCFgrV0550/Vym3V6RZ0SUjAQFe8jrLQw8ZXjERy3swJp5OODBSrUYA==
"@langchain/protocol@^0.0.19":
version "0.0.19"
resolved "https://registry.yarnpkg.com/@langchain/protocol/-/protocol-0.0.19.tgz#7fe43fed115dc34b3b4c246e8d5283fbcbfab7b0"
integrity sha512-9hKcRrH7cBX6gfutdfXPoft1OCchHe4FEpALoDJMl5Qu+n/YG5ynZmyu8+8cxORlPwHBoKTxggvXz+76M1yX1Q==
"@pkgr/core@^0.3.6":
version "0.3.6"
@@ -202,35 +197,35 @@
resolved "https://registry.yarnpkg.com/@tsconfig/recommended/-/recommended-1.0.13.tgz#269fce3ad04ca70b93269ff44cca81b950f542da"
integrity sha512-sySRuBfMKyKO/j2ZAhR8kSembhjuPEV4Ra3AHtmWLq51+iGaudr45crPSzNC5b7/Ctrh9dfUpBuTlYrH6rM58Q==
"@turbo/darwin-64@2.11.10":
version "2.11.10"
resolved "https://registry.yarnpkg.com/@turbo/darwin-64/-/darwin-64-2.11.10.tgz#824aa74de92806ef2a21932b4148feaea98e8887"
integrity sha512-By4idRap5+nLnMuRzLB4qkOPRlvNdyY1ne4VJYDyUoUB11H94k7uif9mbe3JT8JSNXs1D68uMcBj6hJ/jMQRvA==
"@turbo/darwin-64@2.11.5":
version "2.11.5"
resolved "https://registry.yarnpkg.com/@turbo/darwin-64/-/darwin-64-2.11.5.tgz#6ddc580c2b7d05e578ca1c06b3f61473f4b7b00e"
integrity sha512-KdY4y7BBjIisl+jAkb3FFaRjClGqHyaeHCcVcCT8LBzFFwUu6thgfxyRZAx0uL4KoxLmH+iOan8UhVf/aDDcaA==
"@turbo/darwin-arm64@2.11.10":
version "2.11.10"
resolved "https://registry.yarnpkg.com/@turbo/darwin-arm64/-/darwin-arm64-2.11.10.tgz#e9de7c9b9eb2a428cd4fb63fb9826696107339a4"
integrity sha512-pyEYlVcuexOPa92p0Wlwl9YT1Is5D60lxN3ItWm3g30YLvFaUq86TF4BBWg5ZAc0uMTnrOzCvuCsOBTdzUWnRQ==
"@turbo/darwin-arm64@2.11.5":
version "2.11.5"
resolved "https://registry.yarnpkg.com/@turbo/darwin-arm64/-/darwin-arm64-2.11.5.tgz#a756c67bb828cd00d03e909769dd39453c1f1e69"
integrity sha512-ToZDGi2u410+TfRtC/O1/LpujGgvNJoRBZnwVXob7oO0+3OSlwIO3zofXKUvwQvlba7a8ZXPyCZwufjAXd7f0w==
"@turbo/linux-64@2.11.10":
version "2.11.10"
resolved "https://registry.yarnpkg.com/@turbo/linux-64/-/linux-64-2.11.10.tgz#e9f48a6383e7c6926a488f329343792de3ee537c"
integrity sha512-1tYeR/IDZ5HEdsb0jNq2L2xam5nMQ+g80tlV2BZmv4OFw+pMcUM0mMmUcfc5wXPPNHVNPuDMjh9Y8G8HgXPLZA==
"@turbo/linux-64@2.11.5":
version "2.11.5"
resolved "https://registry.yarnpkg.com/@turbo/linux-64/-/linux-64-2.11.5.tgz#b95c05218aebb06c06e5f991d48c55b78dc5e005"
integrity sha512-Ub2d/nAbdWLaSytcEaPp1wm/YuIvHEIqNrCRMI/UgOGyFdOhgxm8VyWPAJilQWRYxYVrqEUoy2K5S5K8SG50rA==
"@turbo/linux-arm64@2.11.10":
version "2.11.10"
resolved "https://registry.yarnpkg.com/@turbo/linux-arm64/-/linux-arm64-2.11.10.tgz#d76831f850d30ef386b221b8ee72270a5ee7e2ba"
integrity sha512-HuekQxvVGhUd46g1Im/DKdOtglHr4/gguZAvzEpPUgtxNkztSa8lWhQEGQ+EmPQ0n7w19qjThFU+OfEEd0uvlw==
"@turbo/linux-arm64@2.11.5":
version "2.11.5"
resolved "https://registry.yarnpkg.com/@turbo/linux-arm64/-/linux-arm64-2.11.5.tgz#c7a1d1a44c8c8dcff4b72fbafe9a5059c8fb668a"
integrity sha512-JQjmGxp724Wuft9FAxPMtmBur1CEdUyxJ+FVoIvKkgj63rXHl9PYCWrsU2tMjVi609XTQ3M8vZD9Yc9qVAVYrw==
"@turbo/windows-64@2.11.10":
version "2.11.10"
resolved "https://registry.yarnpkg.com/@turbo/windows-64/-/windows-64-2.11.10.tgz#fb29a315a112afac9f01a4a1ea7601b28f140f31"
integrity sha512-UJOP3BkbreY3/JlMgJR6ZiimxYIBn+c+B4tnBIxD0E9c3Estmk+XoVNVBNBottZLNRXe3/ScgOU4+/k8tIWysg==
"@turbo/windows-64@2.11.5":
version "2.11.5"
resolved "https://registry.yarnpkg.com/@turbo/windows-64/-/windows-64-2.11.5.tgz#60bdaaa7e8ba32f32623b31a1c75f6c3db400a0c"
integrity sha512-AFkbPmXNd8XgCi/66YEHR2bVl3gtusXyDihTIVEPAuSB+7ulLmJePvz1unWPVKLHdbCTTIdKq2mpJ1B1e3lJXw==
"@turbo/windows-arm64@2.11.10":
version "2.11.10"
resolved "https://registry.yarnpkg.com/@turbo/windows-arm64/-/windows-arm64-2.11.10.tgz#bac3077b0a39643043ece360ff1f4f2af9272106"
integrity sha512-xOkEjF3ws/b/YNlc+bvJHMYBn+vL+QbEISZFVi0qS+ImmhA1X3/gmNmBrjjIG3dg6aeN+n6gQitzSkXQ286qJQ==
"@turbo/windows-arm64@2.11.5":
version "2.11.5"
resolved "https://registry.yarnpkg.com/@turbo/windows-arm64/-/windows-arm64-2.11.5.tgz#18bd5a0ad30c4e094995786bd78916b6de6b5c1c"
integrity sha512-52ziXYP3snGOxjv19xA3WL1WDKv9Q6uDAa/7EKIR5j4hocMlYURkrOHbJ2ZZI+VfPU5MwKpcGeRfHxHJu7Y/Pw==
"@types/esrecurse@^4.3.1":
version "4.3.1"
@@ -252,100 +247,110 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@typescript-eslint/eslint-plugin@^8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.72.1.tgz#95e29de63ce7ed793eb1c6efd9f8b802ce808262"
integrity sha512-5CokTWXpmbylfFabsbvHC0CAw3XLaGnoa+nFJX5FR592h/LoZsENUE6p/rg5xvkKrFEXz6pYmgI29AgXc2N3BQ==
"@typescript-eslint/eslint-plugin@^8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.70.1.tgz#d404bac4cb04e4dcd20086e46e13c952bd0771e0"
integrity sha512-nDNrUQ/4ruSNYbu749TRY7cfrzPtoLHEXSNBI8aaNY32LlZCajixqRf3FqcKC4p5Cam4VOHYx/t+i5+nKXvrqA==
dependencies:
"@eslint-community/regexpp" "^4.12.2"
"@typescript-eslint/scope-manager" "8.72.1"
"@typescript-eslint/type-utils" "8.72.1"
"@typescript-eslint/utils" "8.72.1"
"@typescript-eslint/visitor-keys" "8.72.1"
"@typescript-eslint/scope-manager" "8.70.1"
"@typescript-eslint/type-utils" "8.70.1"
"@typescript-eslint/utils" "8.70.1"
"@typescript-eslint/visitor-keys" "8.70.1"
ignore "^7.0.5"
natural-compare "^1.4.0"
ts-api-utils "^2.5.0"
"@typescript-eslint/parser@^8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.72.1.tgz#7370b68dadc3819b9aac68ea340bd8b43ee62379"
integrity sha512-ZGwZP4OCfyA0AQTpEAMWz+rtX01DRhQWW5hB0ucKDEIX5U7jdmPSaMx94rQfaPAQzIYz2BimOgKOJtjfpRFQ9g==
"@typescript-eslint/parser@^8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.70.1.tgz#6c99b7b954efc05dae179001c8b477d1a8948dcf"
integrity sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw==
dependencies:
"@typescript-eslint/scope-manager" "8.72.1"
"@typescript-eslint/types" "8.72.1"
"@typescript-eslint/typescript-estree" "8.72.1"
"@typescript-eslint/visitor-keys" "8.72.1"
"@typescript-eslint/scope-manager" "8.70.1"
"@typescript-eslint/types" "8.70.1"
"@typescript-eslint/typescript-estree" "8.70.1"
"@typescript-eslint/visitor-keys" "8.70.1"
debug "^4.4.3"
"@typescript-eslint/project-service@8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.72.1.tgz#1d09902a9c23f6cfab779e5acc78a45abf540add"
integrity sha512-fAuVMkwdO/huI8nyo/WKnNKf6Gag93S56Rh3Ts5mGcavwSdaN8tRg1gCeIGX8PF+nzp1BjGfO4gHhGzW1J6jTg==
"@typescript-eslint/project-service@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.70.1.tgz#d5635594cc8a573f0e15a772b1704e93a00134c1"
integrity sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.72.1"
"@typescript-eslint/types" "^8.72.1"
"@typescript-eslint/tsconfig-utils" "^8.70.1"
"@typescript-eslint/types" "^8.70.1"
debug "^4.4.3"
"@typescript-eslint/scope-manager@8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.72.1.tgz#a0fb4dfe5aa2b0149b7a2d1fd00d947992b13067"
integrity sha512-lC/t6eMCZZQAAKZEYGL6RDVSuFehpdmMT6h+nJIjIa6oylN/PwDgDVtEIbB7O9xFfZYtZAYLoLlDNWKJhtF+uQ==
"@typescript-eslint/scope-manager@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.70.1.tgz#0feeae4da2d279b960cdbfe5e0a02fbf1195e93f"
integrity sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==
dependencies:
"@typescript-eslint/types" "8.72.1"
"@typescript-eslint/visitor-keys" "8.72.1"
"@typescript-eslint/types" "8.70.1"
"@typescript-eslint/visitor-keys" "8.70.1"
"@typescript-eslint/tsconfig-utils@8.72.1", "@typescript-eslint/tsconfig-utils@^8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.1.tgz#07394c85095ee38455e76bc221ba23650af73e44"
integrity sha512-e4D5R5vpeAEPBxT2BAzsFQL3sbwyeeeo5+aczgNSRK1V734jE7pTqaGFU+fXapWEQwnZbpFaTn32+Z8DwS6OTg==
"@typescript-eslint/tsconfig-utils@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.1.tgz#e9353a6e5f3498a29b40946ca3e11f6166fef09e"
integrity sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==
"@typescript-eslint/type-utils@8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.72.1.tgz#f1f592f63bd9726de21191aae3ccf64a813914b8"
integrity sha512-iY2wlmyrXJmx4yxUGP3aYCFF3cA/6ShddOKmLU2dx7LnEykbAM8WThlxZEfd1NddqceF6eJmZUayxx9oKIYNlg==
"@typescript-eslint/tsconfig-utils@^8.70.1":
version "8.71.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.0.tgz#83a69c9a514af8f5ff099cc6c0d609c273e8f039"
integrity sha512-Z1UlWHADEK2Mlb9NpWfDeSjqoZ5EyrOv4R3eQpbkzqn/EwaIdOpXXupEA1+0ZIOSJSZZDBHG0BrQyN8zUG6Pwg==
"@typescript-eslint/type-utils@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.70.1.tgz#6ea36f2c7dc0776c1c8e1c380b4896f00bc4c27f"
integrity sha512-7zKTnyvaVWqzLZHPFQtX1hVHqgkMC+WebPWakNCSyrQVbIP1AM0L0TlBZtACldIRb6PptI8Odk+jyZ5kP3B1VA==
dependencies:
"@typescript-eslint/types" "8.72.1"
"@typescript-eslint/typescript-estree" "8.72.1"
"@typescript-eslint/utils" "8.72.1"
"@typescript-eslint/types" "8.70.1"
"@typescript-eslint/typescript-estree" "8.70.1"
"@typescript-eslint/utils" "8.70.1"
debug "^4.4.3"
ts-api-utils "^2.5.0"
"@typescript-eslint/types@8.72.1", "@typescript-eslint/types@^8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.72.1.tgz#067d8f738c294611e33a1425ae43ac9b9c2f24e3"
integrity sha512-LmE5IhQet1BWOWEXqBrVtBPsMAOQ6Nzk35d+WLh3mk4EUF33sqlfxjio1MdYeSvKSG5skactKKuixBIKXhL4Rw==
"@typescript-eslint/types@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.70.1.tgz#2022b9d07d057174eba25b9bbd289641d262a196"
integrity sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==
"@typescript-eslint/typescript-estree@8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.1.tgz#e93b9cc3a56bf12f5fed857bb8f4963cce7924ee"
integrity sha512-DAlUoUV93hIvrtIQLwCbmZQYVZwOMhiHJrOVUUIWicFZaIYjTRqlY3I/NZevfkLmSqkNHRu2qYDcq9lrn16oOQ==
"@typescript-eslint/types@^8.70.1":
version "8.71.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.71.0.tgz#a230a121e71665060afe27d1f1ad110371f9451e"
integrity sha512-cJ4OoxPGWvFnBTnSZyaU+qJzGTqPTGJY+gDchj6cRyLRdmIdt4rcsE4twj+zPfrNiWuVi38wijHzShL++Z9atQ==
"@typescript-eslint/typescript-estree@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.1.tgz#ced48fee6f7043835d2edfed9786ae74813a3c99"
integrity sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==
dependencies:
"@typescript-eslint/project-service" "8.72.1"
"@typescript-eslint/tsconfig-utils" "8.72.1"
"@typescript-eslint/types" "8.72.1"
"@typescript-eslint/visitor-keys" "8.72.1"
"@typescript-eslint/project-service" "8.70.1"
"@typescript-eslint/tsconfig-utils" "8.70.1"
"@typescript-eslint/types" "8.70.1"
"@typescript-eslint/visitor-keys" "8.70.1"
debug "^4.4.3"
minimatch "^10.2.2"
semver "^7.7.3"
tinyglobby "^0.2.15"
ts-api-utils "^2.5.0"
"@typescript-eslint/utils@8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.72.1.tgz#f210c1bf4a25aa1306df80c702ed30bbbc9b4d3d"
integrity sha512-5v5liypurJBFHCBNqh/RjsymAD+VWcE0djuGY/BUqp5fC3Qn+luWDEksjuTtWjPn6i5zQlUtkZCRG/CYG0yCkA==
"@typescript-eslint/utils@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.70.1.tgz#6a97887cee702df513692f5cdc7f79baeef48e02"
integrity sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==
dependencies:
"@eslint-community/eslint-utils" "^4.9.1"
"@typescript-eslint/scope-manager" "8.72.1"
"@typescript-eslint/types" "8.72.1"
"@typescript-eslint/typescript-estree" "8.72.1"
"@typescript-eslint/scope-manager" "8.70.1"
"@typescript-eslint/types" "8.70.1"
"@typescript-eslint/typescript-estree" "8.70.1"
"@typescript-eslint/visitor-keys@8.72.1":
version "8.72.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.1.tgz#c7aec9883b44ef795f1b8200df81d9d44d355de7"
integrity sha512-SB8xqipSzZHUePCCLNeYxqnxq83X61/qzNhjQGbTfjxGZIeOEP7tBgWYX05Tcd6CgE02WdTJnh5b8kMZJ/+bnQ==
"@typescript-eslint/visitor-keys@8.70.1":
version "8.70.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.1.tgz#b4cde0ed16982bd963a6310fcae554f59fd14124"
integrity sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==
dependencies:
"@typescript-eslint/types" "8.72.1"
"@typescript-eslint/types" "8.70.1"
eslint-visitor-keys "^5.0.0"
"@typescript/typescript-aix-ppc64@7.0.2":
@@ -885,13 +890,13 @@ eslint-plugin-no-instanceof@^1.0.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-no-instanceof/-/eslint-plugin-no-instanceof-1.0.1.tgz#5d9fc86d160df6991b654b294a62390207f1bb97"
integrity sha512-zlqQ7EsfzbRO68uI+p8FIE7zYB4njs+nNbkNjSb5QmLi2et67zQLqSeaao5U9SpnlZTTJC87nS2oyHo2ACtajw==
eslint-plugin-prettier@^5.5.7:
version "5.5.7"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.7.tgz#ebef3acc2d664333b1356b717780198abc635398"
integrity sha512-WjHyn3GrFd+ZR0rzsLiM4zJZ35PZMOE0SUIxzGmnWNG0126yNfJKJzObmmkYDwHJfGlkz3PSlzyG9ez/LJntwg==
eslint-plugin-prettier@^5.5.6:
version "5.5.6"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.6.tgz#363ebe4d769bce157ccdd8129ce3efd91dc62564"
integrity sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==
dependencies:
prettier-linter-helpers "^1.0.1"
synckit "^0.12.3"
synckit "^0.11.13"
eslint-scope@^9.1.2:
version "9.1.2"
@@ -918,10 +923,10 @@ eslint-visitor-keys@^5.0.0, eslint-visitor-keys@^5.0.1:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be"
integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==
eslint@^10.13.0:
version "10.13.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.13.0.tgz#a8a4742648c0fa9131f429b509de5407682d4864"
integrity sha512-UohDjD4zSzEwhf58M3O7vc/ycc8O1I7dNlhtD0WxE8Px8EHNJKtUwzBqrzeGhisRuv2ag4sAYswszCJRgKXYUg==
eslint@^10.11.0:
version "10.11.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.11.0.tgz#304d1591b7c6a327e3f64b4f550f99fda160ae20"
integrity sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==
dependencies:
"@eslint-community/eslint-utils" "^4.8.0"
"@eslint-community/regexpp" "^4.12.2"
@@ -1471,10 +1476,10 @@ keyv@^5.6.0:
dependencies:
"@keyv/serialize" "^1.1.1"
"langsmith@>=0.11.0 <1.0.0":
version "0.11.6"
resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.11.6.tgz#cdc0f89a14632f8b172deea5daa1ee625f7442c3"
integrity sha512-5hfJM5DEZpXFxUS3U9nY2CMyBRmFnhkJj7znUODJ8vsr/Jx38tGJl4GIhV484xAY7yQFWrFOTDr33PB95kHUKg==
"langsmith@>=0.5.0 <1.0.0":
version "0.7.1"
resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.7.1.tgz#d721ad2e5211f9a5373e8a84eb4fed36b5621971"
integrity sha512-Wjk90UjNoY5cBHMlNAC/eZx5clI8jnjBOBW8uJu8+MWBtx0QesNjsUiLtjI+I3UnrpxFFpDqGXcnhBjH654Mqg==
dependencies:
p-queue "6.6.2"
@@ -1702,10 +1707,10 @@ prettier-linter-helpers@^1.0.1:
dependencies:
fast-diff "^1.1.2"
prettier@^3.9.10:
version "3.9.10"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.9.10.tgz#ac378e432f1a7381518f2e080816bbaefa38d585"
integrity sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==
prettier@^3.9.9:
version "3.9.9"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.9.9.tgz#09b826918c91cd4cbc80e0cbd1d2a922ff04f233"
integrity sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==
punycode@^2.1.0:
version "2.3.1"
@@ -1935,12 +1940,11 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
synckit@^0.12.3:
version "0.12.3"
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.12.3.tgz#6e30a3441d095ed04c311077350ce205465512d6"
integrity sha512-U+/AXT6LWsxnj2vz9HeUuPWBNgdWxjyIEjEPdbJARC9txmgPzo2VEdqnFo4hHaNyMPtycyVJrSaQYeQv/PoyyA==
synckit@^0.11.13:
version "0.11.13"
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.13.tgz#062a5ea57d81befc35892f8254de5c567e97c80a"
integrity sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==
dependencies:
"@dual-bundle/import-meta-resolve" "^4.2.1"
"@pkgr/core" "^0.3.6"
tinyglobby@^0.2.15:
@@ -1966,17 +1970,17 @@ tsconfig-paths@^3.15.0:
minimist "^1.2.6"
strip-bom "^3.0.0"
turbo@^2.11.10:
version "2.11.10"
resolved "https://registry.yarnpkg.com/turbo/-/turbo-2.11.10.tgz#8b1d40debab2b871ff9e908d6f3018aeda04879d"
integrity sha512-3GJYwig8ygEI89oaOZdduJX8/aw6O9eRPYHoIt6T3FZl9hkKjiyIspufJoVgqXihPoJu8M5CSV0DlZvzfohBCw==
turbo@^2.11.5:
version "2.11.5"
resolved "https://registry.yarnpkg.com/turbo/-/turbo-2.11.5.tgz#e7223abd0555b978d3efe1b00ea9a98f43afb7ab"
integrity sha512-qLXheqz3TUvJpHIV6MUBwkNkvwKxMVG/QsimyiKT9DfNWPHJ294G/i5nDaH+fbHJt3UH7orubNNxAqYkbUNGAQ==
optionalDependencies:
"@turbo/darwin-64" "2.11.10"
"@turbo/darwin-arm64" "2.11.10"
"@turbo/linux-64" "2.11.10"
"@turbo/linux-arm64" "2.11.10"
"@turbo/windows-64" "2.11.10"
"@turbo/windows-arm64" "2.11.10"
"@turbo/darwin-64" "2.11.5"
"@turbo/darwin-arm64" "2.11.5"
"@turbo/linux-64" "2.11.5"
"@turbo/linux-arm64" "2.11.5"
"@turbo/windows-64" "2.11.5"
"@turbo/windows-arm64" "2.11.5"
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.4.41"
__version__ = "0.4.40"
+8
View File
@@ -11,6 +11,7 @@ import click.exceptions
import langgraph_cli.config
import langgraph_cli.docker
from langgraph_cli import langsmith_auth
from langgraph_cli.analytics import log_command
from langgraph_cli.config import Config
from langgraph_cli.constants import DEFAULT_CONFIG, DEFAULT_PORT
@@ -233,6 +234,13 @@ def cli():
pass
@cli.command(help="Log in to LangSmith in the browser (no API key needed).")
@click.option("--no-browser", is_flag=True, help="Print the URL instead of opening it.")
def login(no_browser: bool) -> None:
api_url = langsmith_auth.resolve_api_url(os.environ.get("LANGSMITH_ENDPOINT"))
langsmith_auth.login(api_url, open_browser=not no_browser)
# Wire the deploy group (defined in deploy.py) into the top-level CLI.
cli.add_command(deploy)
+27 -12
View File
@@ -1,6 +1,7 @@
"""Deploy command and subcommands for the LangGraph CLI."""
import base64
import functools
import json as json_mod
import os
import pathlib
@@ -19,6 +20,7 @@ import click.exceptions
from dotenv import dotenv_values, set_key
import langgraph_cli.config
from langgraph_cli import langsmith_auth
from langgraph_cli.analytics import log_command
from langgraph_cli.config import Config
from langgraph_cli.constants import DEFAULT_CONFIG
@@ -1982,17 +1984,6 @@ def _create_host_backend_client(
if val:
resolved_api_key = val
break
if not resolved_api_key:
if effective_no_input:
raise click.ClickException(
"No LangSmith API key found. Set LANGSMITH_API_KEY in the "
"environment or .env file."
)
click.secho(
"No LangSmith API key found. Create one at Settings > API Keys in LangSmith.",
fg="yellow",
)
resolved_api_key = click.prompt("Enter LangSmith API key", hide_input=True)
tenant_id = env_vars.get("LANGSMITH_TENANT_ID") or os.environ.get(
"LANGSMITH_TENANT_ID"
)
@@ -2000,8 +1991,31 @@ def _create_host_backend_client(
"LANGSMITH_ENDPOINT"
)
endpoints = ControlPlaneEndpoints.resolve(host_url, langsmith_endpoint)
access_token: Callable[[], str] | None = None
if not resolved_api_key:
profile = langsmith_auth.load_profile()
api_url = langsmith_auth.resolve_api_url(langsmith_endpoint)
if profile.get("api_key"):
resolved_api_key = profile["api_key"]
else:
if not any((profile.get("oauth") or {}).values()):
if effective_no_input:
raise click.ClickException(
"No LangSmith credentials found. Set LANGSMITH_API_KEY "
"in the environment or .env file (an API key), or run "
"`langgraph login`."
)
click.secho("You are not logged in to LangSmith.", fg="yellow")
langsmith_auth.login(api_url)
profile = langsmith_auth.load_profile()
access_token = functools.partial(langsmith_auth.get_access_token, api_url)
tenant_id = tenant_id or profile.get("workspace_id")
return HostBackendClient(
endpoints.control_plane_url, resolved_api_key, tenant_id=tenant_id
endpoints.control_plane_url,
resolved_api_key,
tenant_id=tenant_id,
**({"access_token": access_token} if access_token else {}),
)
@@ -2431,6 +2445,7 @@ def _deploy_cmd(
validate_deploy_commands(install_command, build_command)
agent = None
if agent_id is not None or environment is not None:
em.note("Note: --agent-id and --agent-environment flags are in private beta")
if not agent_id or not agent_id.strip() or not environment:
raise click.UsageError(
"--agent-id and --agent-environment are required together."
+15 -7
View File
@@ -2,7 +2,7 @@
from __future__ import annotations
from collections.abc import Mapping
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from typing import Any, Literal
from urllib.parse import urlparse
@@ -130,17 +130,18 @@ class HostBackendClient:
def __init__(
self,
base_url: str,
api_key: str,
api_key: str | None,
tenant_id: str | None = None,
*,
access_token: Callable[[], str] | None = None,
transport: httpx.BaseTransport | None = None,
):
if not base_url:
raise click.UsageError("Host backend URL is required")
headers: dict[str, str] = {
"X-Api-Key": api_key,
"Accept": "application/json",
}
self._access_token = access_token
headers: dict[str, str] = {"Accept": "application/json"}
if api_key:
headers["X-Api-Key"] = api_key
if tenant_id:
headers["X-Tenant-ID"] = tenant_id
self._endpoints = ControlPlaneEndpoints.from_control_plane_url(base_url)
@@ -170,8 +171,15 @@ class HostBackendClient:
payload: dict[str, Any] | None = None,
params: dict[str, Any] | None = None,
) -> Any:
headers = (
{"Authorization": f"Bearer {self._access_token()}"}
if self._access_token
else None
)
try:
resp = self._client.request(method, path, json=payload, params=params)
resp = self._client.request(
method, path, json=payload, params=params, headers=headers
)
resp.raise_for_status()
except httpx.HTTPStatusError as err:
detail = _error_detail(err.response)
+260
View File
@@ -0,0 +1,260 @@
"""LangSmith OAuth login, shared with the ``langsmith`` CLI via ``~/.langsmith/config.json``."""
from __future__ import annotations
import datetime
import json
import os
import pathlib
import sys
import time
import webbrowser
from typing import Any
from urllib.parse import urlparse
import click
import httpx
CLIENT_ID = "langsmith-cli"
DEFAULT_API_URL = "https://api.smith.langchain.com"
_WELL_KNOWN = "/.well-known/oauth-authorization-server"
_REFRESH_LEEWAY = datetime.timedelta(seconds=60)
_DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code"
def _config_path() -> pathlib.Path:
override = os.environ.get("LANGSMITH_CONFIG_FILE")
if override:
return pathlib.Path(override)
return pathlib.Path.home() / ".langsmith" / "config.json"
def _read_config() -> dict[str, Any]:
try:
config = json.loads(_config_path().read_text(encoding="utf-8"))
except (OSError, ValueError):
return {}
return config if isinstance(config, dict) else {}
def _write_config(config: dict[str, Any]) -> None:
path = _config_path()
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
tmp = path.with_name(path.name + ".tmp")
tmp.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8")
os.chmod(tmp, 0o600)
os.replace(tmp, path)
def _profile_name(config: dict[str, Any]) -> str:
return (
os.environ.get("LANGSMITH_PROFILE")
or config.get("current_profile")
or "default"
)
def load_profile() -> dict[str, Any]:
"""Return the active profile from the LangSmith config file, or ``{}``."""
config = _read_config()
profile = (config.get("profiles") or {}).get(_profile_name(config))
return profile if isinstance(profile, dict) else {}
def resolve_api_url(endpoint: str | None = None) -> str:
"""Pick the LangSmith API URL: explicit endpoint, then profile, then cloud."""
return endpoint or load_profile().get("api_url") or DEFAULT_API_URL
def _save_tokens(token: dict[str, Any], api_url: str | None = None) -> None:
config = _read_config()
name = _profile_name(config)
profile = config.setdefault("profiles", {}).setdefault(name, {})
if api_url:
profile["api_url"] = api_url
oauth = profile.setdefault("oauth", {})
oauth["access_token"] = token["access_token"]
if token.get("refresh_token"):
oauth["refresh_token"] = token["refresh_token"]
if token.get("expires_in"):
expires_at = datetime.datetime.now(datetime.timezone.utc) + (
datetime.timedelta(seconds=int(token["expires_in"]))
)
oauth["expires_at"] = expires_at.strftime("%Y-%m-%dT%H:%M:%SZ")
config.setdefault("current_profile", name)
_write_config(config)
def _oauth_endpoints(api_url: str) -> dict[str, str]:
"""Discover the OAuth endpoints, falling back to ``<root>/oauth/*``."""
root = api_url.rstrip("/")
for suffix in ("/api/v1", "/api"):
if root.endswith(suffix):
root = root[: -len(suffix)]
break
for base in (root, f"{root}/api"):
try:
resp = httpx.get(base + _WELL_KNOWN, timeout=10)
doc = resp.json() if resp.is_success else None
except (httpx.HTTPError, ValueError):
continue
if (
isinstance(doc, dict)
and str(doc.get("issuer", "")).rstrip("/") == base
and all(
urlparse(str(doc.get(key, ""))).netloc == urlparse(base).netloc
for key in ("token_endpoint", "device_authorization_endpoint")
)
):
return {
"issuer": base,
"token": doc["token_endpoint"],
"device": doc["device_authorization_endpoint"],
}
return {
"issuer": root,
"token": f"{root}/oauth/token",
"device": f"{root}/oauth/device/code",
}
def _expiring(oauth: dict[str, Any]) -> bool:
if not oauth.get("access_token"):
return True
expires_at = oauth.get("expires_at")
if not expires_at:
return False
try:
expiry = datetime.datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
except ValueError:
return True
if expiry.tzinfo is None:
expiry = expiry.replace(tzinfo=datetime.timezone.utc)
return expiry <= datetime.datetime.now(datetime.timezone.utc) + _REFRESH_LEEWAY
def get_access_token(api_url: str) -> str:
"""Return a valid access token from the profile, refreshing it if needed."""
oauth = load_profile().get("oauth") or {}
if not _expiring(oauth):
return oauth["access_token"]
refresh_token = oauth.get("refresh_token")
if not refresh_token:
raise click.ClickException(
"LangSmith session expired. Run `langgraph login` to sign in again."
)
endpoints = _oauth_endpoints(api_url)
try:
resp = httpx.post(
endpoints["token"],
data={
"grant_type": "refresh_token",
"client_id": CLIENT_ID,
"refresh_token": refresh_token,
},
timeout=10,
)
resp.raise_for_status()
token = resp.json()
except (httpx.HTTPError, ValueError):
raise click.ClickException(
"Could not refresh your LangSmith session. "
"Run `langgraph login` to sign in again."
) from None
if not isinstance(token, dict) or not token.get("access_token"):
raise click.ClickException(
"Could not refresh your LangSmith session. "
"Run `langgraph login` to sign in again."
)
_save_tokens(token)
return token["access_token"]
def _write_login_success(api_url: str) -> None:
name = _profile_name(_read_config())
if not sys.stdout.isatty() or os.environ.get("TERM") == "dumb":
click.echo(
f"Logged in to {api_url} as profile {json.dumps(name, ensure_ascii=False)}"
)
return
color = not bool(os.environ.get("NO_COLOR"))
captions = {
2: click.style("✓ You're in.", fg="bright_blue", bold=True),
4: f"Logged in as profile {json.dumps(name, ensure_ascii=False)}.",
6: click.style("Ready when you are.", dim=True),
}
logo = (
"▄█▄ ",
"████▄ ",
"██████▄ ▄▄▄▄▄▄ ",
" ██████ ██████▀ ",
" ▀████ ▀██▀ ",
" ▀█ ▄███▄▄ ",
"██████▀███████▄ ",
" █████ ▀███████▄ ",
" ▀▀█▀ ▀▀███▀▀ ",
)
lines = [""]
for i, row in enumerate(logo):
line = click.style(row, fg="bright_blue")
if i in captions:
line += " " + captions[i]
lines.append(line)
lines.append(f"\n{click.style('API', dim=True)} {api_url}")
click.echo("\n".join(lines), color=color)
def login(api_url: str, *, open_browser: bool = True) -> None:
"""Sign in with the OAuth device flow and store tokens in the profile."""
endpoints = _oauth_endpoints(api_url)
try:
resp = httpx.post(
endpoints["device"],
data={"client_id": CLIENT_ID, "resource": endpoints["issuer"]},
timeout=10,
)
resp.raise_for_status()
device = resp.json()
device_code, user_code = device["device_code"], device["user_code"]
verification_url = (
device.get("verification_uri_complete") or device["verification_uri"]
)
except (httpx.HTTPError, ValueError, KeyError):
raise click.ClickException(
f"Could not start LangSmith login against {api_url}."
) from None
click.echo(f"Open this URL to authorize the CLI:\n{verification_url}\n")
click.echo(f"Code: {user_code}\n")
if open_browser:
webbrowser.open(verification_url)
click.echo("Waiting for authorization...")
interval = max(int(device.get("interval") or 5), 5)
deadline = time.monotonic() + int(device.get("expires_in") or 600)
while time.monotonic() < deadline:
time.sleep(interval)
try:
resp = httpx.post(
endpoints["token"],
data={
"grant_type": _DEVICE_GRANT,
"client_id": CLIENT_ID,
"device_code": device_code,
"resource": endpoints["issuer"],
},
timeout=10,
)
body = resp.json()
except (httpx.HTTPError, ValueError):
continue
if resp.is_success and body.get("access_token"):
_save_tokens(body, api_url)
_write_login_success(api_url)
return
error = body.get("error") if isinstance(body, dict) else None
if error == "slow_down":
interval += 5
elif error != "authorization_pending":
raise click.ClickException(f"LangSmith login failed: {error or 'error'}.")
raise click.ClickException("Timed out waiting for LangSmith login.")
@@ -85,8 +85,6 @@ def test_agent_create(deployment_api, tmp_path, monkeypatch):
assert "name" not in payload
assert build.call_args.kwargs["deployment_id"] == "runtime-id"
assert "server-name" in result.output
assert "'langgraph deploy' is in beta" in result.output
assert "private beta" not in result.output
assert (tmp_path / ".env").read_text() == "LANGSMITH_DEPLOYMENT_NAME=legacy\n"
@@ -578,12 +578,13 @@ def test_a_deployed_result_prints_the_deployment_url(
class TestCreateHostBackendClientNoInput:
def test_raises_when_no_api_key_and_no_input(self, monkeypatch, tmp_path):
monkeypatch.setenv("LANGSMITH_CONFIG_FILE", str(tmp_path / "missing.json"))
monkeypatch.setattr(deploy_mod, "_no_input", True)
monkeypatch.delenv("LANGSMITH_API_KEY", raising=False)
monkeypatch.delenv("LANGCHAIN_API_KEY", raising=False)
monkeypatch.delenv("LANGGRAPH_HOST_API_KEY", raising=False)
with pytest.raises(click.ClickException, match="API key"):
with pytest.raises(click.ClickException, match="credentials"):
_create_host_backend_client(
host_url="https://api.example.com",
api_key=None,
@@ -0,0 +1,228 @@
import json
from datetime import datetime, timedelta, timezone
import click
import httpx
import pytest
from langgraph_cli import deploy as deploy_mod
from langgraph_cli import langsmith_auth
from langgraph_cli.deploy import _create_host_backend_client
from langgraph_cli.host_backend import HostBackendClient
API_URL = "https://api.smith.langchain.com"
def _iso(delta: timedelta) -> str:
return (datetime.now(timezone.utc) + delta).strftime("%Y-%m-%dT%H:%M:%SZ")
@pytest.fixture
def config_file(tmp_path, monkeypatch):
path = tmp_path / "config.json"
monkeypatch.setenv("LANGSMITH_CONFIG_FILE", str(path))
monkeypatch.delenv("LANGSMITH_PROFILE", raising=False)
for name in ("LANGGRAPH_HOST_API_KEY", "LANGSMITH_API_KEY", "LANGCHAIN_API_KEY"):
monkeypatch.delenv(name, raising=False)
return path
def _write_profile(path, **oauth):
path.write_text(
json.dumps(
{
"current_profile": "default",
"profiles": {
"default": {
"api_url": API_URL,
"workspace_id": "ws-1",
"oauth": oauth,
}
},
}
)
)
def _no_discovery(monkeypatch):
def get(*_a, **_k):
raise httpx.ConnectError("no discovery")
monkeypatch.setattr(httpx, "get", get)
def test_valid_token_is_returned_without_network(config_file, monkeypatch):
_write_profile(
config_file, access_token="tok", expires_at=_iso(timedelta(minutes=5))
)
def boom(*_a, **_k):
raise AssertionError("no network expected")
monkeypatch.setattr(httpx, "get", boom)
monkeypatch.setattr(httpx, "post", boom)
assert langsmith_auth.get_access_token(API_URL) == "tok"
def test_expired_token_is_refreshed_and_saved(config_file, monkeypatch):
_write_profile(
config_file,
access_token="old",
refresh_token="refresh-1",
expires_at=_iso(timedelta(seconds=-5)),
)
_no_discovery(monkeypatch)
seen = {}
def post(url, data, timeout):
seen.update(url=url, data=data)
return httpx.Response(
200,
json={
"access_token": "new",
"refresh_token": "refresh-2",
"expires_in": 300,
},
request=httpx.Request("POST", url),
)
monkeypatch.setattr(httpx, "post", post)
assert langsmith_auth.get_access_token(API_URL) == "new"
assert seen["url"] == f"{API_URL}/oauth/token"
assert seen["data"]["refresh_token"] == "refresh-1"
saved = json.loads(config_file.read_text())["profiles"]["default"]["oauth"]
assert saved["access_token"] == "new"
assert saved["refresh_token"] == "refresh-2"
def test_expired_token_without_refresh_token_asks_to_log_in(config_file):
_write_profile(
config_file, access_token="old", expires_at=_iso(timedelta(seconds=-5))
)
with pytest.raises(click.ClickException, match="langgraph login"):
langsmith_auth.get_access_token(API_URL)
def test_login_stores_tokens_after_pending(config_file, monkeypatch, capsys):
_no_discovery(monkeypatch)
monkeypatch.setattr(langsmith_auth.time, "sleep", lambda _s: None)
monkeypatch.setattr(langsmith_auth.webbrowser, "open", lambda _u: True)
token_responses = iter(
[
{"error": "authorization_pending"},
{"access_token": "tok", "refresh_token": "r", "expires_in": 300},
]
)
def post(url, data, timeout):
request = httpx.Request("POST", url)
if url.endswith("/oauth/device/code"):
return httpx.Response(
200,
json={
"device_code": "dc",
"user_code": "ABCD",
"verification_uri": "https://example.com/activate",
"expires_in": 600,
"interval": 5,
},
request=request,
)
body = next(token_responses)
return httpx.Response(
400 if "error" in body else 200, json=body, request=request
)
monkeypatch.setattr(httpx, "post", post)
langsmith_auth.login(API_URL)
profile = json.loads(config_file.read_text())["profiles"]["default"]
assert profile["api_url"] == API_URL
assert profile["oauth"]["access_token"] == "tok"
assert capsys.readouterr().out.endswith(
f'Logged in to {API_URL} as profile "default"\n'
)
@pytest.mark.parametrize(
("terminal", "term", "no_color", "celebrate", "colored"),
[
(True, "xterm-256color", "", True, True),
(True, "xterm-256color", "1", True, False),
(True, "dumb", "", False, False),
(False, "xterm-256color", "", False, False),
],
)
def test_login_success_output(
config_file, monkeypatch, capsys, terminal, term, no_color, celebrate, colored
):
monkeypatch.setenv("LANGSMITH_PROFILE", "work")
monkeypatch.setenv("TERM", term)
monkeypatch.setenv("NO_COLOR", no_color)
monkeypatch.setattr(langsmith_auth.sys.stdout, "isatty", lambda: terminal)
langsmith_auth._write_login_success(API_URL)
output = capsys.readouterr().out
assert ("\x1b[" in output) == colored
if celebrate:
for text in (
"▄█▄",
"✓ You're in.",
'Logged in as profile "work".',
"Ready when you are.",
f"API {API_URL}",
):
assert text in click.unstyle(output)
else:
assert output == f'Logged in to {API_URL} as profile "work"\n'
def test_client_sends_bearer_token_per_request():
seen = []
def handler(req: httpx.Request) -> httpx.Response:
seen.append(dict(req.headers))
return httpx.Response(200, json={"resources": []})
tokens = iter(["t1", "t2"])
client = HostBackendClient(
"https://api.example.com",
None,
access_token=lambda: next(tokens),
transport=httpx.MockTransport(handler),
)
client.list_deployments()
client.list_deployments()
assert [h["authorization"] for h in seen] == ["Bearer t1", "Bearer t2"]
assert "x-api-key" not in seen[0]
def test_factory_uses_logged_in_profile(config_file, monkeypatch):
_write_profile(
config_file, access_token="tok", expires_at=_iso(timedelta(minutes=5))
)
monkeypatch.setattr(deploy_mod, "_no_input", True)
monkeypatch.delenv("LANGSMITH_TENANT_ID", raising=False)
monkeypatch.delenv("LANGSMITH_ENDPOINT", raising=False)
client = _create_host_backend_client(
host_url="https://api.example.com", api_key=None, env_vars={}
)
assert client._access_token is not None
assert client._access_token() == "tok"
assert client._client.headers["x-tenant-id"] == "ws-1"
assert "x-api-key" not in client._client.headers
def test_factory_without_credentials_and_no_input_fails(config_file, monkeypatch):
monkeypatch.setattr(deploy_mod, "_no_input", True)
with pytest.raises(click.ClickException, match="langgraph login"):
_create_host_backend_client(
host_url="https://api.example.com", api_key=None, env_vars={}
)
+6 -6
View File
@@ -250,7 +250,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.19"
version = "1.2.11"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain-core" },
@@ -260,9 +260,9 @@ dependencies = [
{ name = "pydantic" },
{ name = "xxhash" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ba/10/329dc49ce3fc40e36faafd407f08607fe3fab75613e709a66758b53faac2/langgraph-1.2.19.tar.gz", hash = "sha256:4a00fa1aad2b5fc2d3a789a8e9eaf993f76218ca924b434902ecf1bbaaf300f6", size = 782618, upload-time = "2026-10-30T22:23:32.962Z" }
sdist = { url = "https://files.pythonhosted.org/packages/56/0d/c8e7ee98896659e1b6555db0ab115a9ca899844744645d5d894032bab1d7/langgraph-1.2.11.tar.gz", hash = "sha256:9ecfe11e50d338b34b15cf4d8a442642de103e8ae6971320efba84e4542eb363", size = 725753, upload-time = "2026-08-11T14:00:36.945Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d7/9e/bd29e368fb16feea8d24270d377d56c34b49afea371717124f19cdfa890c/langgraph-1.2.19-py3-none-any.whl", hash = "sha256:6094a5cbed52a77d8c74c276fcd2056abde7b2836eab1c7a6b74bacbd1a269b3", size = 262285, upload-time = "2026-10-30T22:23:31.334Z" },
{ url = "https://files.pythonhosted.org/packages/0a/7f/c5c30e4be99ff821029c7ac872a480676bb179c9f3df85ea3f38d13f86d4/langgraph-1.2.11-py3-none-any.whl", hash = "sha256:8bab70de7b2d00b5300fb289bcf38d8b241400f3184c1e95e8ce706fb0e8686b", size = 248854, upload-time = "2026-08-11T14:00:35.494Z" },
]
[[package]]
@@ -308,7 +308,7 @@ wheels = [
[[package]]
name = "langgraph-sdk"
version = "0.4.11"
version = "0.4.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -317,9 +317,9 @@ dependencies = [
{ name = "orjson" },
{ name = "websockets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/64/a8/d5807df5976f2b8e181baddbddf857cc0ba501bba274e81711b10385c65b/langgraph_sdk-0.4.11.tar.gz", hash = "sha256:2550af3bc7cb78ac4facb97d141ec1a76568fa3eb3d29040cff70b45bda84740", size = 366656, upload-time = "2026-10-30T22:17:49.202Z" }
sdist = { url = "https://files.pythonhosted.org/packages/3f/ae/91446c1fffa04a2dc1f81afbfb5bfff3590452891a72bd9098a656ab2657/langgraph_sdk-0.4.4.tar.gz", hash = "sha256:4e651ffa09de695681579396375377bdde23bedbc8e35b070e615cbd5af7da8b", size = 345789, upload-time = "2026-08-27T21:25:22.593Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/01/88/69899bb20b96b80468e98b90377e605c36658125e7e7fba401868a86138e/langgraph_sdk-0.4.11-py3-none-any.whl", hash = "sha256:6a65be4a8f825966a07317b8e34e3337cd227b9b58b185f0123fd13c3d0116fa", size = 169311, upload-time = "2026-10-30T22:17:47.96Z" },
{ url = "https://files.pythonhosted.org/packages/43/d5/2ad7f3a835c6fcebf58b6669552536ecd52d2dfe4cf49c6c36648fd83572/langgraph_sdk-0.4.4-py3-none-any.whl", hash = "sha256:39afe416c91742925e6f8a93715f566d499b36e1b636b804a4ffe3190e4f4e64", size = 162270, upload-time = "2026-08-27T21:25:21.072Z" },
]
[[package]]
+10 -10
View File
@@ -1,5 +1,5 @@
version = 1
revision = 5
revision = 3
requires-python = ">=3.11"
[[package]]
@@ -224,7 +224,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.19"
version = "1.2.11"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain-core" },
@@ -234,22 +234,22 @@ dependencies = [
{ name = "pydantic" },
{ name = "xxhash" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ba/10/329dc49ce3fc40e36faafd407f08607fe3fab75613e709a66758b53faac2/langgraph-1.2.19.tar.gz", hash = "sha256:4a00fa1aad2b5fc2d3a789a8e9eaf993f76218ca924b434902ecf1bbaaf300f6", size = 782618, upload-time = "2026-10-30T22:23:32.962Z" }
sdist = { url = "https://files.pythonhosted.org/packages/56/0d/c8e7ee98896659e1b6555db0ab115a9ca899844744645d5d894032bab1d7/langgraph-1.2.11.tar.gz", hash = "sha256:9ecfe11e50d338b34b15cf4d8a442642de103e8ae6971320efba84e4542eb363", size = 725753, upload-time = "2026-08-11T14:00:36.945Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d7/9e/bd29e368fb16feea8d24270d377d56c34b49afea371717124f19cdfa890c/langgraph-1.2.19-py3-none-any.whl", hash = "sha256:6094a5cbed52a77d8c74c276fcd2056abde7b2836eab1c7a6b74bacbd1a269b3", size = 262285, upload-time = "2026-10-30T22:23:31.334Z" },
{ url = "https://files.pythonhosted.org/packages/0a/7f/c5c30e4be99ff821029c7ac872a480676bb179c9f3df85ea3f38d13f86d4/langgraph-1.2.11-py3-none-any.whl", hash = "sha256:8bab70de7b2d00b5300fb289bcf38d8b241400f3184c1e95e8ce706fb0e8686b", size = 248854, upload-time = "2026-08-11T14:00:35.494Z" },
]
[[package]]
name = "langgraph-checkpoint"
version = "4.3.1"
version = "4.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain-core" },
{ name = "ormsgpack" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f0/c4/f4107f50dd07169d08d95766cd69104231e694f44a1b25b68ad8b393712d/langgraph_checkpoint-4.3.1.tar.gz", hash = "sha256:625e7c78a72b33e7c47f7e87ca051060fb90aa66f5fd551c472c08376fb669cb", size = 182679, upload-time = "2026-10-26T19:57:31.416Z" }
sdist = { url = "https://files.pythonhosted.org/packages/83/47/886af6f886f0bff2273164a45f008694e48a96ff3cd25ff0228f2aa9480e/langgraph_checkpoint-4.1.1.tar.gz", hash = "sha256:6c2bdb530c91f91d7d9c1bd100925d0fc4f498d418c17f3587d1526279482a25", size = 184020, upload-time = "2026-05-22T16:57:38.503Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f3/40/4251d676b5c585d42175c901175d4bff56e22939625e18bcef781fc3e893/langgraph_checkpoint-4.3.1-py3-none-any.whl", hash = "sha256:215a826935c8db4d9676e79fd95943e5b3847d78604390898e1d98581cc594e5", size = 58093, upload-time = "2026-10-26T19:57:30.361Z" },
{ url = "https://files.pythonhosted.org/packages/bd/b4/71425e3e38be92611300b9cc5e46a5bf98ab23f5ea8a75b73d02a2f1413c/langgraph_checkpoint-4.1.1-py3-none-any.whl", hash = "sha256:25d29144b082827218e7bc3f1e9b0566a4bb007895cd6cc26f66a8428739f56e", size = 56212, upload-time = "2026-05-22T16:57:37.203Z" },
]
[[package]]
@@ -267,7 +267,7 @@ wheels = [
[[package]]
name = "langgraph-sdk"
version = "0.4.11"
version = "0.4.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -276,9 +276,9 @@ dependencies = [
{ name = "orjson" },
{ name = "websockets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/64/a8/d5807df5976f2b8e181baddbddf857cc0ba501bba274e81711b10385c65b/langgraph_sdk-0.4.11.tar.gz", hash = "sha256:2550af3bc7cb78ac4facb97d141ec1a76568fa3eb3d29040cff70b45bda84740", size = 366656, upload-time = "2026-10-30T22:17:49.202Z" }
sdist = { url = "https://files.pythonhosted.org/packages/3f/ae/91446c1fffa04a2dc1f81afbfb5bfff3590452891a72bd9098a656ab2657/langgraph_sdk-0.4.4.tar.gz", hash = "sha256:4e651ffa09de695681579396375377bdde23bedbc8e35b070e615cbd5af7da8b", size = 345789, upload-time = "2026-08-27T21:25:22.593Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/01/88/69899bb20b96b80468e98b90377e605c36658125e7e7fba401868a86138e/langgraph_sdk-0.4.11-py3-none-any.whl", hash = "sha256:6a65be4a8f825966a07317b8e34e3337cd227b9b58b185f0123fd13c3d0116fa", size = 169311, upload-time = "2026-10-30T22:17:47.96Z" },
{ url = "https://files.pythonhosted.org/packages/43/d5/2ad7f3a835c6fcebf58b6669552536ecd52d2dfe4cf49c6c36648fd83572/langgraph_sdk-0.4.4-py3-none-any.whl", hash = "sha256:39afe416c91742925e6f8a93715f566d499b36e1b636b804a4ffe3190e4f4e64", size = 162270, upload-time = "2026-08-27T21:25:21.072Z" },
]
[[package]]
+16 -31
View File
@@ -992,11 +992,9 @@ wheels = [
[[package]]
name = "langchain-core"
version = "1.7.3"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "httpx" },
{ name = "jsonpatch" },
{ name = "langchain-protocol" },
{ name = "langsmith" },
@@ -1007,26 +1005,26 @@ dependencies = [
{ name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" } },
{ name = "uuid-utils" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ac/90/f932d9c82d15033ae9676fb3670993e10e1b4d9c59fe1b7ae024dee11955/langchain_core-1.7.3.tar.gz", hash = "sha256:77f70b81e238c981bfd8405840b03fdf143735ac93c0729c2bdb39d6f0648024", size = 1029116, upload-time = "2026-10-29T20:55:56.42Z" }
sdist = { url = "https://files.pythonhosted.org/packages/59/de/679a53472c25860837e32c0442c962fa86e95317a36460e2c9d5c91b17c2/langchain_core-1.4.0.tar.gz", hash = "sha256:1dc341eed802ed9c117c0df3923c991e5e9e226571e5725c194eeb5bd93d1a7f", size = 920260, upload-time = "2026-05-11T18:42:35.919Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b4/c6/88bcc77899601e09a428d9c764e41ac3e7ede161384f85b9a2a8a828f81d/langchain_core-1.7.3-py3-none-any.whl", hash = "sha256:460c214bf31495d7b7e0a879e408dbfda677fd53fd879da786c1b966a4f6bff6", size = 577074, upload-time = "2026-10-29T20:55:54.722Z" },
{ url = "https://files.pythonhosted.org/packages/0f/1a/86c38c27b81913a1c6c12448cab55defb5a1097c7dc9a4cea83f55477a2d/langchain_core-1.4.0-py3-none-any.whl", hash = "sha256:23cbbdb46e38ddd1dd5247e6167e96013eae74bea4c5949c550809970a9e565c", size = 548120, upload-time = "2026-05-11T18:42:33.992Z" },
]
[[package]]
name = "langchain-protocol"
version = "0.0.20"
version = "0.0.17"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" } },
]
sdist = { url = "https://files.pythonhosted.org/packages/cb/8b/d908d082c3cf5368ddd1816ed114de79c873f979ba7f98b95fab0e841643/langchain_protocol-0.0.20.tar.gz", hash = "sha256:0f5bd002474265648e91161771a9e0438fe9480bd55a416e51af0f60070637ec", size = 6280, upload-time = "2026-10-20T11:13:50.84Z" }
sdist = { url = "https://files.pythonhosted.org/packages/44/b3/4e2429876c7a35585618caa2b9f9089f7162a6b50562b614ad82ac11c17e/langchain_protocol-0.0.17.tar.gz", hash = "sha256:e7cbe58c205df4b4fd87dc6d5bb23f10e13b236d0e2e1b0b9d05bc2b648f3eea", size = 6026, upload-time = "2026-06-12T18:39:51.923Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/02/4b/f1c99a0b27c5ff8b4b131c0d6e62012c17cc424089efe213767db927afd2/langchain_protocol-0.0.20-py3-none-any.whl", hash = "sha256:3c935e5e46b0f6f9cb52f45ba7cfa53d1b9fa80c82c4a277d47f03e07f7385b1", size = 7347, upload-time = "2026-10-20T11:13:49.566Z" },
{ url = "https://files.pythonhosted.org/packages/13/0a/a1bfe72c6ec856e99773bbd96c8086421e554b3693d0142b9ea009c6ac92/langchain_protocol-0.0.17-py3-none-any.whl", hash = "sha256:982a08fe152586ed10d4ff3d538c2e0b5766e5f307cdea325e10be3f2c17cae6", size = 7096, upload-time = "2026-06-12T18:39:50.973Z" },
]
[[package]]
name = "langgraph"
version = "1.2.14"
version = "1.2.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain-core" },
@@ -1036,9 +1034,9 @@ dependencies = [
{ name = "pydantic" },
{ name = "xxhash" },
]
sdist = { url = "https://files.pythonhosted.org/packages/49/3f/6e60789eab2c5ceaf34ba2c626ac345df93b026bbe7385018aa4b9c30397/langgraph-1.2.14.tar.gz", hash = "sha256:5b1a72b7a7f994ac6947b7751fbbe00e949f8bb44d9ab78881604b703247ae55", size = 752793, upload-time = "2026-10-06T14:41:19.789Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5c/55/e42f59b5e4cce75085f68abb77114c4d1152628157bd60c2587881bca559/langgraph-1.2.3.tar.gz", hash = "sha256:7f89cd5f0946fe29bd7ca048e2a84d3c14e7f652e38bb98e00f0ba8b7004b9d0", size = 718812, upload-time = "2026-06-01T18:55:54.458Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/4a/f976df0d3cf7fb7986dc39f656c6b7b5655834430e9bdfaef1e1e4fffaba/langgraph-1.2.14-py3-none-any.whl", hash = "sha256:e3f5f8ff4259f9281b73ff50fc9ef5cc3b025b8936b8283ecdca7cc0c4128690", size = 255980, upload-time = "2026-10-06T14:41:18.129Z" },
{ url = "https://files.pythonhosted.org/packages/39/9b/287f11be799f2f4dd289ab19d23788df22ac20ce884a010d0696f79016db/langgraph-1.2.3-py3-none-any.whl", hash = "sha256:22e6c89084adb3edb7855f6ffa692af3d75925f9c70deb43b86da14db1b02c78", size = 244841, upload-time = "2026-06-01T18:55:52.945Z" },
]
[[package]]
@@ -1211,7 +1209,7 @@ wheels = [
[[package]]
name = "langgraph-sdk"
version = "0.4.6"
version = "0.4.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
@@ -1220,34 +1218,30 @@ dependencies = [
{ name = "orjson" },
{ name = "websockets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/78/85/54edf4902c0378510f09a009f64e3d3f5644aeeb51d13cf67ec380a5245b/langgraph_sdk-0.4.6.tar.gz", hash = "sha256:ea5b1749d70987d741c9b9fe2b7e1c5f7da7e4d102f64e390333c1890c9bbd5b", size = 350632, upload-time = "2026-10-06T14:17:53.641Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/9c/7ff305b366ce986052a27dc5946839bef2823b3567664cbad22e294dff5a/langgraph_sdk-0.4.5.tar.gz", hash = "sha256:d49a98a2ee8e0c494b101a7caf8061c8b4b94d3ee5ada0ea2dbe50903a5487b1", size = 349783, upload-time = "2026-09-21T14:42:55.732Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6c/c7/69d13026db3577c157877dea47a9dc5fffd3dcde2d1ab8009fa8f7e5f6a6/langgraph_sdk-0.4.6-py3-none-any.whl", hash = "sha256:57122b02bda41d37a97a5e09eb018aa8c9b3dc85c433880073779bd9324b7eea", size = 162390, upload-time = "2026-10-06T14:17:52.134Z" },
{ url = "https://files.pythonhosted.org/packages/49/a9/71a6499f3481bc00bf4f42a17cb81caf19a02fce762db356626d7214e2c2/langgraph_sdk-0.4.5-py3-none-any.whl", hash = "sha256:e03ef033a20d21288af496e9b6c08ae3afcc1b58dc5abcd37575bfd1a93045de", size = 162304, upload-time = "2026-09-21T14:42:54.623Z" },
]
[[package]]
name = "langsmith"
version = "0.14.16"
version = "0.8.18"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "distro" },
{ name = "httpx2" },
{ name = "httpx" },
{ name = "orjson", marker = "platform_python_implementation != 'PyPy'" },
{ name = "packaging" },
{ name = "pydantic" },
{ name = "requests" },
{ name = "requests-toolbelt" },
{ name = "sniffio" },
{ name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" } },
{ name = "uuid-utils" },
{ name = "websockets" },
{ name = "xxhash" },
{ name = "zstandard" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f5/45/576803a72da96d9c0866b3815f08f447ed16c14acb52174129daca6847d6/langsmith-0.14.16.tar.gz", hash = "sha256:4e5816cbe1541f4d04d71f652eb8f4137889861bebcf7137bcba3553ae9e335e", size = 5096529, upload-time = "2026-10-29T17:53:27.747Z" }
sdist = { url = "https://files.pythonhosted.org/packages/9a/d9/a6681aa9847bbbc5ec21abe20a5e233b94e5edcfe39624db607ac7e8ccb4/langsmith-0.8.18.tar.gz", hash = "sha256:32dde9c0e67e053e0fb738921fc8ced768af7b8fa83d7a0e3fd63597cf8776dd", size = 4526988, upload-time = "2026-06-19T13:12:17.123Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/11/3a/9a072c02a22089ddf20f0f33ea95bee5a5951b31839f118b04f27b94be6e/langsmith-0.14.16-py3-none-any.whl", hash = "sha256:b9ea1a57622b0788b181d78605f974fb206c5f6f75793076ffec23cef4d904c8", size = 933680, upload-time = "2026-10-29T17:53:25.465Z" },
{ url = "https://files.pythonhosted.org/packages/03/70/0e0cc80a3b064c8d6c8d697c3125ed86e39d5a7393ec6dc8b07cb1cf13c4/langsmith-0.8.18-py3-none-any.whl", hash = "sha256:3940183349993faef48e6c7d08e4822ee9cefd906b362d0e3c2d650314d2f282", size = 508108, upload-time = "2026-06-19T13:12:15.348Z" },
]
[package.optional-dependencies]
@@ -2099,15 +2093,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
[[package]]
name = "sse-starlette"
version = "3.3.4"
@@ -74,8 +74,6 @@ CONFIG_KEY_RUNTIME = sys.intern("__pregel_runtime")
# holds a `Runtime` instance with context, store, stream writer, etc.
CONFIG_KEY_RESUME_MAP = sys.intern("__pregel_resume_map")
# holds a mapping of task ns -> resume value for resuming tasks
CONFIG_KEY_STRICT_RESUME = sys.intern("__pregel_strict_resume")
# when True, the root graph refuses a resume that can't be matched to its pending interrupts
CONFIG_KEY_STREAM_MESSAGES_V2 = sys.intern("__pregel_stream_messages_v2")
# when True, attach StreamMessagesHandlerV2 so content-block (v2) events
# flow through stream_mode="messages"; set by StreamingHandler only.
@@ -136,7 +134,6 @@ RESERVED = {
CONFIG_KEY_CHECKPOINT_NS,
CONFIG_KEY_TIMED_ATTEMPT_OBSERVER,
CONFIG_KEY_RESUME_MAP,
CONFIG_KEY_STRICT_RESUME,
CONFIG_KEY_STREAM_MESSAGES_V2,
# other constants
PUSH,
+60 -119
View File
@@ -1,7 +1,6 @@
from __future__ import annotations
import asyncio
import copy
import enum
import inspect
import logging
@@ -24,16 +23,9 @@ from typing import (
Optional,
Protocol,
TypeGuard,
TypeVar,
cast,
)
from langchain_core.callbacks.base import BaseCallbackHandler, BaseCallbackManager
from langchain_core.callbacks.manager import (
AsyncCallbackManagerForChainRun,
BaseRunManager,
CallbackManagerForChainRun,
)
from langchain_core.runnables.base import (
Runnable,
RunnableConfig,
@@ -49,7 +41,6 @@ from langchain_core.runnables.config import (
var_child_runnable_config,
)
from langchain_core.runnables.utils import Input, Output
from langchain_core.tracers.base import BaseTracer
from langchain_core.tracers.langchain import LangChainTracer
from langgraph.store.base import BaseStore
@@ -66,7 +57,7 @@ from langgraph._internal._constants import (
)
from langgraph._internal._typing import MISSING
from langgraph.errors import NodeError
from langgraph.types import StreamWriter, TracePolicy
from langgraph.types import StreamWriter
try:
from langchain_core.tracers._streaming import _StreamingCallbackHandler
@@ -94,29 +85,6 @@ def _trace_payload(value: Any, transform: Callable[[Any], Any] | None) -> Any:
return value
def _is_stream_handler(handler: BaseCallbackHandler) -> bool:
# Inheritance, not isinstance on the runtime-checkable protocol, which also
# matches any exporter that merely has the tap methods: stream handlers get a
# node's real payload and its streamed output.
return _StreamingCallbackHandler in type(handler).__mro__
_M = TypeVar("_M", bound=BaseCallbackManager | BaseRunManager)
def _split_handlers(manager: _M) -> tuple[_M, _M]:
# The recorded payload exists to keep tracer ingest small, so only tracers get it.
# `astream_log`'s handler is a tracer too, but it feeds the caller's own stream.
real, tracers = copy.copy(manager), copy.copy(manager)
tracers.handlers = [
h
for h in manager.handlers
if isinstance(h, BaseTracer) and not _is_stream_handler(h)
]
real.handlers = [h for h in manager.handlers if h not in tracers.handlers]
return real, tracers
def _set_config_context(
config: RunnableConfig, run: Any = None
) -> Token[RunnableConfig | None]:
@@ -625,15 +593,13 @@ class RunnableSeq(Runnable):
*steps: RunnableLike,
name: str | None = None,
trace_inputs: Callable[[Any], Any] | None = None,
trace_policy: TracePolicy | None = None,
trace_outputs: Callable[[Any], Any] | None = None,
) -> None:
"""Create a new RunnableSeq.
Args:
steps: The steps to include in the sequence.
name: The name of the `Runnable`.
trace_inputs: Transforms the input every callback handler receives.
trace_policy: Processes what tracers record, after `trace_inputs`.
Raises:
ValueError: If the sequence has less than 2 steps.
@@ -653,67 +619,7 @@ class RunnableSeq(Runnable):
self.steps = steps_flat
self.name = name
self.trace_inputs = trace_inputs
self.trace_policy = trace_policy or TracePolicy()
def _start(self, input: Any, config: RunnableConfig) -> CallbackManagerForChainRun:
callback_manager = get_callback_manager_for_config(config)
payload = _trace_payload(input, self.trace_inputs)
recorded = _trace_payload(payload, self.trace_policy.process_inputs)
name = config.get("run_name") or self.get_name()
run_id = config.pop("run_id", None)
if recorded is payload:
return callback_manager.on_chain_start(
None, payload, name=name, run_id=run_id
)
real, tracers = _split_handlers(callback_manager)
run_manager = real.on_chain_start(None, payload, name=name, run_id=run_id)
tracers.on_chain_start(None, recorded, name=name, run_id=run_manager.run_id)
run_manager.handlers = callback_manager.handlers
return run_manager
async def _astart(
self, input: Any, config: RunnableConfig
) -> AsyncCallbackManagerForChainRun:
callback_manager = get_async_callback_manager_for_config(config)
payload = _trace_payload(input, self.trace_inputs)
recorded = _trace_payload(payload, self.trace_policy.process_inputs)
name = config.get("run_name") or self.get_name()
run_id = config.pop("run_id", None)
if recorded is payload:
return await callback_manager.on_chain_start(
None, payload, name=name, run_id=run_id
)
real, tracers = _split_handlers(callback_manager)
# Tracers start last, so a cancellation before then leaves no tracer run open.
run_manager = await real.on_chain_start(None, payload, name=name, run_id=run_id)
await tracers.on_chain_start(
None, recorded, name=name, run_id=run_manager.run_id
)
run_manager.handlers = callback_manager.handlers
return run_manager
def _end(self, run_manager: CallbackManagerForChainRun, output: Any) -> None:
recorded = _trace_payload(output, self.trace_policy.process_outputs)
if recorded is output:
run_manager.on_chain_end(output)
else:
real, tracers = _split_handlers(run_manager)
tracers.on_chain_end(recorded)
real.on_chain_end(output)
async def _aend(
self, run_manager: AsyncCallbackManagerForChainRun, output: Any
) -> None:
recorded = _trace_payload(output, self.trace_policy.process_outputs)
if recorded is output:
await run_manager.on_chain_end(output)
else:
real, tracers = _split_handlers(run_manager)
# Tracers end first: on_chain_end is shielded once it starts, so a
# cancellation can only skip the other handlers' end, never leave a
# tracer run open.
await tracers.on_chain_end(recorded)
await real.on_chain_end(output)
self.trace_outputs = trace_outputs
def __or__(
self,
@@ -770,8 +676,15 @@ class RunnableSeq(Runnable):
) -> Any:
if config is None:
config = ensure_config()
# setup callbacks and context
callback_manager = get_callback_manager_for_config(config)
# start the root run
run_manager = self._start(input, config)
run_manager = callback_manager.on_chain_start(
None,
_trace_payload(input, self.trace_inputs),
name=config.get("run_name") or self.get_name(),
run_id=config.pop("run_id", None),
)
# invoke all steps in sequence
try:
for i, step in enumerate(self.steps):
@@ -799,7 +712,7 @@ class RunnableSeq(Runnable):
run_manager.on_chain_error(e)
raise
else:
self._end(run_manager, input)
run_manager.on_chain_end(_trace_payload(input, self.trace_outputs))
return input
async def ainvoke(
@@ -810,8 +723,15 @@ class RunnableSeq(Runnable):
) -> Any:
if config is None:
config = ensure_config()
# setup callbacks
callback_manager = get_async_callback_manager_for_config(config)
# start the root run
run_manager = await self._astart(input, config)
run_manager = await callback_manager.on_chain_start(
None,
_trace_payload(input, self.trace_inputs),
name=config.get("run_name") or self.get_name(),
run_id=config.pop("run_id", None),
)
# invoke all steps in sequence
try:
@@ -845,7 +765,7 @@ class RunnableSeq(Runnable):
await run_manager.on_chain_error(e)
raise
else:
await self._aend(run_manager, input)
await run_manager.on_chain_end(_trace_payload(input, self.trace_outputs))
return input
def stream(
@@ -856,8 +776,15 @@ class RunnableSeq(Runnable):
) -> Iterator[Any]:
if config is None:
config = ensure_config()
# setup callbacks
callback_manager = get_callback_manager_for_config(config)
# start the root run
run_manager = self._start(input, config)
run_manager = callback_manager.on_chain_start(
None,
_trace_payload(input, self.trace_inputs),
name=config.get("run_name") or self.get_name(),
run_id=config.pop("run_id", None),
)
# get the run object
for h in run_manager.handlers:
if isinstance(h, LangChainTracer):
@@ -887,9 +814,10 @@ class RunnableSeq(Runnable):
)
iterator = step.transform(iterator, config)
# populates streamed_output in astream_log() output if needed
for h in run_manager.handlers:
if _is_stream_handler(h):
iterator = h.tap_output_iter(run_manager.run_id, iterator)
if _StreamingCallbackHandler is not None:
for h in run_manager.handlers:
if isinstance(h, _StreamingCallbackHandler):
iterator = h.tap_output_iter(run_manager.run_id, iterator)
# consume into final output
output = context.run(_consume_iter, iterator)
# sequence doesn't emit output, yield to mark as generator
@@ -898,7 +826,7 @@ class RunnableSeq(Runnable):
run_manager.on_chain_error(e)
raise
else:
self._end(run_manager, output)
run_manager.on_chain_end(_trace_payload(output, self.trace_outputs))
async def astream(
self,
@@ -908,8 +836,15 @@ class RunnableSeq(Runnable):
) -> AsyncIterator[Any]:
if config is None:
config = ensure_config()
# setup callbacks
callback_manager = get_async_callback_manager_for_config(config)
# start the root run
run_manager = await self._astart(input, config)
run_manager = await callback_manager.on_chain_start(
None,
_trace_payload(input, self.trace_inputs),
name=config.get("run_name") or self.get_name(),
run_id=config.pop("run_id", None),
)
# stream the last steps
# transform the input stream of each step with the next
# steps that don't natively support transforming an input stream will
@@ -945,11 +880,12 @@ class RunnableSeq(Runnable):
if hasattr(aiterator, "aclose"):
stack.push_async_callback(aiterator.aclose)
# populates streamed_output in astream_log() output if needed
for h in run_manager.handlers:
if _is_stream_handler(h):
aiterator = h.tap_output_aiter(
run_manager.run_id, aiterator
)
if _StreamingCallbackHandler is not None:
for h in run_manager.handlers:
if isinstance(h, _StreamingCallbackHandler):
aiterator = h.tap_output_aiter(
run_manager.run_id, aiterator
)
# consume into final output
output = await asyncio.create_task(
_consume_aiter(aiterator), context=context
@@ -960,7 +896,9 @@ class RunnableSeq(Runnable):
await run_manager.on_chain_error(e)
raise
else:
await self._aend(run_manager, output)
await run_manager.on_chain_end(
_trace_payload(output, self.trace_outputs)
)
else:
try:
async with AsyncExitStack() as stack:
@@ -976,11 +914,12 @@ class RunnableSeq(Runnable):
if hasattr(aiterator, "aclose"):
stack.push_async_callback(aiterator.aclose)
# populates streamed_output in astream_log() output if needed
for h in run_manager.handlers:
if _is_stream_handler(h):
aiterator = h.tap_output_aiter(
run_manager.run_id, aiterator
)
if _StreamingCallbackHandler is not None:
for h in run_manager.handlers:
if isinstance(h, _StreamingCallbackHandler):
aiterator = h.tap_output_aiter(
run_manager.run_id, aiterator
)
# consume into final output
output = await _consume_aiter(aiterator)
# sequence doesn't emit output, yield to mark as generator
@@ -989,7 +928,9 @@ class RunnableSeq(Runnable):
await run_manager.on_chain_error(e)
raise
else:
await self._aend(run_manager, output)
await run_manager.on_chain_end(
_trace_payload(output, self.trace_outputs)
)
def _consume_iter(it: Iterator[Any]) -> Any:
-40
View File
@@ -28,7 +28,6 @@ __all__ = (
"ParentCommand",
"EmptyInputError",
"TaskNotFound",
"UnknownInterruptError",
"is_invalid_resume",
)
@@ -147,45 +146,6 @@ class TaskNotFound(Exception):
pass
class UnknownInterruptError(Exception):
"""Raised in strict resume mode when a resume doesn't match the pending interrupts.
`reason` says how it doesn't match:
- `"not_pending"`: the resume names interrupts that aren't pending, listed in
`interrupt_ids`.
- `"empty_map"`: the resume is an empty map.
- `"ambiguous"`: the resume is a single value while several interrupts are
pending.
`pending_ids` lists the interrupts still waiting for an answer. The error is
raised before anything is written, so they can still be answered.
A single value with no interrupt pending is accepted, because it resumes a
graph paused at a static breakpoint (`interrupt_before` or `interrupt_after`).
The interrupts one task raises share an id, so an answer retried after the
task asked its next question answers that question.
"""
reason: Literal["not_pending", "empty_map", "ambiguous"]
interrupt_ids: tuple[str, ...]
pending_ids: tuple[str, ...]
def __init__(
self,
message: str,
*,
reason: Literal["not_pending", "empty_map", "ambiguous"],
interrupt_ids: Sequence[str] = (),
pending_ids: Sequence[str] = (),
) -> None:
super().__init__(message)
self.reason = reason
self.interrupt_ids = tuple(interrupt_ids)
self.pending_ids = tuple(pending_ids)
@dataclass(frozen=True, slots=True)
class NodeError:
"""Failure context passed to a node-level error handler.
+9 -15
View File
@@ -85,7 +85,6 @@ from langgraph.types import (
RetryPolicy,
Send,
TimeoutPolicy,
_interrupt_id,
)
GetNextVersion = Callable[[V | None, None], V]
@@ -627,7 +626,7 @@ def prepare_single_task(
config[CONF].get(CONFIG_KEY_SCRATCHPAD),
pending_writes,
task_id,
task_checkpoint_ns,
xxh3_128_hexdigest(task_checkpoint_ns.encode()),
config[CONF].get(CONFIG_KEY_RESUME_MAP),
step,
stop,
@@ -874,7 +873,7 @@ def prepare_push_task_functional(
configurable.get(CONFIG_KEY_SCRATCHPAD),
pending_writes,
task_id,
task_checkpoint_ns,
xxh3_128_hexdigest(task_checkpoint_ns.encode()),
configurable.get(CONFIG_KEY_RESUME_MAP),
step,
stop,
@@ -1036,7 +1035,7 @@ def prepare_push_task_send(
config[CONF].get(CONFIG_KEY_SCRATCHPAD),
pending_writes,
task_id,
task_checkpoint_ns,
xxh3_128_hexdigest(task_checkpoint_ns.encode()),
config[CONF].get(CONFIG_KEY_RESUME_MAP),
step,
stop,
@@ -1185,7 +1184,7 @@ def prepare_node_error_handler_task(
config[CONF].get(CONFIG_KEY_SCRATCHPAD),
pending_writes,
task_id,
task_checkpoint_ns,
xxh3_128_hexdigest(task_checkpoint_ns.encode()),
config[CONF].get(CONFIG_KEY_RESUME_MAP),
step,
stop,
@@ -1281,7 +1280,7 @@ def _scratchpad(
parent_scratchpad: PregelScratchpad | None,
pending_writes: list[PendingWrite],
task_id: str,
task_checkpoint_ns: str,
namespace_hash: str,
resume_map: dict[str, Any] | None,
step: int,
stop: int,
@@ -1308,15 +1307,10 @@ def _scratchpad(
task_resume_write = []
del w
# find the resume value for the task's next interrupt
if resume_map:
next_id = _interrupt_id(task_checkpoint_ns, len(task_resume_write))
first_id = _interrupt_id(task_checkpoint_ns, 0)
if next_id in resume_map:
task_resume_write.append(resume_map[next_id])
# Older versions gave all of a task's interrupts the first id.
elif first_id in resume_map:
task_resume_write.append(resume_map[first_id])
# find namespace and task-specific resume value
if resume_map and namespace_hash in resume_map:
mapped_resume_write = resume_map[namespace_hash]
task_resume_write.append(mapped_resume_write)
else:
null_resume_write = None
+3 -33
View File
@@ -48,7 +48,6 @@ from langgraph._internal._constants import (
CONFIG_KEY_RUNTIME,
CONFIG_KEY_SCRATCHPAD,
CONFIG_KEY_STREAM,
CONFIG_KEY_STRICT_RESUME,
CONFIG_KEY_TASK_ID,
CONFIG_KEY_THREAD_ID,
ERROR,
@@ -78,7 +77,6 @@ from langgraph.constants import TAG_HIDDEN
from langgraph.errors import (
EmptyInputError,
GraphInterrupt,
UnknownInterruptError,
)
from langgraph.managed.base import (
ManagedValueMapping,
@@ -1026,45 +1024,17 @@ class PregelLoop:
"Cannot use Command(resume=...) without checkpointer"
)
strict = (
self.config[CONF].get(CONFIG_KEY_STRICT_RESUME, False)
# Nested calls inherit the parent's configurable.
and not self.is_nested
# A finished task can rerun and interrupt again on replay, while
# its earlier output still makes its interrupt look answered.
and not self._addressed_checkpoint_superseded
)
if resume_is_map := (
isinstance(resume, dict)
and all(is_xxh3_128_hexdigest(k) for k in resume)
):
if strict:
pending = self._pending_interrupts()
if not resume:
raise UnknownInterruptError(
"The resume map names no interrupt",
reason="empty_map",
pending_ids=sorted(pending),
)
if unknown := sorted(resume.keys() - pending):
raise UnknownInterruptError(
f"Unknown or already-consumed interrupt: {unknown[0]}",
reason="not_pending",
interrupt_ids=unknown,
pending_ids=sorted(pending),
)
self.config[CONF][CONFIG_KEY_RESUME_MAP] = resume
else:
if len(pending := self._pending_interrupts()) > 1:
message = (
if len(self._pending_interrupts()) > 1:
raise RuntimeError(
"When there are multiple pending interrupts, you must specify the interrupt id when resuming. "
"Docs: https://docs.langchain.com/oss/python/langgraph/interrupts#handling-multiple-interrupts."
"Docs: https://docs.langchain.com/oss/python/langgraph/add-human-in-the-loop#resume-multiple-interrupts-with-one-invocation."
)
if strict:
raise UnknownInterruptError(
message, reason="ambiguous", pending_ids=sorted(pending)
)
raise RuntimeError(message)
writes: defaultdict[str, list[tuple[str, Any]]] = defaultdict(list)
# group writes by task ID
+11 -2
View File
@@ -227,14 +227,23 @@ class PregelNode:
def node(self) -> Runnable[Any, Any] | None:
"""Get a runnable that combines `bound` and `writers`."""
writers = self.flat_writers
trace_inputs = self.trace_policy.process_inputs if self.trace_policy else None
trace_outputs = self.trace_policy.process_outputs if self.trace_policy else None
if self.bound is DEFAULT_BOUND and not writers:
return None
elif self.bound is DEFAULT_BOUND and len(writers) == 1:
return writers[0]
elif self.bound is DEFAULT_BOUND:
return RunnableSeq(*writers, trace_policy=self.trace_policy)
return RunnableSeq(
*writers, trace_inputs=trace_inputs, trace_outputs=trace_outputs
)
elif writers:
return RunnableSeq(self.bound, *writers, trace_policy=self.trace_policy)
return RunnableSeq(
self.bound,
*writers,
trace_inputs=trace_inputs,
trace_outputs=trace_outputs,
)
else:
return self.bound
-21
View File
@@ -28,7 +28,6 @@ from langgraph._internal._constants import (
CONFIG_KEY_CHECKPOINT_NS,
CONFIG_KEY_RESUMING,
CONFIG_KEY_RUNTIME,
CONFIG_KEY_SCRATCHPAD,
CONFIG_KEY_SEND,
CONFIG_KEY_STREAM,
CONFIG_KEY_TASK_ID,
@@ -45,7 +44,6 @@ from langgraph.errors import (
NodeTimeoutError,
ParentCommand,
)
from langgraph.pregel._algo import LazyAtomicCounter
from langgraph.pregel.protocol import StreamProtocol
from langgraph.runtime import ExecutionInfo, Runtime
from langgraph.types import Command, PregelExecutableTask, RetryPolicy, TimeoutPolicy
@@ -573,23 +571,6 @@ def _checkpoint_ns_for_parent_command(ns: str) -> str:
return NS_SEP.join(parts)
def _reset_interrupt_counter(config: RunnableConfig) -> RunnableConfig:
# A retry reruns the node from its first line, so its interrupt() calls count
# from the first again to reuse the answers its task already has, rather than
# asking again under an id the next resume can't match.
scratchpad = config[CONF].get(CONFIG_KEY_SCRATCHPAD)
if scratchpad is None:
return config
return patch_configurable(
config,
{
CONFIG_KEY_SCRATCHPAD: replace(
scratchpad, interrupt_counter=LazyAtomicCounter()
)
},
)
def run_with_retry(
task: PregelExecutableTask,
retry_policy: Sequence[RetryPolicy] | None,
@@ -700,7 +681,6 @@ def run_with_retry(
)
# signal subgraphs to resume (if available)
config = patch_configurable(config, {CONFIG_KEY_RESUMING: True})
config = _reset_interrupt_counter(config)
async def arun_with_retry(
@@ -849,7 +829,6 @@ async def arun_with_retry(
)
# signal subgraphs to resume (if available)
config = patch_configurable(config, {CONFIG_KEY_RESUMING: True})
config = _reset_interrupt_counter(config)
def _return_value(task: PregelExecutableTask) -> Any:
+24 -14
View File
@@ -97,7 +97,6 @@ from langgraph._internal._runnable import (
Runnable,
RunnableLike,
RunnableSeq,
_is_stream_handler,
coerce_to_runnable,
)
from langgraph._internal._timeout import coerce_timeout_policy
@@ -204,6 +203,11 @@ from langgraph.types import (
from langgraph.typing import ContextT, InputT, OutputT, StateT
from langgraph.warnings import LangGraphDeprecatedSinceV10
try:
from langchain_core.tracers._streaming import _StreamingCallbackHandler
except ImportError:
_StreamingCallbackHandler = None # type: ignore
__all__ = ("NodeBuilder", "Pregel")
_WriteValue = Callable[[Input], Output] | Any
@@ -2881,14 +2885,12 @@ class Pregel(
# this graph's namespace starts at the root, so a caller's
# handler would take this graph's events for its own graph's.
callback_manager.handlers = [
h
for h in callback_manager.handlers
if not _is_stream_mode_handler(h)
h for h in callback_manager.handlers if not _is_stream_handler(h)
]
callback_manager.inheritable_handlers = [
h
for h in callback_manager.inheritable_handlers
if not _is_stream_mode_handler(h)
if not _is_stream_handler(h)
]
if "ls_integration" not in callback_manager.metadata:
callback_manager.add_metadata({"ls_integration": "langgraph"})
@@ -3298,14 +3300,12 @@ class Pregel(
# this graph's namespace starts at the root, so a caller's
# handler would take this graph's events for its own graph's.
callback_manager.handlers = [
h
for h in callback_manager.handlers
if not _is_stream_mode_handler(h)
h for h in callback_manager.handlers if not _is_stream_handler(h)
]
callback_manager.inheritable_handlers = [
h
for h in callback_manager.inheritable_handlers
if not _is_stream_mode_handler(h)
if not _is_stream_handler(h)
]
if "ls_integration" not in callback_manager.metadata:
callback_manager.add_metadata({"ls_integration": "langgraph"})
@@ -3320,10 +3320,20 @@ class Pregel(
run_id=run_manager.run_id,
)
# if running from astream_log() run each proc with streaming
do_stream = any(
_is_stream_handler(h)
and not isinstance(h, (StreamMessagesHandler, StreamToolCallHandler))
for h in run_manager.handlers
do_stream = (
next(
(
True
for h in run_manager.handlers
if isinstance(h, _StreamingCallbackHandler)
and not isinstance(
h, (StreamMessagesHandler, StreamToolCallHandler)
)
),
False,
)
if _StreamingCallbackHandler is not None
else False
)
if checkpointer is None and durability is not None:
warnings.warn(
@@ -4457,7 +4467,7 @@ def _coerce_checkpoint_values(payload: Any, mapper: Callable[[Any], Any]) -> Non
payload["values"] = mapper(payload["values"])
def _is_stream_mode_handler(handler: object) -> bool:
def _is_stream_handler(handler: object) -> bool:
return isinstance(handler, (StreamMessagesHandler, StreamToolCallHandler))
@@ -50,7 +50,6 @@ from langgraph._internal._constants import (
CONFIG_KEY_CHECKPOINT_MAP,
CONFIG_KEY_CHECKPOINT_NS,
CONFIG_KEY_STREAM,
CONFIG_KEY_STRICT_RESUME,
CONFIG_KEY_TASK_ID,
INTERRUPT,
NS_SEP,
@@ -82,7 +81,6 @@ _CONF_DROPLIST = frozenset(
CONFIG_KEY_CHECKPOINT_ID,
CONFIG_KEY_CHECKPOINT_NS,
CONFIG_KEY_TASK_ID,
CONFIG_KEY_STRICT_RESUME,
),
)
+3 -15
View File
@@ -33,7 +33,6 @@ from xxhash import xxh3_128_hexdigest
from langgraph._internal._cache import default_cache_key
from langgraph._internal._constants import INTERRUPT as _INTERRUPT_KEY
from langgraph._internal._constants import NS_SEP
from langgraph._internal._fields import get_cached_annotated_keys, get_update_as_tuples
from langgraph._internal._retry import default_retry_on
from langgraph._internal._typing import MISSING, DeprecatedKwargs
@@ -580,15 +579,6 @@ def omit_payload(_value: Any) -> dict[str, Any]:
_DEFAULT_INTERRUPT_ID = "placeholder-id"
def _interrupt_id(ns: str, answered: int) -> str:
# Keyed by how many answers the task already has, the slot this interrupt's
# answer fills; call order isn't stable across parallel calls in one task.
# With none it's the bare namespace hash older versions gave every
# interrupt, so saved ids still match. The suffix can't form another task's
# namespace, whose segments all contain ":".
return xxh3_128_hexdigest((f"{ns}{NS_SEP}{answered}" if answered else ns).encode())
@final
@dataclass(init=False, slots=True)
class Interrupt(Generic[ResponseT]):
@@ -651,7 +641,7 @@ class Interrupt(Generic[ResponseT]):
) -> Interrupt[ResponseT]:
return cls(
value=value,
id=_interrupt_id(ns, 0),
id=xxh3_128_hexdigest(ns.encode()),
response_schema=response_schema,
)
@@ -1047,11 +1037,9 @@ def interrupt(
# no resume value found
raise GraphInterrupt(
(
Interrupt(
Interrupt.from_ns(
value=value,
id=_interrupt_id(
conf[CONFIG_KEY_CHECKPOINT_NS], len(scratchpad.resume)
),
ns=conf[CONFIG_KEY_CHECKPOINT_NS],
response_schema=adapter.json_schema() if adapter else response_schema,
),
)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph"
version = "1.2.20"
version = "1.2.18"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
requires-python = ">=3.10"
+1 -2
View File
@@ -16,7 +16,7 @@ from langgraph_sdk.schema import StreamPart
from pydantic import BaseModel
from typing_extensions import TypedDict
from langgraph._internal._constants import CONFIG_KEY_STREAM, CONFIG_KEY_STRICT_RESUME
from langgraph._internal._constants import CONFIG_KEY_STREAM
from langgraph.errors import GraphInterrupt
from langgraph.graph import END, START, MessagesState, StateGraph, add_messages
from langgraph.pregel import Pregel
@@ -1360,7 +1360,6 @@ def test_sanitize_config():
"checkpoint_map": {"key": "value"}, # Should be dropped
"checkpoint_id": "123", # Should be dropped
"checkpoint_ns": "ns", # Should be dropped
CONFIG_KEY_STRICT_RESUME: True, # Should be dropped
}
}
sanitized = remote._sanitize_config(config_with_drops)
-423
View File
@@ -1,423 +0,0 @@
import operator
import sys
import uuid
from typing import Annotated
import pytest
from langchain_core.runnables import RunnableConfig
from langchain_core.runnables.config import get_executor_for_config
from langgraph.checkpoint.base import BaseCheckpointSaver
from typing_extensions import TypedDict
from xxhash import xxh3_128_hexdigest
from langgraph._internal._constants import CONFIG_KEY_STRICT_RESUME, INTERRUPT
from langgraph.errors import UnknownInterruptError
from langgraph.graph import START, StateGraph
from langgraph.graph.state import CompiledStateGraph
from langgraph.types import Command, Durability, Interrupt, RetryPolicy, interrupt
pytestmark = pytest.mark.anyio
NEEDS_CONTEXTVARS = pytest.mark.skipif(
sys.version_info < (3, 11),
reason="Python 3.11+ is required for async contextvars support",
)
UNKNOWN_ID = "0" * 32
class State(TypedDict, total=False):
log: Annotated[list[str], operator.add]
def _config(*, strict: bool = True) -> RunnableConfig:
return {
"configurable": {
"thread_id": str(uuid.uuid4()),
CONFIG_KEY_STRICT_RESUME: strict,
}
}
def _parallel(checkpointer: BaseCheckpointSaver) -> CompiledStateGraph:
def a(state: State):
return {"log": [f"a:{interrupt('a?')}"]}
def b(state: State):
return {"log": [f"b:{interrupt('b?')}"]}
return (
StateGraph(State)
.add_node("a", a)
.add_node("b", b)
.add_edge(START, "a")
.add_edge(START, "b")
.compile(checkpointer=checkpointer)
)
def _ask_twice(checkpointer: BaseCheckpointSaver) -> CompiledStateGraph:
def ask_twice(state: State):
first = interrupt("first?")
second = interrupt("second?")
return {"log": [f"{first},{second}"]}
return (
StateGraph(State)
.add_node("ask", ask_twice)
.add_edge(START, "ask")
.compile(checkpointer=checkpointer)
)
def _ask_then_fail_once(checkpointer: BaseCheckpointSaver) -> CompiledStateGraph:
failures = [ConnectionError("flaky api")]
def ask(state: State):
answer = interrupt("delete a.txt?")
if failures:
raise failures.pop()
return {"log": [answer]}
return (
StateGraph(State)
.add_node(
"ask", ask, retry_policy=RetryPolicy(initial_interval=0, jitter=False)
)
.add_edge(START, "ask")
.compile(checkpointer=checkpointer)
)
def _pending_ids(graph: CompiledStateGraph, config: RunnableConfig) -> dict[str, str]:
return {i.value: i.id for i in graph.get_state(config).interrupts}
def test_unknown_id_is_refused_before_anything_is_written(
sync_checkpointer: BaseCheckpointSaver, durability: Durability
) -> None:
graph = _parallel(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config, durability=durability)
before = graph.get_state(config)
checkpoints = len(list(sync_checkpointer.list(config)))
ids = _pending_ids(graph, config)
with pytest.raises(
UnknownInterruptError,
match=f"Unknown or already-consumed interrupt: {UNKNOWN_ID}",
) as refused:
graph.invoke(Command(resume={UNKNOWN_ID: "x"}), config, durability=durability)
assert (
refused.value.reason,
refused.value.interrupt_ids,
refused.value.pending_ids,
) == ("not_pending", (UNKNOWN_ID,), tuple(sorted(ids.values())))
after = graph.get_state(config)
assert len(list(sync_checkpointer.list(config))) == checkpoints
assert (after.values, after.next, after.interrupts) == (
before.values,
before.next,
before.interrupts,
)
result = graph.invoke(
Command(resume={ids["a?"]: "yes", ids["b?"]: "no"}),
config,
durability=durability,
)
assert sorted(result["log"]) == ["a:yes", "b:no"]
def test_an_answered_id_is_refused(sync_checkpointer: BaseCheckpointSaver) -> None:
graph = _parallel(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
ids = _pending_ids(graph, config)
graph.invoke(Command(resume={ids["a?"]: "yes"}), config)
with pytest.raises(UnknownInterruptError, match=ids["a?"]) as refused:
graph.invoke(Command(resume={ids["a?"]: "again"}), config)
assert (
refused.value.reason,
refused.value.interrupt_ids,
refused.value.pending_ids,
) == ("not_pending", (ids["a?"],), (ids["b?"],))
result = graph.invoke(Command(resume={ids["b?"]: "no"}), config)
assert sorted(result["log"]) == ["a:yes", "b:no"]
def test_an_empty_map_is_refused(sync_checkpointer: BaseCheckpointSaver) -> None:
graph = _parallel(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
with pytest.raises(UnknownInterruptError, match="names no interrupt") as refused:
graph.invoke(Command(resume={}), config)
assert (
refused.value.reason,
refused.value.interrupt_ids,
refused.value.pending_ids,
) == ("empty_map", (), tuple(sorted(_pending_ids(graph, config).values())))
def test_one_value_is_refused_while_several_are_pending(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _parallel(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
with pytest.raises(
UnknownInterruptError, match="multiple pending interrupts"
) as refused:
graph.invoke(Command(resume="yes"), config)
ids = _pending_ids(graph, config)
assert (refused.value.reason, refused.value.pending_ids) == (
"ambiguous",
tuple(sorted(ids.values())),
)
graph.invoke(Command(resume={ids["a?"]: "yes"}), config)
result = graph.invoke(Command(resume="no"), config)
assert sorted(result["log"]) == ["a:yes", "b:no"]
def test_one_value_resumes_a_static_breakpoint(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = (
StateGraph(State)
.add_node("a", lambda state: {"log": ["a"]})
.add_edge(START, "a")
.compile(checkpointer=sync_checkpointer, interrupt_before=["a"])
)
config = _config()
graph.invoke({"log": []}, config)
with pytest.raises(UnknownInterruptError, match=UNKNOWN_ID):
graph.invoke(Command(resume={UNKNOWN_ID: "x"}), config)
assert graph.invoke(Command(resume="go"), config) == {"log": ["a"]}
def test_without_strict_mode_nothing_changes(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _parallel(sync_checkpointer)
config = _config(strict=False)
graph.invoke({"log": []}, config)
graph.invoke(Command(resume={UNKNOWN_ID: "x"}), config)
assert set(_pending_ids(graph, config)) == {"a?", "b?"}
with pytest.raises(RuntimeError, match="multiple pending interrupts"):
graph.invoke(Command(resume="yes"), config)
def test_a_subgraph_interrupt_is_answered_by_its_id(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
def ask(state: State):
return {"log": [f"asked:{interrupt('sub?')}"]}
subgraph = StateGraph(State).add_node("ask", ask).add_edge(START, "ask").compile()
graph = (
StateGraph(State)
.add_node("sub", subgraph)
.add_edge(START, "sub")
.compile(checkpointer=sync_checkpointer)
)
config = _config()
graph.invoke({"log": []}, config)
result = graph.invoke(
Command(resume={_pending_ids(graph, config)["sub?"]: "yes"}), config
)
assert result["log"] == ["asked:yes"]
def test_a_second_interrupt_in_one_node_gets_its_own_id(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _ask_twice(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
state = graph.get_state(config)
(card,) = state.interrupts
assert card.id == xxh3_128_hexdigest(f"ask:{state.tasks[0].id}".encode())
graph.invoke(Command(resume={card.id: "1"}), config)
(second,) = graph.get_state(config).interrupts
assert second.id != card.id
result = graph.invoke(Command(resume={second.id: "2"}), config)
assert result["log"] == ["1,2"]
def test_a_retried_answer_in_one_node_is_refused(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _ask_twice(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
(card,) = graph.get_state(config).interrupts
graph.invoke(Command(resume={card.id: "yes"}), config)
(second,) = graph.get_state(config).interrupts
with pytest.raises(UnknownInterruptError) as refused:
graph.invoke(Command(resume={card.id: "yes"}), config)
assert (
refused.value.reason,
refused.value.interrupt_ids,
refused.value.pending_ids,
) == ("not_pending", (card.id,), (second.id,))
result = graph.invoke(Command(resume={second.id: "no"}), config)
assert result["log"] == ["yes,no"]
def test_parallel_interrupts_in_one_node_each_take_an_answer(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
def ask(name: str) -> str:
return interrupt(f"{name}?")
def ask_in_parallel(state: State, config: RunnableConfig):
with get_executor_for_config(config) as pool:
b = pool.submit(ask, "b")
b.exception()
a = pool.submit(ask, "a")
return {"log": [f"{a.result()},{b.result()}"]}
graph = (
StateGraph(State)
.add_node("ask", ask_in_parallel)
.add_edge(START, "ask")
.compile(checkpointer=sync_checkpointer)
)
config = _config()
graph.invoke({"log": []}, config)
for answer in ("1", "2"):
(card,) = graph.get_state(config).interrupts
graph.invoke(Command(resume={card.id: answer}), config)
assert graph.get_state(config).next == ()
def test_a_later_interrupt_saved_with_the_first_id_is_still_answered(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _ask_twice(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
(card,) = graph.get_state(config).interrupts
graph.invoke(Command(resume={card.id: "1"}), config)
state = graph.get_state(config)
sync_checkpointer.put_writes(
state.config,
[(INTERRUPT, (Interrupt(value="second?", id=card.id),))],
state.tasks[0].id,
)
result = graph.invoke(Command(resume={card.id: "2"}), config)
assert result["log"] == ["1,2"]
def test_a_retried_node_reuses_its_answer(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _ask_then_fail_once(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
(card,) = graph.get_state(config).interrupts
result = graph.invoke(Command(resume={card.id: "yes"}), config)
assert result["log"] == ["yes"]
def test_a_card_whose_resumed_task_raised_is_not_refused(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
failures = ["once"]
def ask_then_fail_once(state: State):
answer = interrupt("approve?")
if failures:
failures.pop()
raise ValueError("failed after the answer")
return {"log": [f"answered:{answer}"]}
graph = (
StateGraph(State)
.add_node("ask", ask_then_fail_once)
.add_edge(START, "ask")
.compile(checkpointer=sync_checkpointer)
)
config = _config()
graph.invoke({"log": []}, config)
(card,) = graph.get_state(config).interrupts
with pytest.raises(ValueError, match="failed after the answer"):
graph.invoke(Command(resume={card.id: "first"}), config)
graph.invoke(Command(resume={card.id: "second"}), config)
assert graph.get_state(config).next == ()
def test_a_pinned_head_checkpoint_is_checked(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _parallel(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
head = graph.get_state(config).config
pinned = {"configurable": {**config["configurable"], **head["configurable"]}}
with pytest.raises(UnknownInterruptError):
graph.invoke(Command(resume={UNKNOWN_ID: "x"}), pinned)
ids = _pending_ids(graph, config)
result = graph.invoke(Command(resume={ids["a?"]: "yes", ids["b?"]: "no"}), pinned)
assert sorted(result["log"]) == ["a:yes", "b:no"]
def test_a_superseded_checkpoint_is_not_checked(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _parallel(sync_checkpointer)
config = _config()
graph.invoke({"log": []}, config)
paused = graph.get_state(config).config
ids = _pending_ids(graph, config)
graph.invoke(Command(resume={ids["a?"]: "yes", ids["b?"]: "no"}), config)
pinned = {"configurable": {**config["configurable"], **paused["configurable"]}}
graph.invoke(Command(resume={ids["a?"]: "again", ids["b?"]: "again"}), pinned)
@NEEDS_CONTEXTVARS
async def test_unknown_id_is_refused_async(
async_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _parallel(async_checkpointer)
config = _config()
await graph.ainvoke({"log": []}, config)
with pytest.raises(UnknownInterruptError, match=UNKNOWN_ID):
await graph.ainvoke(Command(resume={UNKNOWN_ID: "x"}), config)
state = await graph.aget_state(config)
assert {i.value for i in state.interrupts} == {"a?", "b?"}
@NEEDS_CONTEXTVARS
async def test_a_retried_node_reuses_its_answer_async(
async_checkpointer: BaseCheckpointSaver,
) -> None:
graph = _ask_then_fail_once(async_checkpointer)
config = _config()
await graph.ainvoke({"log": []}, config)
(card,) = (await graph.aget_state(config)).interrupts
result = await graph.ainvoke(Command(resume={card.id: "yes"}), config)
assert result["log"] == ["yes"]
+1 -190
View File
@@ -1,24 +1,11 @@
"""End-to-end tests for `TracePolicy` input processing on node trace runs."""
import asyncio
from collections.abc import AsyncIterator, Iterator
from typing import Any
from uuid import UUID
import pytest
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.language_models.fake_chat_models import FakeMessagesListChatModel
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.runnables import RunnableConfig
from typing_extensions import TypedDict
from langgraph._internal._runnable import (
ASYNCIO_ACCEPTS_CONTEXT,
RunnableCallable,
RunnableSeq,
)
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.graph.state import CompiledStateGraph
from langgraph.graph import END, START, StateGraph
from langgraph.types import TracePolicy
from tests.fake_tracer import FakeTracer, Run
@@ -160,179 +147,3 @@ async def test_trace_policy_transforms_recorded_inputs_async() -> None:
run = _node_run(tracer, "n")
assert run.inputs == {"scrubbed_in": True}
assert run.outputs == {"value": 6}
_RECORDING_POLICY = TracePolicy(
process_inputs=lambda _: {"recorded": "inputs"},
process_outputs=lambda _: {"recorded": "outputs"},
)
def _chat_graph() -> CompiledStateGraph:
model = FakeMessagesListChatModel(
responses=[AIMessage("model reply", id="ai-model")]
)
def chat(state: MessagesState) -> dict[str, Any]:
reply = model.invoke(state["messages"])
return {
"messages": [
*state["messages"],
reply,
AIMessage("from node", id="ai-node"),
]
}
return (
StateGraph(MessagesState)
.add_node("chat", chat, trace_policy=_RECORDING_POLICY)
.add_edge(START, "chat")
.compile()
)
def _assert_scrubbed_node_run(tracer: FakeTracer, *, model_is_child: bool) -> None:
run = _node_run(tracer, "chat")
assert run.inputs == {"recorded": "inputs"}
assert run.outputs == {"recorded": "outputs"}
if model_is_child:
assert [c.name for c in run.child_runs] == ["FakeMessagesListChatModel"]
def test_trace_policy_scrubs_tracer_not_messages_stream() -> None:
tracer = FakeTracer()
streamed = [
message.content
for message, _ in _chat_graph().stream(
{"messages": [HumanMessage("hello", id="h-1")]},
{"callbacks": [tracer]},
stream_mode="messages",
)
]
assert streamed == ["model reply", "from node"]
_assert_scrubbed_node_run(tracer, model_is_child=True)
@pytest.mark.anyio
async def test_trace_policy_scrubs_tracer_not_messages_stream_async() -> None:
tracer = FakeTracer()
streamed = [
message.content
async for message, _ in _chat_graph().astream(
{"messages": [HumanMessage("hello", id="h-1")]},
{"callbacks": [tracer]},
stream_mode="messages",
)
]
assert streamed == ["model reply", "from node"]
_assert_scrubbed_node_run(tracer, model_is_child=ASYNCIO_ACCEPTS_CONTEXT)
@pytest.mark.anyio
async def test_trace_policy_scrubs_tracer_not_astream_events() -> None:
tracer = FakeTracer()
chat_events = [
event
async for event in _chat_graph().astream_events(
{"messages": [HumanMessage("hello", id="h-1")]},
{"callbacks": [tracer]},
version="v2",
)
if event["name"] == "chat"
and event["event"] in ("on_chain_start", "on_chain_end")
]
assert [e["event"] for e in chat_events] == ["on_chain_start", "on_chain_end"]
start, end = (e["data"] for e in chat_events)
assert [m.content for m in start["input"]["messages"]] == ["hello"]
assert [m.content for m in end["output"]["messages"]] == [
"hello",
"model reply",
"from node",
]
_assert_scrubbed_node_run(tracer, model_is_child=ASYNCIO_ACCEPTS_CONTEXT)
class _HandlerWithTapMethods(BaseCallbackHandler):
def __init__(self) -> None:
self.node_payloads: list[Any] = []
self.node_run_ids: set[UUID] = set()
def _record(self, run_id: UUID, payload: Any) -> None:
if run_id in self.node_run_ids:
self.node_payloads.append(payload)
def tap_output_iter(self, run_id: UUID, output: Iterator[Any]) -> Iterator[Any]:
for chunk in output:
self._record(run_id, chunk)
yield chunk
async def tap_output_aiter(
self, run_id: UUID, output: AsyncIterator[Any]
) -> AsyncIterator[Any]:
async for chunk in output:
self._record(run_id, chunk)
yield chunk
def on_chain_start(
self, serialized: Any, inputs: Any, *, run_id: UUID, **kwargs: Any
) -> None:
if kwargs.get("name") == "n":
self.node_run_ids.add(run_id)
self._record(run_id, inputs)
def on_chain_end(self, outputs: Any, *, run_id: UUID, **kwargs: Any) -> None:
self._record(run_id, outputs)
@pytest.mark.anyio
@pytest.mark.parametrize("via_events", [False, True], ids=["ainvoke", "astream_events"])
async def test_trace_policy_reaches_tracers_but_not_custom_handlers(
via_events: bool,
) -> None:
graph = (
StateGraph(State)
.add_node("n", _incr, trace_policy=_RECORDING_POLICY)
.add_edge(START, "n")
.add_edge("n", END)
.compile()
)
handler = _HandlerWithTapMethods()
tracer = FakeTracer()
config: RunnableConfig = {"callbacks": [handler, tracer]}
if via_events:
async for _ in graph.astream_events({"value": 1}, config, version="v2"):
pass
else:
assert await graph.ainvoke({"value": 1}, config) == {"value": 2}
assert handler.node_payloads == [{"value": 1}, {"value": 2}]
run = _node_run(tracer, "n")
assert (run.inputs, run.outputs) == (
{"recorded": "inputs"},
{"recorded": "outputs"},
)
@pytest.mark.anyio
async def test_trace_policy_cancel_at_node_end_still_closes_tracer_run() -> None:
def cancel_own_task(value: Any) -> Any:
asyncio.current_task().cancel()
return value
tracer = FakeTracer()
seq = RunnableSeq(
RunnableCallable(lambda x: x, trace=False),
RunnableCallable(cancel_own_task, trace=False),
trace_policy=_RECORDING_POLICY,
)
with pytest.raises(asyncio.CancelledError):
await asyncio.create_task(seq.ainvoke({}, {"callbacks": [tracer]}))
for _ in range(100):
if tracer.runs:
break
await asyncio.sleep(0.01)
assert [r.outputs for r in tracer.runs] == [{"recorded": "outputs"}]
+34 -33
View File
@@ -1439,7 +1439,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.20"
version = "1.2.18"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },
@@ -1722,7 +1722,7 @@ test = [
[[package]]
name = "langgraph-checkpoint-sqlite"
version = "3.1.3"
version = "3.1.2"
source = { editable = "../checkpoint-sqlite" }
dependencies = [
{ name = "aiosqlite" },
@@ -2699,36 +2699,37 @@ wheels = [
[[package]]
name = "pycryptodome"
version = "4.2.0"
version = "3.23.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/33/c7/f9374c8939560ab22db1dd9dbe215b62d20f32634aeb7f259dc03c807b44/pycryptodome-4.2.0.tar.gz", hash = "sha256:21d7fdba239d6ef863d3d08388801c683453a3bb4897c2b27f27847aad89bdf7", size = 1750332, upload-time = "2026-10-24T10:24:42.016Z" }
sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/05/61028b65913438a091ed3a2ec55bb50843e674b6ae14bccf196bbed652a2/pycryptodome-4.2.0-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:4b58c4fd5615a788e0df6793d578e565b19d75a348c0ba02d469b0c38bbf0332", size = 1684620, upload-time = "2026-10-24T10:23:50.473Z" },
{ url = "https://files.pythonhosted.org/packages/cd/73/0930fd56dd5eb856ecc33ec48845def52a1d25a9cd6b810a4c3ef3633cf4/pycryptodome-4.2.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:42ab554efcc14928f1db42deac9c84989fd49e289a16aacbbee709fe2eb838cc", size = 1520423, upload-time = "2026-10-24T10:23:52.505Z" },
{ url = "https://files.pythonhosted.org/packages/19/ba/4b931de42881053a6d818b9ee70f6dde3dfe1cfcf78527d45db3f99d8b47/pycryptodome-4.2.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b4ed1a34d2d667ec165beb15313a759bcd8e704e2743f9942491c5a88a2d6ca5", size = 2296340, upload-time = "2026-10-24T10:23:54.294Z" },
{ url = "https://files.pythonhosted.org/packages/d4/63/68fc2a01d621c2ff389ea261e731e8f1abe6aba89992681bbcdb19fcca43/pycryptodome-4.2.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1482a7d9ceed7beadbb0853f83d4d44406a9f9fa8ee596d637618f095869e14", size = 2987589, upload-time = "2026-10-24T10:23:56.547Z" },
{ url = "https://files.pythonhosted.org/packages/f5/63/69e211459f4625c89bb47a7ba4527a6cc3276c9bbd79ae871a2d0b05c021/pycryptodome-4.2.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:dceadb6f478b0a687d129f1b58220e0587fb17a31b2502ae7a1a4234bc304ee1", size = 2322482, upload-time = "2026-10-24T10:23:58.538Z" },
{ url = "https://files.pythonhosted.org/packages/4a/65/21a4ed3ae5af153c57861d7f987c81fca5bf02d49b76f34c545f8eb6b95f/pycryptodome-4.2.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:8217736d5ac9b7fef49d1d2b292d5af4e8300298ef4926742aaec49b2bf6505b", size = 3548352, upload-time = "2026-10-24T10:24:01.185Z" },
{ url = "https://files.pythonhosted.org/packages/4a/a4/9a7e4e8896cfd8131978d31e39c930fb114f6a63751f39d2069b965dad45/pycryptodome-4.2.0-cp315-cp315t-win32.whl", hash = "sha256:b717e36cf14cffe463ff4e7d423a0c9fd6f83b40d207388e0f40dd0a3d4018bc", size = 1306386, upload-time = "2026-10-24T10:24:03.047Z" },
{ url = "https://files.pythonhosted.org/packages/af/6c/e1a78019b7db83e2cfc1c98cea4a7a0afbb6b233514b9dfee2bad7443bd9/pycryptodome-4.2.0-cp315-cp315t-win_amd64.whl", hash = "sha256:585f0a792cdc7fb381ca0d1fac4e20f03fd99bb78cf2733eaab59f62c77ccf88", size = 1688319, upload-time = "2026-10-24T10:24:04.963Z" },
{ url = "https://files.pythonhosted.org/packages/c6/96/7cb051ba325141b33bc7e6ef6152499cf74b74606c5bb4c8c72411e8afdc/pycryptodome-4.2.0-cp315-cp315t-win_arm64.whl", hash = "sha256:831ae8ce070e990d4fcb913c49c7fb0181ea7ab2691fc58dcb9d41fdb5384d85", size = 1330914, upload-time = "2026-10-24T10:24:06.934Z" },
{ url = "https://files.pythonhosted.org/packages/41/4c/954a9a1451f7bcc89d66db39bf6499ef5701e8ff35b6c898c659a7ec8546/pycryptodome-4.2.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:3e430168f767ecec60c1f850df4abbd0b9f94786b9cc8041a00e976f6d5190c6", size = 1680663, upload-time = "2026-10-24T10:24:08.982Z" },
{ url = "https://files.pythonhosted.org/packages/6e/84/23f8013dce07cbc8c8b9ed878972294230a1edc49a6b64d271c77c878c29/pycryptodome-4.2.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aea22120791b4534a27e39fc01d91265a249ba72dbd682a8a5c1adee475d5483", size = 1513323, upload-time = "2026-10-24T10:24:11.076Z" },
{ url = "https://files.pythonhosted.org/packages/1a/82/693940a8e4a20c3db91050480429daf39c818be3c7ec59f992ded4567ce6/pycryptodome-4.2.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f6f30a457bd83a3074747d11bcb95375397a2e9b9a6b9179ab001e0cdd8ed0c5", size = 2292885, upload-time = "2026-10-24T10:24:13.486Z" },
{ url = "https://files.pythonhosted.org/packages/18/88/33df6b1176b63aed427745696d88ed38f7877104cf0f6004ee9ad600f897/pycryptodome-4.2.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:76777686de3a354c41710727988c2284448d5db9bd82e5ddb9a9b6710d17d9df", size = 2976488, upload-time = "2026-10-24T10:24:16.139Z" },
{ url = "https://files.pythonhosted.org/packages/b1/c6/c1c137d6e3284409ca5b2cfd7db3621167c316f9c314a96274af69a66fbb/pycryptodome-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c5bc41a418451616cd0ec3d765ba8539416d7429d7cd633ffb603a5a22355e70", size = 2313324, upload-time = "2026-10-24T10:24:18.302Z" },
{ url = "https://files.pythonhosted.org/packages/ef/26/b7752cffc8200040835ad24dc17c9de6376ac27612c8bf943766cb1b3c36/pycryptodome-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2c5b1e9058687daca3aaaff23eeb1c1853c8251986f0de7a950235e8bc0cc8b7", size = 3537773, upload-time = "2026-10-24T10:24:20.482Z" },
{ url = "https://files.pythonhosted.org/packages/df/1b/34bec1416507cda23b5dfa33c0bca936fd4ac8ed4d3269803477b897649d/pycryptodome-4.2.0-cp39-abi3-win32.whl", hash = "sha256:bb7659f87d7aa69105e1be12764f9cf1908287eb424b242186f5ac3c192ed612", size = 1292974, upload-time = "2026-10-24T10:24:22.422Z" },
{ url = "https://files.pythonhosted.org/packages/03/5b/a4a31e6e1aabd5a26da77c7960e7fcce4fca9132e51868f1bba10bf1e932/pycryptodome-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:3282c9fd744a16594e51e1a9fbfe4d658b6b0aaa1f050926e6d591ac7494f78b", size = 1658684, upload-time = "2026-10-24T10:24:24.009Z" },
{ url = "https://files.pythonhosted.org/packages/2b/81/bddb0637fa85a03d00eea2f3514c5aa8d7b9c4921ef5e0c125b4ce662a8c/pycryptodome-4.2.0-cp39-abi3-win_arm64.whl", hash = "sha256:34ec11258967efec61d92a83a63a050ab7a7c31dc3806412f6334b8aef47e2d9", size = 1305591, upload-time = "2026-10-24T10:24:25.993Z" },
{ url = "https://files.pythonhosted.org/packages/df/a1/5460bbb750121f71abacaaf25f0cb1df67be7fe13c70cf6d7c2b8ad54fc0/pycryptodome-4.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7ea7dbf6b8ce0f0d2c0e17c1226c74d07c01499db7704797bdbb6cb9ec511ccb", size = 1472158, upload-time = "2026-10-24T10:24:27.92Z" },
{ url = "https://files.pythonhosted.org/packages/e2/65/27ac8fb15f0ffb70b72a0413291637de46d29b075804221703273c3ca6d1/pycryptodome-4.2.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0106af8684e1d73c93bc39a2f1cb4ae2f2636d1f80e6291caddcc37b4efc5dc1", size = 1247107, upload-time = "2026-10-24T10:24:29.672Z" },
{ url = "https://files.pythonhosted.org/packages/f0/7d/6b3ed6cd8626007f6a6b13616b08150bc0bb936a0b84c310e59558ed5136/pycryptodome-4.2.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4deb46e0d0a540b1f047cb6f430249692da15c21edd75f5e6278314089fb859", size = 1426092, upload-time = "2026-10-24T10:24:31.627Z" },
{ url = "https://files.pythonhosted.org/packages/73/45/5adf4dbb1eab1da6291d25f777d1d34f745c7f2993bf2d4d3b0d9abb87c6/pycryptodome-4.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e094873fe87ae4d546ef393cec7ea1e1cf8e0684f649612b19352afa67eb8bdb", size = 1662257, upload-time = "2026-10-24T10:24:33.373Z" },
{ url = "https://files.pythonhosted.org/packages/57/88/fef93a9ecbda783a312b15a8823fa1a9a4ec3474685e756584af595457aa/pycryptodome-4.2.0-pp311-pypy311_pp80-macosx_10_15_x86_64.whl", hash = "sha256:30f418cd1f4e776bde6c5df1fcd90ddfe74dfc8f679286a82f17ec1d4018cce0", size = 1467319, upload-time = "2026-10-24T10:24:35.108Z" },
{ url = "https://files.pythonhosted.org/packages/78/65/cb53934c9f8d5c41dfcfaddb0d98103f4dd6670af1ffdbc2582bcdd99382/pycryptodome-4.2.0-pp311-pypy311_pp80-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20cb4a489f7a5150a882901884b84ee0df77a47b9a6eb6e2dd8e98997b7b826f", size = 1247105, upload-time = "2026-10-24T10:24:36.859Z" },
{ url = "https://files.pythonhosted.org/packages/11/38/5bba7fd24beb6bc7a82fdd155f2df39f51c8ad16143fc99d3a1ccc7c76b4/pycryptodome-4.2.0-pp311-pypy311_pp80-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f104dc1a38aa1172a166ebe3716f2666f785bfa015b7ca13bb808c12c902e455", size = 1426092, upload-time = "2026-10-24T10:24:38.584Z" },
{ url = "https://files.pythonhosted.org/packages/38/cf/e70a8cb1ab184b59b2d02dfd403965a0c6419571d8520463944bcb0ca8a0/pycryptodome-4.2.0-pp311-pypy311_pp80-win_amd64.whl", hash = "sha256:a7c6857077255f5f4e6c34e6325802ba1bb59960133c0a5e584d9371dbdf3dea", size = 1662214, upload-time = "2026-10-24T10:24:40.139Z" },
{ url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" },
{ url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" },
{ url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" },
{ url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" },
{ url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" },
{ url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" },
{ url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" },
{ url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" },
{ url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" },
{ url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" },
{ url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" },
{ url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" },
{ url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" },
{ url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" },
{ url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" },
{ url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" },
{ url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" },
{ url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" },
{ url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" },
{ url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" },
{ url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" },
{ url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" },
{ url = "https://files.pythonhosted.org/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379", size = 1623886, upload-time = "2025-05-17T17:21:20.614Z" },
{ url = "https://files.pythonhosted.org/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4", size = 1672151, upload-time = "2025-05-17T17:21:22.666Z" },
{ url = "https://files.pythonhosted.org/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630", size = 1664461, upload-time = "2025-05-17T17:21:25.225Z" },
{ url = "https://files.pythonhosted.org/packages/d6/92/608fbdad566ebe499297a86aae5f2a5263818ceeecd16733006f1600403c/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353", size = 1702440, upload-time = "2025-05-17T17:21:27.991Z" },
{ url = "https://files.pythonhosted.org/packages/d1/92/2eadd1341abd2989cce2e2740b4423608ee2014acb8110438244ee97d7ff/pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5", size = 1803005, upload-time = "2025-05-17T17:21:31.37Z" },
]
[[package]]
@@ -3668,14 +3669,14 @@ wheels = [
[[package]]
name = "types-requests"
version = "2.33.0.20261006"
version = "2.33.0.20260906"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/57/15/9b7e2e2e7c87d01366185b198b3febc6bc0c973f2bf21a62da4ab7d3495e/types_requests-2.33.0.20261006.tar.gz", hash = "sha256:0652999e9306aea345f40732d58fa49a7f6cade6a0d74d92119c5c8d82eddaf0", size = 25316, upload-time = "2026-10-06T08:15:57.782Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c0/18/4c2c0290953f8b3b9612adfcb07b57f144ade3ad32a76764fca42b77c5f3/types_requests-2.33.0.20260906.tar.gz", hash = "sha256:76ab8a0fb736744a0c3deee7aa57b2927e301f078d9e61f5391b3e92002416b9", size = 25263, upload-time = "2026-09-06T06:35:47.707Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/72/b82789207b3d360ce9f5a4372c790c52cc3dd928aeb7d4faeec652b651b2/types_requests-2.33.0.20261006-py3-none-any.whl", hash = "sha256:26cc8146505cab33cda9737991929e4144c559bebe05078ccc6998f27c4ca2c1", size = 21445, upload-time = "2026-10-06T08:15:56.658Z" },
{ url = "https://files.pythonhosted.org/packages/60/4c/51ec821d22a45b4162fa3f3e9e94ea4c0f8c49e82363b10955baa5438391/types_requests-2.33.0.20260906-py3-none-any.whl", hash = "sha256:9f53622652bd921ead7a54d665be1b8d518165c8b51cc9d68d944cd6b6bfa8fb", size = 21461, upload-time = "2026-09-06T06:35:45.856Z" },
]
[[package]]
+2 -2
View File
@@ -286,7 +286,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.20"
version = "1.2.18"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },
@@ -467,7 +467,7 @@ test = [
[[package]]
name = "langgraph-checkpoint-sqlite"
version = "3.1.3"
version = "3.1.2"
source = { editable = "../checkpoint-sqlite" }
dependencies = [
{ name = "aiosqlite" },
+2 -17
View File
@@ -1,21 +1,6 @@
# Changelog
## 0.4.12
### Removed
- **`PrioritySettings.fairness_weight`** — removed. The server stores only
`priority` and `fairness_group` on the thread; it ignores a
`fairness_weight` and logs a warning. Remove it from your callback.
### Changed
- **`PrioritySettings.priority`** — now says -5 runs first and 5 runs last,
matching how the server orders runs. 0.4.9 to 0.4.11 said the reverse, and
their example gave premium users 3, which runs them after everyone at the
default 0. Check any callback written from those docs.
## 0.4.11 and earlier
## Unreleased
### Added
@@ -53,7 +38,7 @@
and name the object in the `priority` section of `langgraph.json`. The
server calls the function with a `PriorityContext` at the first run on a
thread with no settings and stores the `PrioritySettings` it returns
(`priority`, `fairness_group`) on the thread. Requires
(`priority`, `fairness_group`, `fairness_weight`) on the thread. Requires
`langgraph-api` 0.18.0 or later and a `langgraph-cli` that supports the
section; older versions of either ignore it.
+1 -1
View File
@@ -5,7 +5,7 @@ from langgraph_sdk.encryption.types import DecryptResult, EncryptionContext
from langgraph_sdk.priority import Priority
from langgraph_sdk.priority.types import PriorityContext, PrioritySettings
__version__ = "0.4.13"
__version__ = "0.4.10"
__all__ = [
"Auth",
-37
View File
@@ -15,8 +15,6 @@ import orjson
from langgraph_sdk._shared.utilities import (
_orjson_default,
_reconnect_delay,
_run_check_headers,
_run_url_from_stream_location,
_validate_reconnect_location,
)
from langgraph_sdk.errors import (
@@ -234,7 +232,6 @@ class HttpClient:
reconnect_path: str | None = None
reconnect_attempts = 0
max_reconnect_attempts = 5
run_ended = False
while True:
current_headers = dict(
@@ -304,7 +301,6 @@ class HttpClient:
# Progress since the last drop earns a fresh
# set of reconnect attempts.
reconnect_attempts = 0
run_ended |= sse.event in ("end", "error")
yield sse
except httpx.HTTPError as err:
# httpx.TransportError inherits from HTTPError, so transient
@@ -321,7 +317,6 @@ class HttpClient:
# return an empty placeholder when there is no pending
# message. Skip these no-op events so the stream doesn't
# emit a trailing blank item after reconnects.
run_ended |= sse.event in ("end", "error")
yield sse
except (httpx.TransportError, RateLimitError, InternalServerError) as err:
# A reconnect that can't open (connection refused, 429, 5xx or a
@@ -331,13 +326,6 @@ class HttpClient:
if reconnect_path is None:
raise
reconnect_cause = err
# A run's stream closes cleanly both when the run ends and when a
# proxy timeout or a server restart cuts it. Without an `end` or
# `error` event the two look the same, so ask the run.
if reconnect_cause is None and reconnect_path is not None and not run_ended:
reconnect_cause = await _acheck_run(
self.client, reconnect_path, reconnect_headers
)
if reconnect_cause is not None:
reconnect_attempts += 1
if reconnect_attempts > max_reconnect_attempts:
@@ -374,28 +362,3 @@ async def _adecode_json(r: httpx.Response) -> Any:
if body
else None
)
async def _acheck_run(
client: httpx.AsyncClient, stream_location: str, headers: Mapping[str, str]
) -> httpx.HTTPError | None:
run_url = _run_url_from_stream_location(stream_location)
if run_url is None:
return None
try:
r = await client.get(run_url, headers=_run_check_headers(headers))
if r.status_code == 429 or r.status_code >= 500:
await _araise_for_status_typed(r)
status = (await _adecode_json(r))["status"] if r.is_success else None
except (httpx.TransportError, RateLimitError, InternalServerError) as err:
# The restart that closed the stream can fail the check too, so retry
# it like a drop.
return err
except Exception:
# Anything else must not turn a normal end of stream into an error.
return None
if status in ("pending", "running"):
return httpx.TransportError(
"Stream closed while the run was still pending or running"
)
return None
+1 -1
View File
@@ -305,7 +305,7 @@ class RunsClient:
StreamPart(event='metadata', data={'run_id': '1ef4a9b8-d7da-679a-a45a-872054341df2'})
StreamPart(event='values', data={'messages': [{'content': 'how are you?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'fe0a5778-cfe9-42ee-b807-0adaa1873c10', 'example': False}]})
StreamPart(event='values', data={'messages': [{'content': 'how are you?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'fe0a5778-cfe9-42ee-b807-0adaa1873c10', 'example': False}, {'content': "I'm doing well, thanks for asking! I'm an AI assistant created by Anthropic to be helpful, honest, and harmless.", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-159b782c-b679-4830-83c6-cef87798fe8b', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]})
StreamPart(event='end', data={'run_id': '1ef4a9b8-d7da-679a-a45a-872054341df2', 'status': 'success'})
StreamPart(event='end', data=None)
```
"""
+3 -30
View File
@@ -21,7 +21,6 @@ from langgraph_sdk.schema import (
ThreadSelectField,
ThreadSortBy,
ThreadState,
ThreadStateSelectField,
ThreadStatus,
ThreadStreamMode,
ThreadUpdateStateResponse,
@@ -490,7 +489,6 @@ class ThreadsClient:
checkpoint_id: str | None = None, # deprecated
*,
subgraphs: bool = False,
select: list[ThreadStateSelectField] | None = None,
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> ThreadState:
@@ -501,9 +499,6 @@ class ThreadsClient:
checkpoint: The checkpoint to get the state of.
checkpoint_id: (deprecated) The checkpoint ID to get the state of.
subgraphs: Include subgraphs states.
select: Fields to include in the response. Use `values.<key>` to
return a single state key (e.g. `values.messages`), which lets the
server skip loading the rest of the state. Defaults to all fields.
headers: Optional custom headers to include with the request.
params: Optional query parameters to include with the request.
@@ -596,30 +591,16 @@ class ThreadsClient:
}
}
```
Fetch only the `messages` state key:
```python
thread_state = await client.threads.get_state(
thread_id="my_thread_id",
select=["values.messages"],
)
```
"""
if checkpoint:
payload: dict[str, Any] = {"checkpoint": checkpoint, "subgraphs": subgraphs}
if select:
payload["select"] = select
return await self.http.post(
f"/threads/{_quote_path_param(thread_id)}/state/checkpoint",
json=payload,
json={"checkpoint": checkpoint, "subgraphs": subgraphs},
headers=headers,
params=params,
)
elif checkpoint_id:
get_params: dict[str, Any] = {"subgraphs": subgraphs}
if select:
get_params["select"] = select
get_params = {"subgraphs": subgraphs}
if params:
get_params = {**get_params, **dict(params)}
return await self.http.get(
@@ -628,9 +609,7 @@ class ThreadsClient:
headers=headers,
)
else:
get_params: dict[str, Any] = {"subgraphs": subgraphs}
if select:
get_params["select"] = select
get_params = {"subgraphs": subgraphs}
if params:
get_params = {**get_params, **dict(params)}
return await self.http.get(
@@ -713,7 +692,6 @@ class ThreadsClient:
before: str | Checkpoint | None = None,
metadata: Mapping[str, Any] | None = None,
checkpoint: Checkpoint | None = None,
select: list[ThreadStateSelectField] | None = None,
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> list[ThreadState]:
@@ -725,9 +703,6 @@ class ThreadsClient:
limit: The maximum number of states to return.
before: Return states before this checkpoint.
metadata: Filter states by metadata key-value pairs.
select: Fields to include in the response. Use `values.<key>` to
return a single state key (e.g. `values.messages`), which lets the
server skip loading the rest of the state. Defaults to all fields.
headers: Optional custom headers to include with the request.
params: Optional query parameters to include with the request.
@@ -754,8 +729,6 @@ class ThreadsClient:
payload["metadata"] = metadata
if checkpoint:
payload["checkpoint"] = checkpoint
if select:
payload["select"] = select
return await self.http.post(
f"/threads/{_quote_path_param(thread_id)}/history",
json=payload,
+1 -20
View File
@@ -9,7 +9,7 @@ import re
from collections.abc import Mapping
from datetime import tzinfo
from typing import TYPE_CHECKING, Any, cast
from urllib.parse import quote, urlparse, urlsplit, urlunsplit
from urllib.parse import quote, urlparse
import httpx
@@ -195,25 +195,6 @@ def _validate_reconnect_location(base_url: httpx.URL, location: str) -> str:
return location
_RUN_STREAM_PATH = re.compile(r"(.*/threads/[^/]+/runs/[^/]+)/stream")
def _run_url_from_stream_location(location: str) -> str | None:
url = urlsplit(location)
if match := _RUN_STREAM_PATH.fullmatch(url.path):
return urlunsplit((url.scheme, url.netloc, match[1], "", ""))
return None
def _run_check_headers(reconnect_headers: Mapping[str, str]) -> dict[str, str]:
headers = {
key: value
for key, value in reconnect_headers.items()
if key.lower() not in ("accept", "last-event-id")
}
return {**headers, "Accept": "application/json"}
def _reconnect_delay(attempt: int, *, base: float, cap: float) -> float:
"""Seconds to wait before reconnect attempt `attempt` (0-based).
-37
View File
@@ -15,8 +15,6 @@ import orjson
from langgraph_sdk._shared.utilities import (
_orjson_default,
_reconnect_delay,
_run_check_headers,
_run_url_from_stream_location,
_validate_reconnect_location,
)
from langgraph_sdk.errors import (
@@ -237,7 +235,6 @@ class SyncHttpClient:
reconnect_path: str | None = None
reconnect_attempts = 0
max_reconnect_attempts = 5
run_ended = False
while True:
current_headers = dict(
@@ -306,7 +303,6 @@ class SyncHttpClient:
# Progress since the last drop earns a fresh
# set of reconnect attempts.
reconnect_attempts = 0
run_ended |= sse.event in ("end", "error")
yield sse
except httpx.HTTPError as err:
# httpx.TransportError inherits from HTTPError, so transient
@@ -321,7 +317,6 @@ class SyncHttpClient:
if sse.event or decoder.has_data:
# See async stream implementation for rationale on
# skipping empty flush events.
run_ended |= sse.event in ("end", "error")
yield sse
except (httpx.TransportError, RateLimitError, InternalServerError) as err:
# A reconnect that can't open (connection refused, 429, 5xx or a
@@ -331,13 +326,6 @@ class SyncHttpClient:
if reconnect_path is None:
raise
reconnect_cause = err
# A run's stream closes cleanly both when the run ends and when a
# proxy timeout or a server restart cuts it. Without an `end` or
# `error` event the two look the same, so ask the run.
if reconnect_cause is None and reconnect_path is not None and not run_ended:
reconnect_cause = _check_run(
self.client, reconnect_path, reconnect_headers
)
if reconnect_cause is not None:
reconnect_attempts += 1
if reconnect_attempts > max_reconnect_attempts:
@@ -366,28 +354,3 @@ def _encode_json(json: Any) -> tuple[dict[str, str], bytes | None]:
def _decode_json(r: httpx.Response) -> Any:
body = r.read()
return orjson.loads(body) if body else None
def _check_run(
client: httpx.Client, stream_location: str, headers: Mapping[str, str]
) -> httpx.HTTPError | None:
run_url = _run_url_from_stream_location(stream_location)
if run_url is None:
return None
try:
r = client.get(run_url, headers=_run_check_headers(headers))
if r.status_code == 429 or r.status_code >= 500:
_raise_for_status_typed(r)
status = _decode_json(r)["status"] if r.is_success else None
except (httpx.TransportError, RateLimitError, InternalServerError) as err:
# The restart that closed the stream can fail the check too, so retry
# it like a drop.
return err
except Exception:
# Anything else must not turn a normal end of stream into an error.
return None
if status in ("pending", "running"):
return httpx.TransportError(
"Stream closed while the run was still pending or running"
)
return None
+1 -1
View File
@@ -302,7 +302,7 @@ class SyncRunsClient:
StreamPart(event='metadata', data={'run_id': '1ef4a9b8-d7da-679a-a45a-872054341df2'})
StreamPart(event='values', data={'messages': [{'content': 'how are you?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'fe0a5778-cfe9-42ee-b807-0adaa1873c10', 'example': False}]})
StreamPart(event='values', data={'messages': [{'content': 'how are you?', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': 'fe0a5778-cfe9-42ee-b807-0adaa1873c10', 'example': False}, {'content': "I'm doing well, thanks for asking! I'm an AI assistant created by Anthropic to be helpful, honest, and harmless.", 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-159b782c-b679-4830-83c6-cef87798fe8b', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]})
StreamPart(event='end', data={'run_id': '1ef4a9b8-d7da-679a-a45a-872054341df2', 'status': 'success'})
StreamPart(event='end', data=None)
```
"""
if checkpoint_during is not None:
+3 -29
View File
@@ -21,7 +21,6 @@ from langgraph_sdk.schema import (
ThreadSelectField,
ThreadSortBy,
ThreadState,
ThreadStateSelectField,
ThreadStatus,
ThreadStreamMode,
ThreadUpdateStateResponse,
@@ -481,7 +480,6 @@ class SyncThreadsClient:
checkpoint_id: str | None = None, # deprecated
*,
subgraphs: bool = False,
select: list[ThreadStateSelectField] | None = None,
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> ThreadState:
@@ -491,9 +489,6 @@ class SyncThreadsClient:
thread_id: The ID of the thread to get the state of.
checkpoint: The checkpoint to get the state of.
subgraphs: Include subgraphs states.
select: Fields to include in the response. Use `values.<key>` to
return a single state key (e.g. `values.messages`), which lets the
server skip loading the rest of the state. Defaults to all fields.
headers: Optional custom headers to include with the request.
Returns:
@@ -586,29 +581,16 @@ class SyncThreadsClient:
}
```
Fetch only the `messages` state key:
```python
thread_state = client.threads.get_state(
thread_id="my_thread_id",
select=["values.messages"],
)
```
"""
if checkpoint:
payload: dict[str, Any] = {"checkpoint": checkpoint, "subgraphs": subgraphs}
if select:
payload["select"] = select
return self.http.post(
f"/threads/{_quote_path_param(thread_id)}/state/checkpoint",
json=payload,
json={"checkpoint": checkpoint, "subgraphs": subgraphs},
headers=headers,
params=params,
)
elif checkpoint_id:
get_params: dict[str, Any] = {"subgraphs": subgraphs}
if select:
get_params["select"] = select
get_params = {"subgraphs": subgraphs}
if params:
get_params = {**get_params, **dict(params)}
return self.http.get(
@@ -617,9 +599,7 @@ class SyncThreadsClient:
headers=headers,
)
else:
get_params: dict[str, Any] = {"subgraphs": subgraphs}
if select:
get_params["select"] = select
get_params = {"subgraphs": subgraphs}
if params:
get_params = {**get_params, **dict(params)}
return self.http.get(
@@ -699,7 +679,6 @@ class SyncThreadsClient:
before: str | Checkpoint | None = None,
metadata: Mapping[str, Any] | None = None,
checkpoint: Checkpoint | None = None,
select: list[ThreadStateSelectField] | None = None,
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> list[ThreadState]:
@@ -711,9 +690,6 @@ class SyncThreadsClient:
limit: The maximum number of states to return.
before: Return states before this checkpoint.
metadata: Filter states by metadata key-value pairs.
select: Fields to include in the response. Use `values.<key>` to
return a single state key (e.g. `values.messages`), which lets the
server skip loading the rest of the state. Defaults to all fields.
headers: Optional custom headers to include with the request.
Returns:
@@ -741,8 +717,6 @@ class SyncThreadsClient:
payload["metadata"] = metadata
if checkpoint:
payload["checkpoint"] = checkpoint
if select:
payload["select"] = select
return self.http.post(
f"/threads/{_quote_path_param(thread_id)}/history",
json=payload,
@@ -102,7 +102,7 @@ class Priority:
@priority.assign
async def assign(ctx: PriorityContext) -> PrioritySettings | None:
return {
"priority": -3 if "premium" in ctx.permissions else 0,
"priority": 3 if "premium" in ctx.permissions else 0,
# Stored unencrypted, so an opaque ID rather than personal data.
"fairness_group": ctx.user["org_id"],
}
@@ -134,7 +134,7 @@ class Priority:
@priority.assign
async def assign(ctx: PriorityContext) -> PrioritySettings | None:
if ctx.cron_id is not None:
return {"priority": 2}
return {"priority": -2}
return None
```
+6 -8
View File
@@ -50,20 +50,18 @@ class PrioritySettings(typing.TypedDict, total=False):
Every key is optional: a missing key or a key set to `None` takes its
default, and returning `None` instead of a dict gives every key its default.
A `priority` outside -5 to 5, or a `fairness_group` longer than 256
characters, is an error.
A value outside its range, or an empty `fairness_group`, is an error.
"""
priority: int | None
"""An integer from -5 to 5, where -5 runs first and 5 runs last. Defaults to 0."""
"""An integer from -5 to 5, where 5 runs first and -5 runs last. Defaults to 0."""
fairness_group: str | None
"""Currently disabled.
The thread's fairness group, a string of at most 256 characters.
Defaults to `"default"`, as does an empty string. It has no effect on run
order for now.
"""The thread's fairness group, a non-empty string. Defaults to `"default"`.
The server stores it unencrypted, even when the deployment uses custom
encryption, so use an opaque ID rather than personal data.
"""
fairness_weight: int | None
"""The weight of the thread's fairness group, an integer from 1 to 100. Defaults to 1."""
-18
View File
@@ -380,8 +380,6 @@ class Run(TypedDict):
"""The run metadata."""
multitask_strategy: MultitaskStrategy
"""Strategy to handle concurrent runs on the same thread."""
cancel_reason: NotRequired[str | None]
"""Why the run was cancelled, if it was. Omitted by older servers."""
class Cron(TypedDict):
@@ -482,21 +480,6 @@ ThreadSelectField = Literal[
"interrupts",
]
ThreadStateSelectField = (
Literal[
"values",
"next",
"tasks",
"checkpoint",
"metadata",
"created_at",
"parent_checkpoint",
"interrupts",
]
| str
)
"""Also accepts `values.<key>` for a single state key, e.g. `values.messages`."""
RunSelectField = Literal[
"run_id",
"thread_id",
@@ -507,7 +490,6 @@ RunSelectField = Literal[
"metadata",
"kwargs",
"multitask_strategy",
"cancel_reason",
]
CronSelectField = Literal[
+4 -259
View File
@@ -651,18 +651,16 @@ def _long_stream_steps(stream_cls, drops):
return steps
def _run_sync(steps, headers=None):
def _run_sync(steps):
handler, seen = _scripted(steps)
with httpx.Client(
transport=httpx.MockTransport(handler), base_url="https://example.com"
) as client:
parts = list(
SyncHttpClient(client).stream("/stream", "POST", json={}, headers=headers)
)
parts = list(SyncHttpClient(client).stream("/stream", "POST", json={}))
return parts, seen
async def _run_async(steps, headers=None):
async def _run_async(steps):
sync_handler, seen = _scripted(steps)
async def handler(request):
@@ -671,12 +669,7 @@ async def _run_async(steps, headers=None):
async with httpx.AsyncClient(
transport=httpx.MockTransport(handler), base_url="https://example.com"
) as client:
parts = [
p
async for p in HttpClient(client).stream(
"/stream", "POST", json={}, headers=headers
)
]
parts = [p async for p in HttpClient(client).stream("/stream", "POST", json={})]
return parts, seen
@@ -851,254 +844,6 @@ async def test_async_reconnect_backs_off(no_sleep):
assert len(no_sleep) == 2 and all(d > 0 for d in no_sleep)
# --- a run stream that closes cleanly before the run ends ---
RUN_STREAM = "/threads/t1/runs/r1/stream"
RUN = "/threads/t1/runs/r1"
END = b"event: end\ndata: null\n\n"
Step = Callable[[httpx.Request], httpx.Response]
def _run_status(status: str) -> Step:
return lambda request: httpx.Response(200, json={"status": status}, request=request)
def _clean_close_steps(stream_cls: type, closes: int, check: Step) -> list[Step]:
steps = [_ok(stream_cls, [_event(0)], location=RUN_STREAM)]
for i in range(1, closes + 1):
steps += [check, _ok(stream_cls, [_event(i)])]
return [*steps, _run_status("success")]
def _stalled_steps(stream_cls: type) -> list[Step]:
return [
_ok(stream_cls, [_event(1)], location=RUN_STREAM),
*[_run_status("running"), _ok(stream_cls, [])] * 5,
_run_status("running"),
]
def _requests(seen: list[httpx.Request]) -> list[tuple[str, str, str | None]]:
return [(r.method, r.url.path, r.headers.get("last-event-id")) for r in seen]
RESUMED = [
("POST", "/stream", None),
("GET", RUN, None),
("GET", RUN_STREAM, "0"),
("GET", RUN, None),
]
CHECKS_THAT_RECONNECT = {
"pending": _run_status("pending"),
"running": _run_status("running"),
"429": _status(429),
"503": _status(503),
"connect_error": _connect_error,
}
@pytest.mark.parametrize(
"check", CHECKS_THAT_RECONNECT.values(), ids=CHECKS_THAT_RECONNECT.keys()
)
def test_sync_clean_close_reconnects_after_check(check: Step, no_sleep: list[float]):
parts, seen = _run_sync(_clean_close_steps(ListByteStream, 1, check))
assert [p.data for p in parts] == [0, 1]
assert _requests(seen) == RESUMED
assert len(no_sleep) == 1
@pytest.mark.parametrize(
"check", CHECKS_THAT_RECONNECT.values(), ids=CHECKS_THAT_RECONNECT.keys()
)
async def test_async_clean_close_reconnects_after_check(
check: Step, no_sleep: list[float]
):
parts, seen = await _run_async(_clean_close_steps(AsyncListByteStream, 1, check))
assert [p.data for p in parts] == [0, 1]
assert _requests(seen) == RESUMED
assert len(no_sleep) == 1
@pytest.mark.usefixtures("no_sleep")
def test_sync_clean_closes_with_progress_do_not_run_out_of_attempts():
parts, _ = _run_sync(_clean_close_steps(ListByteStream, 6, _run_status("running")))
assert [p.data for p in parts] == [0, 1, 2, 3, 4, 5, 6]
@pytest.mark.usefixtures("no_sleep")
async def test_async_clean_closes_with_progress_do_not_run_out_of_attempts():
parts, _ = await _run_async(
_clean_close_steps(AsyncListByteStream, 6, _run_status("running"))
)
assert [p.data for p in parts] == [0, 1, 2, 3, 4, 5, 6]
CHECKS_THAT_END = {
"success": _run_status("success"),
"error": _run_status("error"),
"timeout": _run_status("timeout"),
"interrupted": _run_status("interrupted"),
"403": _status(403),
"404": _status(404),
"not_json": _not_sse,
"json_without_status": lambda request: httpx.Response(
200, json={}, request=request
),
}
CHECKED = [("POST", "/stream", None), ("GET", RUN, None)]
@pytest.mark.usefixtures("no_sleep")
@pytest.mark.parametrize("check", CHECKS_THAT_END.values(), ids=CHECKS_THAT_END.keys())
def test_sync_clean_close_ends_the_stream_after_check(check: Step):
parts, seen = _run_sync(
[_ok(ListByteStream, [_event(1)], location=RUN_STREAM), check]
)
assert [p.data for p in parts] == [1]
assert _requests(seen) == CHECKED
@pytest.mark.usefixtures("no_sleep")
@pytest.mark.parametrize("check", CHECKS_THAT_END.values(), ids=CHECKS_THAT_END.keys())
async def test_async_clean_close_ends_the_stream_after_check(check: Step):
parts, seen = await _run_async(
[_ok(AsyncListByteStream, [_event(1)], location=RUN_STREAM), check]
)
assert [p.data for p in parts] == [1]
assert _requests(seen) == CHECKED
NO_CHECK = {
"end": lambda s: [_ok(s, [_event(1), END], location=RUN_STREAM)],
"error": lambda s: [
_ok(s, [_event(1), b"event: error\ndata: {}\n\n"], location=RUN_STREAM)
],
"unterminated_end": lambda s: [
_ok(s, [_event(1), b"event: end\ndata: null\n"], location=RUN_STREAM)
],
"end_on_an_earlier_connection": lambda s: [
_ok(s, [_event(1), END], drop=True, location=RUN_STREAM),
_ok(s, []),
],
"thread_stream": lambda s: [_ok(s, [_event(1)], location="/threads/t1/stream")],
"threadless_run_location": lambda s: [
_ok(s, [_event(1)], location="/runs/r1/stream")
],
}
@pytest.mark.usefixtures("no_sleep")
@pytest.mark.parametrize("steps", NO_CHECK.values(), ids=NO_CHECK.keys())
def test_sync_stream_ends_without_a_run_check(steps: Callable[[type], list[Step]]):
_, seen = _run_sync(steps(ListByteStream))
assert [r.url.path for r in seen if not r.url.path.endswith("/stream")] == []
@pytest.mark.usefixtures("no_sleep")
@pytest.mark.parametrize("steps", NO_CHECK.values(), ids=NO_CHECK.keys())
async def test_async_stream_ends_without_a_run_check(
steps: Callable[[type], list[Step]],
):
_, seen = await _run_async(steps(AsyncListByteStream))
assert [r.url.path for r in seen if not r.url.path.endswith("/stream")] == []
def test_sync_clean_closes_without_progress_give_up_after_five_attempts(
no_sleep: list[float],
):
with pytest.raises(httpx.TransportError, match=GIVE_UP) as exc_info:
_run_sync(_stalled_steps(ListByteStream))
assert "still pending or running" in str(exc_info.value.__cause__)
_assert_backed_off_for_about_20s(no_sleep)
async def test_async_clean_closes_without_progress_give_up_after_five_attempts(
no_sleep: list[float],
):
with pytest.raises(httpx.TransportError, match=GIVE_UP) as exc_info:
await _run_async(_stalled_steps(AsyncListByteStream))
assert "still pending or running" in str(exc_info.value.__cause__)
_assert_backed_off_for_about_20s(no_sleep)
CHECK_URLS = {
"path": ("/threads/t1/runs/r1/stream", "https://example.com/threads/t1/runs/r1"),
"prefix_and_query": (
"/api/threads/t1/runs/r1/stream?stream_mode=values",
"https://example.com/api/threads/t1/runs/r1",
),
"absolute": (
"https://example.com/threads/t1/runs/r1/stream",
"https://example.com/threads/t1/runs/r1",
),
}
@pytest.mark.usefixtures("no_sleep")
@pytest.mark.parametrize(
("location", "url"), CHECK_URLS.values(), ids=CHECK_URLS.keys()
)
def test_sync_run_check_gets_the_run_behind_the_stream_location(
location: str, url: str
):
_, seen = _run_sync(
[_ok(ListByteStream, [_event(1)], location=location), _run_status("success")]
)
assert str(seen[-1].url) == url
@pytest.mark.usefixtures("no_sleep")
@pytest.mark.parametrize(
("location", "url"), CHECK_URLS.values(), ids=CHECK_URLS.keys()
)
async def test_async_run_check_gets_the_run_behind_the_stream_location(
location: str, url: str
):
_, seen = await _run_async(
[
_ok(AsyncListByteStream, [_event(1)], location=location),
_run_status("success"),
]
)
assert str(seen[-1].url) == url
CALLER_HEADERS = {"Authorization": "Bearer token", "Last-Event-ID": "0"}
BODY_AND_CURSOR_HEADERS = {"content-type", "content-length", "last-event-id"}
def _assert_run_check_headers(seen: list[httpx.Request]) -> None:
check = seen[-1]
assert check.url.path == RUN
assert check.headers["authorization"] == "Bearer token"
assert check.headers["accept"] == "application/json"
assert check.headers.keys() & BODY_AND_CURSOR_HEADERS == set()
@pytest.mark.usefixtures("no_sleep")
def test_sync_run_check_sends_caller_headers_and_accepts_json_without_body_or_cursor():
_, seen = _run_sync(
[_ok(ListByteStream, [_event(1)], location=RUN_STREAM), _run_status("success")],
headers=CALLER_HEADERS,
)
_assert_run_check_headers(seen)
@pytest.mark.usefixtures("no_sleep")
async def test_async_run_check_sends_caller_headers_and_accepts_json_without_body_or_cursor():
_, seen = await _run_async(
[
_ok(AsyncListByteStream, [_event(1)], location=RUN_STREAM),
_run_status("success"),
],
headers=CALLER_HEADERS,
)
_assert_run_check_headers(seen)
# --- _sse_to_v2_dict conversion ---
+5 -1
View File
@@ -120,5 +120,9 @@ def test_context_is_frozen():
def test_every_settings_key_is_optional():
assert PrioritySettings.__optional_keys__ == {"priority", "fairness_group"}
assert PrioritySettings.__optional_keys__ == {
"priority",
"fairness_group",
"fairness_weight",
}
assert PrioritySettings.__required_keys__ == frozenset()
-126
View File
@@ -11,14 +11,6 @@ from langgraph_sdk.client import (
SyncThreadsClient,
ThreadsClient,
)
from langgraph_sdk.schema import Checkpoint
CHECKPOINT: Checkpoint = {
"thread_id": "thread_123",
"checkpoint_ns": "",
"checkpoint_id": "cp_1",
"checkpoint_map": None,
}
@pytest.mark.asyncio
@@ -64,121 +56,3 @@ def test_sync_threads_update_return_minimal():
)
assert result is None
@pytest.mark.asyncio
async def test_async_threads_get_state_select():
requests: list[httpx.Request] = []
async def handler(request: httpx.Request) -> httpx.Response:
requests.append(request)
return httpx.Response(200, json={})
transport = httpx.MockTransport(handler)
async with httpx.AsyncClient(
transport=transport, base_url="https://example.com"
) as client:
threads_client = ThreadsClient(HttpClient(client))
await threads_client.get_state("thread_123", select=["values.messages", "next"])
await threads_client.get_state(
"thread_123", checkpoint_id="cp_1", select=["values.messages"]
)
await threads_client.get_state(
"thread_123",
checkpoint=CHECKPOINT,
select=["values.messages"],
)
await threads_client.get_state("thread_123")
latest, by_id, by_checkpoint, default = requests
assert latest.method == "GET"
assert latest.url.path == "/threads/thread_123/state"
assert latest.url.params.get_list("select") == ["values.messages", "next"]
assert by_id.method == "GET"
assert by_id.url.path == "/threads/thread_123/state/cp_1"
assert by_id.url.params.get_list("select") == ["values.messages"]
assert by_checkpoint.method == "POST"
assert by_checkpoint.url.path == "/threads/thread_123/state/checkpoint"
assert json.loads(by_checkpoint.content) == {
"checkpoint": CHECKPOINT,
"subgraphs": False,
"select": ["values.messages"],
}
assert "select" not in default.url.params
@pytest.mark.asyncio
async def test_async_threads_get_history_select():
bodies: list[dict] = []
async def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/threads/thread_123/history"
bodies.append(json.loads(request.content))
return httpx.Response(200, json=[])
transport = httpx.MockTransport(handler)
async with httpx.AsyncClient(
transport=transport, base_url="https://example.com"
) as client:
threads_client = ThreadsClient(HttpClient(client))
await threads_client.get_history("thread_123", select=["values.messages"])
await threads_client.get_history("thread_123")
assert bodies == [{"limit": 10, "select": ["values.messages"]}, {"limit": 10}]
def test_sync_threads_get_state_select():
requests: list[httpx.Request] = []
def handler(request: httpx.Request) -> httpx.Response:
requests.append(request)
return httpx.Response(200, json={})
transport = httpx.MockTransport(handler)
with httpx.Client(transport=transport, base_url="https://example.com") as client:
threads_client = SyncThreadsClient(SyncHttpClient(client))
threads_client.get_state("thread_123", select=["values.messages", "next"])
threads_client.get_state(
"thread_123", checkpoint_id="cp_1", select=["values.messages"]
)
threads_client.get_state(
"thread_123",
checkpoint=CHECKPOINT,
select=["values.messages"],
)
threads_client.get_state("thread_123")
latest, by_id, by_checkpoint, default = requests
assert latest.method == "GET"
assert latest.url.path == "/threads/thread_123/state"
assert latest.url.params.get_list("select") == ["values.messages", "next"]
assert by_id.method == "GET"
assert by_id.url.path == "/threads/thread_123/state/cp_1"
assert by_id.url.params.get_list("select") == ["values.messages"]
assert by_checkpoint.method == "POST"
assert by_checkpoint.url.path == "/threads/thread_123/state/checkpoint"
assert json.loads(by_checkpoint.content) == {
"checkpoint": CHECKPOINT,
"subgraphs": False,
"select": ["values.messages"],
}
assert "select" not in default.url.params
def test_sync_threads_get_history_select():
bodies: list[dict] = []
def handler(request: httpx.Request) -> httpx.Response:
assert request.method == "POST"
assert request.url.path == "/threads/thread_123/history"
bodies.append(json.loads(request.content))
return httpx.Response(200, json=[])
transport = httpx.MockTransport(handler)
with httpx.Client(transport=transport, base_url="https://example.com") as client:
threads_client = SyncThreadsClient(SyncHttpClient(client))
threads_client.get_history("thread_123", select=["values.messages"])
threads_client.get_history("thread_123")
assert bodies == [{"limit": 10, "select": ["values.messages"]}, {"limit": 10}]
+1 -1
View File
@@ -301,7 +301,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "1.2.20"
version = "1.2.18"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },